Skip to content

Error handling#380

Closed
kearfy wants to merge 6 commits intomainfrom
micha/error-handling
Closed

Error handling#380
kearfy wants to merge 6 commits intomainfrom
micha/error-handling

Conversation

@kearfy
Copy link
Member

@kearfy kearfy commented Feb 16, 2026

Structured error handling for SurrealDB v3

Adds full support for the structured error format introduced in SurrealDB v3.

What changed

V3 detail format support — SurrealDB v3 switched from serde externally-tagged enums ({"Auth": "TokenExpired"}) to an internally-tagged format ({"kind": "Auth", "details": {"kind": "TokenExpired"}}). New helpers (detailKind, detailInner, getDetailString) handle the v3 format while retaining full backward compatibility with the old format.

Recursive cause chainServerError now carries a cause *ServerError field, matching Rust's cause: Option<Box<Error>>. The chain is parsed recursively from both RPC errors and query result errors. Since Go has no automatic error-chain display (unlike JS's Error.cause or Python's __cause__), Error() now joins the full cause chain with ": " so the complete context is visible in logs. A new Message() method returns just the current error's message without the chain.

Cause chain traversalServerCause(), Unwrap(), HasKind(), and FindCause() methods on *ServerError, plus top-level HasKind(err, kind) and FindCause(err, kind) functions that work on any error.

Kind-aware errors.IsIs() now supports optional kind matching: errors.Is(err, &ServerError{}) matches any server error (catch-all), while errors.Is(err, &ServerError{kind: "NotFound"}) only matches that specific kind.

New detail accessorsSessionID() and TargetName() for parity with the Python SDK. Updated doc comments on NamespaceName() and DatabaseName() to reflect they work for both NotFound and AlreadyExists kinds.

Double-wrapped details unwrapping — The server's query result path may double-wrap details (e.g. {"kind": "AlreadyExists", "details": {"kind": "Record", ...}}). parseQueryError now detects and unwraps this so detail accessors like RecordID() work correctly.

Integration tests — New contrib/testenv/structured_errors_integration_test.go validates structured errors against a live SurrealDB v3 instance: invalid credentials, parse errors, THROW, duplicate records, schema violations, and mixed multi-statement queries.

CI — Updated test matrix from v3.0.0-beta.2 to v3.0.0 GA.

Lint cleanup — Resolved all 32 pre-existing golangci-lint issues across the codebase (gosec, gocritic, gofmt, misspell, staticcheck, unparam). The repo now passes golangci-lint run ./... with zero issues.

API surface

// Cause chain
err.ServerCause() *ServerError   // direct cause
err.Unwrap() error               // for errors.Unwrap/Is/As
err.HasKind(kind string) bool    // check entire chain
err.FindCause(kind string) *SE   // find first match in chain
err.Error() string               // "msg: cause: root" (full chain)
err.Message() string             // "msg" (this error only)

// Top-level helpers (work on any error)
HasKind(err, kind) bool
FindCause(err, kind) *ServerError

// Kind-aware Is()
errors.Is(err, &ServerError{})                  // catch-all
errors.Is(err, &ServerError{kind: "NotFound"})  // kind-specific

// New detail accessors
err.SessionID() string   // NotFound/AlreadyExists Session detail
err.TargetName() string  // NotAllowed Target detail

Test plan

  • All existing unit tests updated and passing
  • V3 internally-tagged format tests (all error kinds and detail variants)
  • Old externally-tagged format backward compatibility tests
  • Cause chain traversal: deep recursion, HasKind, FindCause, Unwrap
  • Double-wrapped detail unwrapping (query result path)
  • Error() chain formatting and Message() isolation
  • Kind-aware Is() matching
  • SessionID() and TargetName() accessors (V3 and old format)
  • Integration tests against live SurrealDB v3.0.0
  • golangci-lint run ./... passes with 0 issues

@kearfy kearfy marked this pull request as ready for review February 24, 2026 12:02
@mumoshu
Copy link
Contributor

mumoshu commented Mar 3, 2026

I discussed with @kearfy and am going with a simpler approach with minimal API surface for now, #382
Thanks for your work and contribution, @kearfy!

@mumoshu mumoshu closed this Mar 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants