masterror 0.2.1

Application error types and response mapping
Documentation

masterror · Framework-agnostic application error types

Crates.io docs.rs Downloads MSRV License

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/Actix integration
  • 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 (HTTP IntoResponse)
    • actix (ready-to-use integration)
    • serde_json (JSON details)
    • openapi (schemas via utoipa)
    • 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

[dependencies]
# lean core, no extra deps
masterror = { version = "0.2", default-features = false }

# Or with features:
# JSON + Axum/Actix + common integrations
# masterror = { version = "0.2", features = [
#   "axum", "actix", "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 masterror::{AppError, AppErrorKind};

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

Use the prelude to keep signatures tidy:

use masterror::prelude::*;

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

Error response payload

ErrorResponse is a wire-level payload for HTTP APIs. You can build it directly or convert from AppError:

use masterror::{AppError, AppErrorKind, ErrorResponse};

let app_err = AppError::new(AppErrorKind::NotFound, "User not found");
let resp: ErrorResponse = (&app_err).into();
assert_eq!(resp.status, 404);

Web framework integrations

Axum

Enable axum (and usually serde_json) to return errors directly from handlers:

// requires: 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));

Actix

Enable actix (and usually serde_json) to return errors directly from handlers:

// requires: 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, "Validation failed")
}

OpenAPI

Enable openapi to derive an OpenAPI schema for ErrorResponse (via utoipa).

[dependencies]
masterror = { version = "0.2", features = ["openapi", "serde_json"] }
utoipa = "5"

Feature flags

  • axumIntoResponse for AppError and JSON responses
  • actixResponseError/Responder integration
  • openapi — schema for ErrorResponse via utoipa
  • serde_json — JSON details support
  • sqlxFrom<sqlx::Error>
  • redisFrom<redis::RedisError>
  • validatorFrom<validator::ValidationErrors>
  • configFrom<config::ConfigError>
  • tokioFrom<tokio::time::error::Elapsed>
  • reqwestFrom<reqwest::Error>
  • multipart — compatibility flag for projects using multipart in Axum

Conversions

All mappings are conservative and avoid leaking internals:

  • std::io::ErrorInternal
  • StringBadRequest
  • sqlx::ErrorNotFound (for RowNotFound) or Database
  • redis::RedisErrorService
  • reqwest::ErrorTimeout / Network / ExternalApi
  • validator::ValidationErrorsValidation
  • config::ConfigErrorConfig
  • tokio::time::error::ElapsedTimeout

Typical setups

Minimal core:

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

API service (Axum + JSON + common deps):

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

API service (Actix + JSON + common deps):

masterror = { version = "0.2", features = [
  "actix", "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.