55[ ![ Downloads] ( https://img.shields.io/crates/d/masterror )] ( https://crates.io/crates/masterror )
66![ MSRV] ( https://img.shields.io/badge/MSRV-1.89-blue )
77![ License] ( https://img.shields.io/badge/License-MIT%20or%20Apache--2.0-informational )
8+ [ ![ CI] ( https://github.com/RAprogramm/masterror/actions/workflows/ci.yml/badge.svg?branch=main )] ( https://github.com/RAprogramm/masterror/actions/workflows/ci.yml?query=branch%3Amain )
89
910Small, pragmatic error model for API-heavy Rust services.
1011Core is framework-agnostic; integrations are opt-in via feature flags.
@@ -17,60 +18,70 @@ Stable categories, conservative HTTP mapping, no `unsafe`.
1718
1819---
1920
20- ## Why this crate?
21+ ### TL;DR
2122
22- - ** 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.
23- - ** Framework-agnostic core.** No web framework assumptions. No ` unsafe ` . MSRV pinned. Works in libraries and binaries alike.
24- - ** Opt-in integrations.** Zero default features. You pull only what you need:
25- - ` axum ` (HTTP ` IntoResponse ` )
26- - ` actix ` (ready-to-use integration)
27- - ` serde_json ` (JSON details)
28- - ` openapi ` (schemas via ` utoipa ` )
29- - ` sqlx ` , ` reqwest ` , ` redis ` , ` validator ` , ` config ` , ` tokio ` , ` multipart ` (error conversions)
30- - ** Clean wire contract.** A small ` ErrorResponse { status, code, message, details?, retry?, www_authenticate? } ` payload for HTTP, with optional OpenAPI schema. No leaking of internal sources.
31- - ** One log at the boundary.** Use ` tracing ` once when converting to HTTP, avoiding duplicate logs and keeping fields stable (` kind ` , ` status ` , ` message ` ).
32- - ** Less boilerplate.** Built-in ` From<...> ` conversions for common libs and a compact prelude for handler signatures.
33- - ** Consistent across a workspace.** Share the same error surface between services and crates, making clients and tests simpler.
34-
35- > * Since v0.3.0: stable AppCode enum and extended ErrorResponse with retry/authentication metadata*
23+ ~~~ toml
24+ [dependencies ]
25+ masterror = { version = " 0.3" , default-features = false }
26+ # or with features:
27+ # masterror = { version = "0.3", features = [
28+ # "axum", "actix", "serde_json", "openapi",
29+ # "sqlx", "reqwest", "redis", "validator", "config", "tokio"
30+ # ] }
31+ ~~~
3632
33+ * Since v0.3.0: stable ` AppCode ` enum and extended ` ErrorResponse ` with retry/authentication metadata.*
3734
3835---
3936
40- ## Installation
37+ <details >
38+ <summary ><b >Why this crate?</b ></summary >
4139
42- ``` toml
40+ - ** Stable taxonomy.** Small set of ` AppErrorKind ` categories mapping conservatively to HTTP.
41+ - ** Framework-agnostic.** No assumptions, no ` unsafe ` , MSRV pinned.
42+ - ** Opt-in integrations.** Zero default features; you enable what you need.
43+ - ** Clean wire contract.** ` ErrorResponse { status, code, message, details?, retry?, www_authenticate? } ` .
44+ - ** One log at boundary.** Log once with ` tracing ` .
45+ - ** Less boilerplate.** Built-in conversions, compact prelude.
46+ - ** Consistent workspace.** Same error surface across crates.
47+
48+ </details >
49+
50+ <details >
51+ <summary ><b >Installation</b ></summary >
52+
53+ ~~~ toml
4354[dependencies ]
44- # lean core, no extra deps
55+ # lean core
4556masterror = { version = " 0.3" , default-features = false }
4657
47- # Or with features:
48- # JSON + Axum/Actix + common integrations
58+ # with Axum/Actix + JSON + integrations
4959# masterror = { version = "0.3", features = [
5060# "axum", "actix", "serde_json", "openapi",
5161# "sqlx", "reqwest", "redis", "validator", "config", "tokio"
5262# ] }
53- ```
63+ ~~~
5464
5565** MSRV:** 1.89
56- ** No unsafe:** this crate forbids ` unsafe ` .
66+ ** No unsafe:** forbidden by crate .
5767
58- ---
68+ </ details >
5969
60- ## Quick start
70+ <details >
71+ <summary ><b >Quick start</b ></summary >
6172
62- Create an error with a semantic kind and an optional public message :
73+ Create an error:
6374
64- ``` rust
75+ ~~~ rust
6576use masterror :: {AppError , AppErrorKind };
6677
6778let err = AppError :: new (AppErrorKind :: BadRequest , " Flag must be set" );
6879assert! (matches! (err . kind, AppErrorKind :: BadRequest ));
69- ```
80+ ~~~
7081
71- Use the prelude to keep signatures tidy :
82+ With prelude:
7283
73- ``` rust
84+ ~~~ rust
7485use masterror :: prelude :: * ;
7586
7687fn do_work (flag : bool ) -> AppResult <()> {
@@ -79,14 +90,14 @@ fn do_work(flag: bool) -> AppResult<()> {
7990 }
8091 Ok (())
8192}
82- ```
93+ ~~~
8394
84- ### Error response payload
95+ </ details >
8596
86- ` ErrorResponse ` is a wire-level payload for HTTP APIs. You can build it directly or convert from ` AppError ` :
97+ <details >
98+ <summary ><b >Error response payload</b ></summary >
8799
88-
89- ``` rust
100+ ~~~ rust
90101use masterror :: {AppError , AppErrorKind , AppCode , ErrorResponse };
91102
92103let app_err = AppError :: new (AppErrorKind :: Unauthorized , " Token expired" );
@@ -95,19 +106,18 @@ let resp: ErrorResponse = (&app_err).into()
95106 . with_www_authenticate (r # " Bearer realm="api", error="invalid_token"" # );
96107
97108assert_eq! (resp . status, 401 );
98- ```
99-
100-
101- ---
109+ ~~~
102110
103- ## Web framework integrations
111+ </ details >
104112
105- ### Axum
113+ <details >
114+ <summary ><b >Web framework integrations</b ></summary >
106115
107- Enable ` axum ` (and usually ` serde_json ` ) to return errors directly from handlers:
116+ <details >
117+ <summary >Axum</summary >
108118
109- ``` rust
110- // requires: features = ["axum", "serde_json"]
119+ ~~~ rust
120+ // features = ["axum", "serde_json"]
111121use masterror :: {AppError , AppResult };
112122use axum :: {routing :: get, Router };
113123
@@ -116,14 +126,15 @@ async fn handler() -> AppResult<&'static str> {
116126}
117127
118128let app = Router :: new (). route (" /demo" , get (handler ));
119- ```
129+ ~~~
120130
121- ### Actix
131+ </ details >
122132
123- Enable ` actix ` (and usually ` serde_json ` ) to return errors directly from handlers:
133+ <details >
134+ <summary >Actix</summary >
124135
125- ``` rust
126- // requires: features = ["actix", "serde_json"]
136+ ~~~ rust
137+ // features = ["actix", "serde_json"]
127138use actix_web :: {get, App , HttpServer , Responder };
128139use masterror :: prelude :: * ;
129140
@@ -132,117 +143,109 @@ async fn err() -> AppResult<&'static str> {
132143 Err (AppError :: forbidden (" No access" ))
133144}
134145
135-
136146#[get(" /payload" )]
137147async fn payload () -> impl Responder {
138148 ErrorResponse :: new (422 , AppCode :: Validation , " Validation failed" )
139149}
150+ ~~~
140151
141- ```
142-
143- ---
152+ </details >
144153
145- ## OpenAPI
154+ </ details >
146155
147- Enable ` openapi ` to derive an OpenAPI schema for ` ErrorResponse ` (via ` utoipa ` ).
156+ <details >
157+ <summary ><b >OpenAPI</b ></summary >
148158
149- ``` toml
159+ ~~~ toml
150160[dependencies ]
151161masterror = { version = " 0.3" , features = [" openapi" , " serde_json" ] }
152162utoipa = " 5"
153- ```
163+ ~~~
154164
155- ---
156-
157- ## Feature flags
165+ </details >
158166
159- - ` axum ` — ` IntoResponse ` for ` AppError ` and JSON responses
160- - ` actix ` — ` ResponseError ` /` Responder ` integration
161- - ` openapi ` — schema for ` ErrorResponse ` via ` utoipa `
162- - ` serde_json ` — JSON details support
163- - ` sqlx ` — ` From<sqlx::Error> `
164- - ` redis ` — ` From<redis::RedisError> `
165- - ` validator ` — ` From<validator::ValidationErrors> `
166- - ` config ` — ` From<config::ConfigError> `
167- - ` tokio ` — ` From<tokio::time::error::Elapsed> `
168- - ` reqwest ` — ` From<reqwest::Error> `
169- - ` multipart ` — compatibility flag for projects using multipart in Axum
167+ <details >
168+ <summary ><b >Feature flags</b ></summary >
170169
171- ---
170+ - ` axum ` — IntoResponse
171+ - ` actix ` — ResponseError/Responder
172+ - ` openapi ` — utoipa schema
173+ - ` serde_json ` — JSON details
174+ - ` sqlx ` , ` redis ` , ` reqwest ` , ` validator ` , ` config ` , ` tokio ` , ` multipart `
172175
173- ## Conversions
176+ </ details >
174177
175- All mappings are conservative and avoid leaking internals:
178+ <details >
179+ <summary ><b >Conversions</b ></summary >
176180
177- - ` std::io::Error ` → ` Internal `
178- - ` String ` → ` BadRequest `
179- - ` sqlx::Error ` → ` NotFound ` (for ` RowNotFound ` ) or ` Database `
180- - ` redis::RedisError ` → ` Service `
181- - ` reqwest::Error ` → ` Timeout ` / ` Network ` / ` ExternalApi `
182- - ` validator::ValidationErrors ` → ` Validation `
183- - ` config::ConfigError ` → ` Config `
184- - ` tokio::time::error::Elapsed ` → ` Timeout `
181+ - ` std::io::Error ` → Internal
182+ - ` String ` → BadRequest
183+ - ` sqlx::Error ` → NotFound/ Database
184+ - ` redis::RedisError ` → Service
185+ - ` reqwest::Error ` → Timeout/ Network/ ExternalApi
186+ - ` validator::ValidationErrors ` → Validation
187+ - ` config::ConfigError ` → Config
188+ - ` tokio::time::error::Elapsed ` → Timeout
185189
186- ---
190+ </ details >
187191
188- ## Typical setups
192+ <details >
193+ <summary ><b >Typical setups</b ></summary >
189194
190195Minimal core:
191196
192- ``` toml
197+ ~~~ toml
193198masterror = { version = " 0.3" , default-features = false }
194- ```
199+ ~~~
195200
196- API service (Axum + JSON + common deps):
201+ API (Axum + JSON + deps):
197202
198- ``` toml
203+ ~~~ toml
199204masterror = { version = " 0.3" , features = [
200205 " axum" , " serde_json" , " openapi" ,
201206 " sqlx" , " reqwest" , " redis" , " validator" , " config" , " tokio"
202207] }
203- ```
208+ ~~~
204209
205- API service (Actix + JSON + common deps):
210+ API (Actix + JSON + deps):
206211
207- ``` toml
212+ ~~~ toml
208213masterror = { version = " 0.3" , features = [
209214 " actix" , " serde_json" , " openapi" ,
210215 " sqlx" , " reqwest" , " redis" , " validator" , " config" , " tokio"
211216] }
212- ```
217+ ~~~
213218
214- ---
219+ </ details >
215220
216- ## Migration from 0.2.x to 0.3.0
221+ <details >
222+ <summary ><b >Migration 0.2 → 0.3</b ></summary >
217223
218- - Replace ` ErrorResponse::new(status, "msg") ` with
219- ` ErrorResponse::new(status, AppCode::<Variant>, "msg") `
220- - Use ` .with_retry_after_secs(...) ` and ` .with_www_authenticate(...) `
221- if you want to surface HTTP headers.
222- - ` ErrorResponse::new_legacy ` is provided temporarily as a deprecated shim.
224+ - Use ` ErrorResponse::new(status, AppCode::..., "msg") ` instead of legacy
225+ - New helpers: ` .with_retry_after_secs ` , ` .with_www_authenticate `
226+ - ` ErrorResponse::new_legacy ` is temporary shim
223227
228+ </details >
224229
225- ---
230+ <details >
231+ <summary ><b >Versioning & MSRV</b ></summary >
226232
227- ## Versioning and MSRV
233+ Semantic versioning. Breaking API/wire contract → major bump.
234+ MSRV = 1.89 (may raise in minor, never in patch).
228235
229- - Semantic versioning. Breaking API or wire-contract changes bump the major version.
230- - MSRV: 1.89 (may be raised in a ** minor** release with a changelog note, never in a patch).
236+ </details >
231237
232- ---
238+ <details >
239+ <summary ><b >Non-goals</b ></summary >
233240
234- ## Non-goals
235-
236- - Not a general-purpose error aggregator like ` anyhow ` for CLIs.
237- - Not a replacement for your domain errors. Use it as the public API surface and transport mapping.
238-
239- ---
241+ - Not a general-purpose error aggregator like ` anyhow `
242+ - Not a replacement for your domain errors
240243
241- ## License
244+ </ details >
242245
243- Licensed under either of
246+ <details >
247+ <summary ><b >License</b ></summary >
244248
245- - Apache License, Version 2.0
246- - MIT license
249+ Apache-2.0 OR MIT, at your option.
247250
248- at your option.
251+ </ details >
0 commit comments