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 )
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 )
99
1010Small, pragmatic error model for API-heavy Rust services.
1111Core is framework-agnostic; integrations are opt-in via feature flags.
@@ -18,60 +18,70 @@ Stable categories, conservative HTTP mapping, no `unsafe`.
1818
1919---
2020
21- ## Why this crate?
21+ ### TL;DR
2222
23- - ** 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.
24- - ** Framework-agnostic core.** No web framework assumptions. No ` unsafe ` . MSRV pinned. Works in libraries and binaries alike.
25- - ** Opt-in integrations.** Zero default features. You pull only what you need:
26- - ` axum ` (HTTP ` IntoResponse ` )
27- - ` actix ` (ready-to-use integration)
28- - ` serde_json ` (JSON details)
29- - ` openapi ` (schemas via ` utoipa ` )
30- - ` sqlx ` , ` reqwest ` , ` redis ` , ` validator ` , ` config ` , ` tokio ` , ` multipart ` (error conversions)
31- - ** 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.
32- - ** One log at the boundary.** Use ` tracing ` once when converting to HTTP, avoiding duplicate logs and keeping fields stable (` kind ` , ` status ` , ` message ` ).
33- - ** Less boilerplate.** Built-in ` From<...> ` conversions for common libs and a compact prelude for handler signatures.
34- - ** Consistent across a workspace.** Share the same error surface between services and crates, making clients and tests simpler.
35-
36- > * 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+ ~~~
3732
33+ * Since v0.3.0: stable ` AppCode ` enum and extended ` ErrorResponse ` with retry/authentication metadata.*
3834
3935---
4036
41- ## Installation
37+ <details >
38+ <summary ><b >Why this crate?</b ></summary >
4239
43- ``` 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
4454[dependencies ]
45- # lean core, no extra deps
55+ # lean core
4656masterror = { version = " 0.3" , default-features = false }
4757
48- # Or with features:
49- # JSON + Axum/Actix + common integrations
58+ # with Axum/Actix + JSON + integrations
5059# masterror = { version = "0.3", features = [
5160# "axum", "actix", "serde_json", "openapi",
5261# "sqlx", "reqwest", "redis", "validator", "config", "tokio"
5362# ] }
54- ```
63+ ~~~
5564
5665** MSRV:** 1.89
57- ** No unsafe:** this crate forbids ` unsafe ` .
66+ ** No unsafe:** forbidden by crate .
5867
59- ---
68+ </ details >
6069
61- ## Quick start
70+ <details >
71+ <summary ><b >Quick start</b ></summary >
6272
63- Create an error with a semantic kind and an optional public message :
73+ Create an error:
6474
65- ``` rust
75+ ~~~ rust
6676use masterror :: {AppError , AppErrorKind };
6777
6878let err = AppError :: new (AppErrorKind :: BadRequest , " Flag must be set" );
6979assert! (matches! (err . kind, AppErrorKind :: BadRequest ));
70- ```
80+ ~~~
7181
72- Use the prelude to keep signatures tidy :
82+ With prelude:
7383
74- ``` rust
84+ ~~~ rust
7585use masterror :: prelude :: * ;
7686
7787fn do_work (flag : bool ) -> AppResult <()> {
@@ -80,14 +90,14 @@ fn do_work(flag: bool) -> AppResult<()> {
8090 }
8191 Ok (())
8292}
83- ```
93+ ~~~
8494
85- ### Error response payload
95+ </ details >
8696
87- ` 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 >
8899
89-
90- ``` rust
100+ ~~~ rust
91101use masterror :: {AppError , AppErrorKind , AppCode , ErrorResponse };
92102
93103let app_err = AppError :: new (AppErrorKind :: Unauthorized , " Token expired" );
@@ -96,19 +106,18 @@ let resp: ErrorResponse = (&app_err).into()
96106 . with_www_authenticate (r # " Bearer realm="api", error="invalid_token"" # );
97107
98108assert_eq! (resp . status, 401 );
99- ```
100-
101-
102- ---
109+ ~~~
103110
104- ## Web framework integrations
111+ </ details >
105112
106- ### Axum
113+ <details >
114+ <summary ><b >Web framework integrations</b ></summary >
107115
108- Enable ` axum ` (and usually ` serde_json ` ) to return errors directly from handlers:
116+ <details >
117+ <summary >Axum</summary >
109118
110- ``` rust
111- // requires: features = ["axum", "serde_json"]
119+ ~~~ rust
120+ // features = ["axum", "serde_json"]
112121use masterror :: {AppError , AppResult };
113122use axum :: {routing :: get, Router };
114123
@@ -117,14 +126,15 @@ async fn handler() -> AppResult<&'static str> {
117126}
118127
119128let app = Router :: new (). route (" /demo" , get (handler ));
120- ```
129+ ~~~
121130
122- ### Actix
131+ </ details >
123132
124- Enable ` actix ` (and usually ` serde_json ` ) to return errors directly from handlers:
133+ <details >
134+ <summary >Actix</summary >
125135
126- ``` rust
127- // requires: features = ["actix", "serde_json"]
136+ ~~~ rust
137+ // features = ["actix", "serde_json"]
128138use actix_web :: {get, App , HttpServer , Responder };
129139use masterror :: prelude :: * ;
130140
@@ -133,117 +143,109 @@ async fn err() -> AppResult<&'static str> {
133143 Err (AppError :: forbidden (" No access" ))
134144}
135145
136-
137146#[get(" /payload" )]
138147async fn payload () -> impl Responder {
139148 ErrorResponse :: new (422 , AppCode :: Validation , " Validation failed" )
140149}
150+ ~~~
141151
142- ```
143-
144- ---
152+ </details >
145153
146- ## OpenAPI
154+ </ details >
147155
148- Enable ` openapi ` to derive an OpenAPI schema for ` ErrorResponse ` (via ` utoipa ` ).
156+ <details >
157+ <summary ><b >OpenAPI</b ></summary >
149158
150- ``` toml
159+ ~~~ toml
151160[dependencies ]
152161masterror = { version = " 0.3" , features = [" openapi" , " serde_json" ] }
153162utoipa = " 5"
154- ```
163+ ~~~
155164
156- ---
157-
158- ## Feature flags
165+ </details >
159166
160- - ` axum ` — ` IntoResponse ` for ` AppError ` and JSON responses
161- - ` actix ` — ` ResponseError ` /` Responder ` integration
162- - ` openapi ` — schema for ` ErrorResponse ` via ` utoipa `
163- - ` serde_json ` — JSON details support
164- - ` sqlx ` — ` From<sqlx::Error> `
165- - ` redis ` — ` From<redis::RedisError> `
166- - ` validator ` — ` From<validator::ValidationErrors> `
167- - ` config ` — ` From<config::ConfigError> `
168- - ` tokio ` — ` From<tokio::time::error::Elapsed> `
169- - ` reqwest ` — ` From<reqwest::Error> `
170- - ` multipart ` — compatibility flag for projects using multipart in Axum
167+ <details >
168+ <summary ><b >Feature flags</b ></summary >
171169
172- ---
170+ - ` axum ` — IntoResponse
171+ - ` actix ` — ResponseError/Responder
172+ - ` openapi ` — utoipa schema
173+ - ` serde_json ` — JSON details
174+ - ` sqlx ` , ` redis ` , ` reqwest ` , ` validator ` , ` config ` , ` tokio ` , ` multipart `
173175
174- ## Conversions
176+ </ details >
175177
176- All mappings are conservative and avoid leaking internals:
178+ <details >
179+ <summary ><b >Conversions</b ></summary >
177180
178- - ` std::io::Error ` → ` Internal `
179- - ` String ` → ` BadRequest `
180- - ` sqlx::Error ` → ` NotFound ` (for ` RowNotFound ` ) or ` Database `
181- - ` redis::RedisError ` → ` Service `
182- - ` reqwest::Error ` → ` Timeout ` / ` Network ` / ` ExternalApi `
183- - ` validator::ValidationErrors ` → ` Validation `
184- - ` config::ConfigError ` → ` Config `
185- - ` 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
186189
187- ---
190+ </ details >
188191
189- ## Typical setups
192+ <details >
193+ <summary ><b >Typical setups</b ></summary >
190194
191195Minimal core:
192196
193- ``` toml
197+ ~~~ toml
194198masterror = { version = " 0.3" , default-features = false }
195- ```
199+ ~~~
196200
197- API service (Axum + JSON + common deps):
201+ API (Axum + JSON + deps):
198202
199- ``` toml
203+ ~~~ toml
200204masterror = { version = " 0.3" , features = [
201205 " axum" , " serde_json" , " openapi" ,
202206 " sqlx" , " reqwest" , " redis" , " validator" , " config" , " tokio"
203207] }
204- ```
208+ ~~~
205209
206- API service (Actix + JSON + common deps):
210+ API (Actix + JSON + deps):
207211
208- ``` toml
212+ ~~~ toml
209213masterror = { version = " 0.3" , features = [
210214 " actix" , " serde_json" , " openapi" ,
211215 " sqlx" , " reqwest" , " redis" , " validator" , " config" , " tokio"
212216] }
213- ```
217+ ~~~
214218
215- ---
219+ </ details >
216220
217- ## Migration from 0.2.x to 0.3.0
221+ <details >
222+ <summary ><b >Migration 0.2 → 0.3</b ></summary >
218223
219- - Replace ` ErrorResponse::new(status, "msg") ` with
220- ` ErrorResponse::new(status, AppCode::<Variant>, "msg") `
221- - Use ` .with_retry_after_secs(...) ` and ` .with_www_authenticate(...) `
222- if you want to surface HTTP headers.
223- - ` 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
224227
228+ </details >
225229
226- ---
230+ <details >
231+ <summary ><b >Versioning & MSRV</b ></summary >
227232
228- ## Versioning and MSRV
233+ Semantic versioning. Breaking API/wire contract → major bump.
234+ MSRV = 1.89 (may raise in minor, never in patch).
229235
230- - Semantic versioning. Breaking API or wire-contract changes bump the major version.
231- - MSRV: 1.89 (may be raised in a ** minor** release with a changelog note, never in a patch).
236+ </details >
232237
233- ---
238+ <details >
239+ <summary ><b >Non-goals</b ></summary >
234240
235- ## Non-goals
236-
237- - Not a general-purpose error aggregator like ` anyhow ` for CLIs.
238- - Not a replacement for your domain errors. Use it as the public API surface and transport mapping.
239-
240- ---
241+ - Not a general-purpose error aggregator like ` anyhow `
242+ - Not a replacement for your domain errors
241243
242- ## License
244+ </ details >
243245
244- Licensed under either of
246+ <details >
247+ <summary ><b >License</b ></summary >
245248
246- - Apache License, Version 2.0
247- - MIT license
249+ Apache-2.0 OR MIT, at your option.
248250
249- at your option.
251+ </ details >
0 commit comments