masterror 0.3.1

Application error types and response mapping
Documentation

masterror · Framework-agnostic application error types

Crates.io docs.rs Downloads MSRV License CI

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

[dependencies]
masterror = { version = "0.3", default-features = false }
# or with features:
# masterror = { version = "0.3", features = [
#   "axum", "actix", "serde_json", "openapi",
#   "sqlx", "reqwest", "redis", "validator", "config", "tokio"
# ] }

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.
  • Consistent workspace. Same error surface across crates.
[dependencies]
# lean core
masterror = { version = "0.3", default-features = false }

# with Axum/Actix + JSON + integrations
# masterror = { version = "0.3", features = [
#   "axum", "actix", "serde_json", "openapi",
#   "sqlx", "reqwest", "redis", "validator", "config", "tokio"
# ] }

MSRV: 1.89
No unsafe: forbidden by crate.

Create an error:

use masterror::{AppError, AppErrorKind};

let err = AppError::new(AppErrorKind::BadRequest, "Flag must be set");
assert!(matches!(err.kind, AppErrorKind::BadRequest));

With prelude:

use masterror::prelude::*;

fn do_work(flag: bool) -> AppResult<()> {
    if !flag {
        return Err(AppError::bad_request("Flag must be set"));
    }
    Ok(())
}
use masterror::{AppError, AppErrorKind, AppCode, ErrorResponse};

let app_err = AppError::new(AppErrorKind::Unauthorized, "Token expired");
let resp: ErrorResponse = (&app_err).into()
    .with_retry_after_secs(30)
    .with_www_authenticate(r#"Bearer realm="api", error="invalid_token""#);

assert_eq!(resp.status, 401);
// features = ["axum", "serde_json"]
use masterror::{AppError, AppResult};
use axum::{routing::get, Router};

async fn handler() -> AppResult<&'static str> {
    Err(AppError::forbidden("No access"))
}

let app = Router::new().route("/demo", get(handler));
// features = ["actix", "serde_json"]
use actix_web::{get, App, HttpServer, Responder};
use masterror::prelude::*;

#[get("/err")]
async fn err() -> AppResult<&'static str> {
    Err(AppError::forbidden("No access"))
}

#[get("/payload")]
async fn payload() -> impl Responder {
    ErrorResponse::new(422, AppCode::Validation, "Validation failed")
}
[dependencies]
masterror = { version = "0.3", features = ["openapi", "serde_json"] }
utoipa = "5"
  • axum — IntoResponse
  • actix — ResponseError/Responder
  • openapi — utoipa schema
  • serde_json — JSON details
  • sqlx, redis, reqwest, validator, config, tokio, multipart
  • std::io::Error → Internal
  • String → BadRequest
  • sqlx::Error → NotFound/Database
  • redis::RedisError → Service
  • reqwest::Error → Timeout/Network/ExternalApi
  • validator::ValidationErrors → Validation
  • config::ConfigError → Config
  • tokio::time::error::Elapsed → Timeout

Minimal core:

masterror = { version = "0.3", default-features = false }

API (Axum + JSON + deps):

masterror = { version = "0.3", features = [
  "axum", "serde_json", "openapi",
  "sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }

API (Actix + JSON + deps):

masterror = { version = "0.3", features = [
  "actix", "serde_json", "openapi",
  "sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
  • Use ErrorResponse::new(status, AppCode::..., "msg") instead of legacy
  • New helpers: .with_retry_after_secs, .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).

  • Not a general-purpose error aggregator like anyhow
  • Not a replacement for your domain errors

Apache-2.0 OR MIT, at your option.