# xerrors Structured application errors with stable typed codes, cause chaining, and key-value context fields. ## Purpose `xerrors` provides a single error type — `*Err` — that carries a machine-readable `Code`, a human-readable message, an optional cause, and optional key-value fields. It replaces ad-hoc string errors and sentinel variables with a consistent, structured, JSON-serialisable error model that works across service boundaries, log pipelines, and HTTP transports. ## Tier & Dependencies **Tier:** 0 **Imports:** `encoding/json`, `fmt` (stdlib only) **Must NOT import:** `logz`, `rbac`, `launcher`, or any other micro-lib module. The logz bridge is achieved via duck-typed private interfaces — no import required. ## Key Design Decisions - Typed error codes as a `string` type alias — stable wire values aligned with gRPC status names. See `docs/adr/ADR-001-typed-error-codes.md`. - `*Err` implements `Unwrap`, `ErrorCode`, `ErrorContext`, and `json.Marshaler` for full stdlib compatibility and automatic log enrichment. See `docs/adr/ADR-002-stdlib-errors-compatibility.md`. - Twelve codes cover the gRPC canonical set (`INVALID_ARGUMENT`, `NOT_FOUND`, `INTERNAL`, `ALREADY_EXISTS`, `PERMISSION_DENIED`, `UNAUTHENTICATED`, `GONE`, `FAILED_PRECONDITION`, `RESOURCE_EXHAUSTED`, `CANCELLED`, `UNIMPLEMENTED`, `UNAVAILABLE`, `DEADLINE_EXCEEDED`). ## Patterns **Creating errors:** ```go // Primary factory err := xerrors.New(xerrors.ErrNotFound, "user not found") // Convenience constructors (most common codes) err := xerrors.NotFound("user %s not found", userID) err := xerrors.InvalidInput("email is required") err := xerrors.Internal("unexpected database state") // Wrapping a cause err := xerrors.Wrap(xerrors.ErrInternal, "failed to query database", dbErr) // Builder pattern for structured context err := xerrors.New(xerrors.ErrInvalidInput, "validation failed"). WithContext("field", "email"). WithContext("rule", "required"). WithError(cause) ``` **Inspecting errors:** ```go var e *xerrors.Err if errors.As(err, &e) { switch e.Code() { case xerrors.ErrNotFound: // handle 404 case xerrors.ErrInvalidInput: // handle 400 } } ``` **Cause chain traversal:** ```go // errors.Is and errors.As walk through *Err via Unwrap if errors.Is(err, sql.ErrNoRows) { ... } ``` **JSON serialisation (API responses):** ```go // *Err marshals to: {"code":"NOT_FOUND","message":"user not found","fields":{...}} json.NewEncoder(w).Encode(err) ``` **Automatic log enrichment (no extra code needed):** ```go // If err is *Err, logz appends error_code and context fields automatically logger.Error("request failed", err) ``` ## What to Avoid - Do not match on `err.Error()` strings. Always use `errors.As` + `e.Code()`. - Do not add HTTP status code logic to this package. HTTP mapping belongs in the transport layer. - Do not add new code constants unless they map to a gRPC canonical status name. - Do not import `logz` from this package. The duck-type bridge (`ErrorCode`, `ErrorContext`) keeps the two packages decoupled. - `ErrorContext()` returns the live internal map — do not mutate it. Use `Fields()` if you need a safe copy. ## Testing Notes - `compliance_test.go` uses compile-time nil-pointer assertions to enforce that `*Err` satisfies the `error`, `Unwrap`, `ErrorCode`, `ErrorContext`, and `json.Marshaler` contracts. These assertions have zero runtime cost. - `xerrors_test.go` covers construction, chaining, builder methods, and `errors.Is`/`errors.As` behaviour. - No test setup is needed — all tests use plain Go with no external dependencies.