masterror · Framework-agnostic application error types
Small, pragmatic error model for API-heavy Rust services. Core is framework-agnostic; integrations are opt-in via feature flags. Stable categories, conservative HTTP mapping, no unsafe
.
- Core types:
AppError
,AppErrorKind
,AppResult
,ErrorResponse
- Optional Axum
IntoResponse
- Optional OpenAPI schema (via
utoipa
) - Conversions from
sqlx
,reqwest
,redis
,validator
,config
,tokio
Why this crate?
- Stable, predictable taxonomy. A small set of error categories (
AppErrorKind
) that map conservatively to HTTP. Easy to reason about, safe to expose, and consistent across services. - Framework-agnostic core. No web framework assumptions. No
unsafe
. MSRV pinned. Works in libraries and binaries alike. - Opt-in integrations. Zero default features. You pull only what you need:
axum
(HTTPIntoResponse
)serde_json
(JSON details)openapi
(schemas viautoipa
)sqlx
,reqwest
,redis
,validator
,config
,tokio
,multipart
(error conversions)
- Clean wire contract. A small
ErrorResponse { status, message, details? }
payload for HTTP, with optional OpenAPI schema. No leaking of internal sources into the response. - One log at the boundary. Use
tracing
once when converting to HTTP, avoiding duplicate logs and keeping fields stable (kind
,status
,message
). - Less boilerplate. Built-in
From<...>
conversions for common libs and a compact prelude for handler signatures. - Consistent across a workspace. Share the same error surface between services and crates, making clients and tests simpler.
Installation
[]
# lean core, no extra deps
= { = "0.1", = false }
# Or with features:
# JSON + Axum + common integrations
# masterror = { version = "0.1", features = [
# "axum", "serde_json", "openapi",
# "sqlx", "reqwest", "redis", "validator", "config", "tokio"
# ] }
MSRV: 1.89
No unsafe: this crate forbids unsafe
.
Quick start
Create an error with a semantic kind and an optional public message:
use ;
let err = new;
assert!;
Use the prelude to keep signatures tidy:
use *;
Error response payload
ErrorResponse
is a wire-level payload for HTTP APIs. You can build it directly or convert from AppError
:
use ;
let app_err = new;
let resp: ErrorResponse = .into;
assert_eq!;
Axum integration
Enable axum
(and usually serde_json
) to return errors directly from handlers:
// requires: features = ["axum", "serde_json"]
use ;
use ;
async
let app = new.route;
Note: for JSON bodies you typically need both
axum
andserde_json
features enabled.
OpenAPI
Enable openapi
to derive an OpenAPI schema for ErrorResponse
(via utoipa
).
[]
= { = "0.1", = ["openapi", "serde_json"] }
= "5"
Feature flags
axum
—IntoResponse
forAppError
and JSON responsesopenapi
— schema forErrorResponse
viautoipa
serde_json
— JSON details supportsqlx
—From<sqlx::Error>
redis
—From<redis::RedisError>
validator
—From<validator::ValidationErrors>
config
—From<config::ConfigError>
tokio
—From<tokio::time::error::Elapsed>
reqwest
—From<reqwest::Error>
multipart
— compatibility flag for projects using multipart in Axum
Conversions
All mappings are conservative and avoid leaking internals:
std::io::Error
→Internal
String
→BadRequest
sqlx::Error
→NotFound
(forRowNotFound
) orDatabase
redis::RedisError
→Service
reqwest::Error
→Timeout
/Network
/ExternalApi
validator::ValidationErrors
→Validation
config::ConfigError
→Config
tokio::time::error::Elapsed
→Timeout
Typical setups
Minimal core:
= { = "0.1", = false }
API service (Axum + JSON + common deps):
= { = "0.1", = [
"axum", "serde_json", "openapi",
"sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
Versioning and MSRV
- Semantic versioning. Breaking API or wire-contract changes bump the major version.
- MSRV: 1.89 (may be raised in a minor release with a changelog note, never in a patch).
Non-goals
- Not a general-purpose error aggregator like
anyhow
for CLIs. - Not a replacement for your domain errors. Use it as the public API surface and transport mapping.
License
Licensed under either of
- Apache License, Version 2.0
- MIT license
at your option.