Crates.io | masterror |
lib.rs | masterror |
version | 0.3.1 |
created_at | 2025-08-12 03:43:17.571937+00 |
updated_at | 2025-08-25 08:36:27.061113+00 |
description | Application error types and response mapping |
homepage | |
repository | https://github.com/RAprogramm/masterror |
max_upload_size | |
id | 1791306 |
size | 205,114 |
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
.
AppError
, AppErrorKind
, AppResult
, AppCode
, ErrorResponse
utoipa
)sqlx
, reqwest
, redis
, validator
, config
, tokio
[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.
AppErrorKind
categories mapping conservatively to HTTP.unsafe
, MSRV pinned.ErrorResponse { status, code, message, details?, retry?, www_authenticate? }
.tracing
.[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
— IntoResponseactix
— ResponseError/Responderopenapi
— utoipa schemaserde_json
— JSON detailssqlx
, redis
, reqwest
, validator
, config
, tokio
, multipart
std::io::Error
→ InternalString
→ BadRequestsqlx::Error
→ NotFound/Databaseredis::RedisError
→ Servicereqwest::Error
→ Timeout/Network/ExternalApivalidator::ValidationErrors
→ Validationconfig::ConfigError
→ Configtokio::time::error::Elapsed
→ TimeoutMinimal 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"
] }
ErrorResponse::new(status, AppCode::..., "msg")
instead of legacy.with_retry_after_secs
, .with_www_authenticate
ErrorResponse::new_legacy
is temporary shimSemantic versioning. Breaking API/wire contract → major bump.
MSRV = 1.89 (may raise in minor, never in patch).
anyhow
Apache-2.0 OR MIT, at your option.