Skip to content

Commit 7e9e349

Browse files
authored
Update README.md
1 parent ffb9cc7 commit 7e9e349

File tree

1 file changed

+117
-115
lines changed

1 file changed

+117
-115
lines changed

README.md

Lines changed: 117 additions & 115 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
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

1010
Small, pragmatic error model for API-heavy Rust services.
1111
Core 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
4656
masterror = { 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
6676
use masterror::{AppError, AppErrorKind};
6777

6878
let err = AppError::new(AppErrorKind::BadRequest, "Flag must be set");
6979
assert!(matches!(err.kind, AppErrorKind::BadRequest));
70-
```
80+
~~~
7181

72-
Use the prelude to keep signatures tidy:
82+
With prelude:
7383

74-
```rust
84+
~~~rust
7585
use masterror::prelude::*;
7686

7787
fn 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
91101
use masterror::{AppError, AppErrorKind, AppCode, ErrorResponse};
92102

93103
let 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

98108
assert_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"]
112121
use masterror::{AppError, AppResult};
113122
use axum::{routing::get, Router};
114123

@@ -117,14 +126,15 @@ async fn handler() -> AppResult<&'static str> {
117126
}
118127

119128
let 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"]
128138
use actix_web::{get, App, HttpServer, Responder};
129139
use 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")]
138147
async 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]
152161
masterror = { version = "0.3", features = ["openapi", "serde_json"] }
153162
utoipa = "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

191195
Minimal core:
192196

193-
```toml
197+
~~~toml
194198
masterror = { 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
200204
masterror = { 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
209213
masterror = { 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

Comments
 (0)