Error Strategy
Layer errors: precise domain enums inside, transport mapping at edges, context aggregation in binaries.
Recipe
// domain
pub enum OrderError { NotFound(OrderId), InvalidState }
// api
impl IntoResponse for ApiError {
fn into_response(self) -> Response { /* status + body */ }
}When to reach for this: More than one crate or external API surface.
Working Example
User request -> ApiError -> OrderError -> sqlx::Error
- Domain returns
Result<T, OrderError> - Infra impl maps
sqlx::ErrortoOrderError::Database - API maps
OrderErrorto404/409with problem+json mainusesanyhowonly for startup banner errors
What this demonstrates:
- Variants align with business cases, not driver strings
#[from]for infra translation once- Public HTTP body differs from internal Debug logs
Deep Dive
Workspace error crate optional for shared AppError wrapper. Use #[non_exhaustive] on public enums.
Gotchas
- Leaking sqlx to handlers - coupling. Fix: map at repository boundary.
- Single giant Error enum - unmaintainable. Fix: per bounded context enum.
- Display as API contract - i18n pain. Fix: stable error codes in JSON.
- anyhow in library - avoid.
- panic in mapper - unreachable variants should be
#[non_exhaustive]handled.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
| snafu | error crates with context | team standard thiserror |
| StatusCode only | internal tools | Public API |
FAQs
retry mapping?
Mark OrderError::Transient for middleware retry.
grpc mapping?
tonic::Status from domain variant table.
client SDK?
Export stable error code enum mirroring server.
log levels?
4xx warn, 5xx error, with trace id.
test mapping?
Table tests variant -> status.
serde errors?
Separate DeserializationError at boundary before domain.
multiple apis?
Shared ApiError in api crate re-exported.
no_std domain?
core::error::Error trait manual impls.
observability?
tracing::error!(error=?e) on 5xx path.
migration?
Add variants without breaking with non_exhaustive.
Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.