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
,AppCode
,ErrorResponse
- Optional Axum/Actix integration
- Optional OpenAPI schema (via
utoipa
) - Conversions from
sqlx
,reqwest
,redis
,validator
,config
,tokio
TL;DR
[]
= { = "0.5.0", = false }
# or with features:
# masterror = { version = "0.5.0", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "reqwest", "redis", "validator",
# "config", "tokio", "multipart", "teloxide",
# "telegram-webapp-sdk", "frontend", "turnkey"
# ] }
Since v0.5.0: derive custom errors via #[derive(Error)]
(use masterror::Error;
) and inspect browser logging failures with BrowserConsoleError::context()
.
Since v0.4.0: optional frontend
feature for WASM/browser console logging.
Since v0.3.0: stable AppCode
enum and extended ErrorResponse
with retry/authentication metadata.
- Stable taxonomy. Small set of
AppErrorKind
categories mapping conservatively to HTTP. - Framework-agnostic. No assumptions, no
unsafe
, MSRV pinned. - Opt-in integrations. Zero default features; you enable what you need.
- Clean wire contract.
ErrorResponse { status, code, message, details?, retry?, www_authenticate? }
. - One log at boundary. Log once with
tracing
. - Less boilerplate. Built-in conversions, compact prelude, and the
masterror::Error
re-export ofthiserror::Error
with#[from]
/#[error(transparent)]
support. - Consistent workspace. Same error surface across crates.
[]
# lean core
= { = "0.5.0", = false }
# with Axum/Actix + JSON + integrations
# masterror = { version = "0.5.0", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "reqwest", "redis", "validator",
# "config", "tokio", "multipart", "teloxide",
# "telegram-webapp-sdk", "frontend", "turnkey"
# ] }
MSRV: 1.89 No unsafe: forbidden by crate.
Create an error:
use ;
let err = new;
assert!;
With prelude:
use *;
use io;
use Error;
;
let err = load.unwrap_err;
assert_eq!;
let wrapped = from;
assert_eq!;
use masterror::Error;
re-exportsthiserror::Error
.#[from]
automatically implementsFrom<...>
while ensuring wrapper shapes are valid.#[error(transparent)]
enforces single-field wrappers that forwardDisplay
/source
to the inner error.
use ;
use Duration;
let app_err = new;
let resp: ErrorResponse = .into
.with_retry_after_duration
.with_www_authenticate;
assert_eq!;
// features = ["axum", "serde_json"]
...
assert!;
Ok
}
- On non-WASM targets
log_to_browser_console
returnsBrowserConsoleError::UnsupportedTarget
. BrowserConsoleError::context()
exposes optional browser diagnostics for logging/telemetry when console logging fails.
axum
— IntoResponse integration with structured JSON bodiesactix
— Actix Web ResponseError and Responder implementationsopenapi
— Generate utoipa OpenAPI schema for ErrorResponseserde_json
— Attach structured JSON details to AppErrorsqlx
— Classify sqlx::Error variants into AppError kindsreqwest
— Classify reqwest::Error as timeout/network/external APIredis
— Map redis::RedisError into cache-aware AppErrorvalidator
— Convert validator::ValidationErrors into validation failuresconfig
— Propagate config::ConfigError as configuration issuestokio
— Classify tokio::time::error::Elapsed as timeoutmultipart
— Handle axum multipart extraction errorsteloxide
— Convert teloxide_core::RequestError into domain errorstelegram-webapp-sdk
— Surface Telegram WebApp validation failuresfrontend
— Log to the browser console and convert to JsValue on WASMturnkey
— Ship Turnkey-specific error taxonomy and conversions
std::io::Error
→ InternalString
→ BadRequestsqlx::Error
→ NotFound/Databaseredis::RedisError
→ Cachereqwest::Error
→ Timeout/Network/ExternalApiaxum::extract::multipart::MultipartError
→ BadRequestvalidator::ValidationErrors
→ Validationconfig::ConfigError
→ Configtokio::time::error::Elapsed
→ Timeoutteloxide_core::RequestError
→ RateLimited/Network/ExternalApi/Deserialization/Internaltelegram_webapp_sdk::utils::validate_init_data::ValidationError
→ TelegramAuth
Minimal core:
= { = "0.5.0", = false }
API (Axum + JSON + deps):
= { = "0.5.0", = [
"axum", "serde_json", "openapi",
"sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
API (Actix + JSON + deps):
= { = "0.5.0", = [
"actix", "serde_json", "openapi",
"sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
// features = ["turnkey"]
use ;
use ;
// Classify a raw SDK/provider error
let kind = classify_turnkey_error;
assert!;
// Wrap into AppError
let e = new;
let app: AppError = e.into;
assert_eq!;
- Use
ErrorResponse::new(status, AppCode::..., "msg")
instead of legacy - New helpers:
.with_retry_after_secs
,.with_retry_after_duration
,.with_www_authenticate
ErrorResponse::new_legacy
is temporary shim
Semantic versioning. Breaking API/wire contract → major bump. MSRV = 1.89 (may raise in minor, never in patch).
cargo +nightly fmt --
cargo clippy -- -D warnings
cargo test --all
cargo build
(regenerates README.md from the template)cargo doc --no-deps
cargo package --locked
- Not a general-purpose error aggregator like
anyhow
- Not a replacement for your domain errors
Apache-2.0 OR MIT, at your option.