Files
xerrors/CLAUDE.md
Rene Nochebuena 3cc36801a1 feat(xerrors): initial stable release v0.9.0
Structured application errors with typed codes, cause chaining, key-value context fields, and zero-import logz enrichment bridge.

What's included:
- `*Err` type implementing error, errors.Unwrap, json.Marshaler, ErrorCode(), and ErrorContext()
- Twelve typed Code constants aligned with gRPC canonical status names
- New / Wrap factory constructors plus InvalidInput / NotFound / Internal convenience constructors
- Builder methods WithContext and WithError for attaching structured fields and causes
- Duck-typed ErrorCode() / ErrorContext() bridge so logz auto-enriches log records without an import

Tested-via: todo-api POC integration
Reviewed-against: docs/adr/
2026-03-18 13:09:31 -06:00

3.6 KiB

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:

// 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:

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:

// errors.Is and errors.As walk through *Err via Unwrap
if errors.Is(err, sql.ErrNoRows) { ... }

JSON serialisation (API responses):

// *Err marshals to: {"code":"NOT_FOUND","message":"user not found","fields":{...}}
json.NewEncoder(w).Encode(err)

Automatic log enrichment (no extra code needed):

// 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.