Generate each domain's error types with a declarative macro
Decision
Declare each backend domain's error codes once, in a macro_rules! macro of our own, and let the macro generate the rest. For each domain it emits a code enum exposed to GraphQL as an async-graphql enum, an error struct that keeps its source chain, a tracing error event on construction, the ErrorExtensions impl that writes the code into GraphQL error extensions (ADR-0006), a per-code retry disposition the client reads to decide whether to retry, and a Result alias.
Domain errors, such as a failed validation or a broken business rule, use these codes. Infrastructure errors, such as an sqlx or S3 failure, propagate with ? into generic application codes and are never wrapped in a domain code. The macro generates no From impl for any external error type, so wrapping one takes a deliberate with_source call that keeps the cause.
Why
Every API error needs a machine-readable code the iOS app can switch on. The backend also wants the error's source chain for debugging and a trace event when the error is created. No standard crate provides the combination.
The LLM agents writing backend code kept wrapping infrastructure errors, such as a UUID parse or an sqlx failure, in domain codes instead of propagating them. After we cleaned that up across the domains, we moved the rule into the macro so the compiler enforces it.
Rejected alternatives
- anyhow. It erases the error's type and leaves a string where the iOS app needs a code.
- thiserror for the API's domain errors. It produces typed errors but no GraphQL extensions and no tracing, so every error type would still need the same hand-written glue. Hand-written glue is where infrastructure errors kept getting wrapped in domain codes.
Consequences
The macro's output decides which errors get codes, so review doesn't have to. The macro itself takes effort to read, and an author still has to judge whether a failure deserves a domain code or should propagate.