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

109 lines
3.6 KiB
Markdown

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