Skip to content

Commit 4790f7a

Browse files
committed
Merge branch 'main' of github.com:RAprogramm/masterror
2 parents 282e716 + 7e9e349 commit 4790f7a

File tree

1 file changed

+117
-114
lines changed

1 file changed

+117
-114
lines changed

README.md

Lines changed: 117 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +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?query=branch%3Amain)
89

910
Small, pragmatic error model for API-heavy Rust services.
1011
Core 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
4556
masterror = { 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
6576
use masterror::{AppError, AppErrorKind};
6677

6778
let err = AppError::new(AppErrorKind::BadRequest, "Flag must be set");
6879
assert!(matches!(err.kind, AppErrorKind::BadRequest));
69-
```
80+
~~~
7081

71-
Use the prelude to keep signatures tidy:
82+
With prelude:
7283

73-
```rust
84+
~~~rust
7485
use masterror::prelude::*;
7586

7687
fn 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
90101
use masterror::{AppError, AppErrorKind, AppCode, ErrorResponse};
91102

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

97108
assert_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"]
111121
use masterror::{AppError, AppResult};
112122
use axum::{routing::get, Router};
113123

@@ -116,14 +126,15 @@ async fn handler() -> AppResult<&'static str> {
116126
}
117127

118128
let 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"]
127138
use actix_web::{get, App, HttpServer, Responder};
128139
use 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")]
137147
async 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]
151161
masterror = { version = "0.3", features = ["openapi", "serde_json"] }
152162
utoipa = "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

190195
Minimal core:
191196

192-
```toml
197+
~~~toml
193198
masterror = { 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
199204
masterror = { 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
208213
masterror = { 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

Comments
 (0)