Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.11.1] - 2025-10-27

### Documentation
- Added a multi-page error-handling wiki (`docs/wiki`) with beginner-friendly
walkthroughs, framework patterns, and comparisons against `thiserror` and
`anyhow`.
- Linked the wiki from the README template so crate consumers can discover it
directly on crates.io and docs.rs.
- Highlighted the native derive macros, typed telemetry, browser logging, and
Turnkey taxonomy across the README template and regenerated README.
- Refreshed the Russian README with the same capability summary and updated the
installation snippets to `0.11.1`.
- Expanded the crate-level documentation to cover `#[app_error]`/`#[provide]`
usage and link to `std::error::Request` telemetry extraction.

## [0.11.0] - 2025-10-26

### Changed
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "masterror"
version = "0.11.0"
version = "0.11.1"
rust-version = "1.90"
edition = "2024"
license = "MIT OR Apache-2.0"
Expand Down
26 changes: 16 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,24 +14,31 @@

> 🇷🇺 Читайте README на [русском языке](README.ru.md).

Small, pragmatic error model for API-heavy Rust services.
Small, pragmatic error model for API-heavy Rust services with native derives
and typed telemetry.
Core is framework-agnostic; integrations are opt-in via feature flags.
Stable categories, conservative HTTP mapping, no `unsafe`.

- Core types: `AppError`, `AppErrorKind`, `AppResult`, `AppCode`, `ErrorResponse`
- Optional Axum/Actix integration
- Derive macros: `#[derive(Error)]`, `#[app_error]`, `#[provide]` for domain
mappings and structured telemetry
- Optional Axum/Actix integration and browser/WASM console logging
- Optional OpenAPI schema (via `utoipa`)
- Conversions from `sqlx`, `reqwest`, `redis`, `validator`, `config`, `tokio`
- Turnkey domain taxonomy and helpers (`turnkey` feature)

👉 Explore the new [error-handling wiki](docs/wiki/index.md) for step-by-step
guides, comparisons with `thiserror`/`anyhow`, and troubleshooting recipes.

---

### TL;DR

~~~toml
[dependencies]
masterror = { version = "0.11.0", default-features = false }
masterror = { version = "0.11.1", default-features = false }
# or with features:
# masterror = { version = "0.11.0", features = [
# masterror = { version = "0.11.1", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
Expand Down Expand Up @@ -66,10 +73,10 @@ masterror = { version = "0.11.0", default-features = false }
~~~toml
[dependencies]
# lean core
masterror = { version = "0.11.0", default-features = false }
masterror = { version = "0.11.1", default-features = false }

# with Axum/Actix + JSON + integrations
# masterror = { version = "0.11.0", features = [
# masterror = { version = "0.11.1", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
Expand Down Expand Up @@ -625,13 +632,13 @@ assert_eq!(resp.status, 401);
Minimal core:

~~~toml
masterror = { version = "0.11.0", default-features = false }
masterror = { version = "0.11.1", default-features = false }
~~~

API (Axum + JSON + deps):

~~~toml
masterror = { version = "0.11.0", features = [
masterror = { version = "0.11.1", features = [
"axum", "serde_json", "openapi",
"sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
Expand All @@ -640,7 +647,7 @@ masterror = { version = "0.11.0", features = [
API (Actix + JSON + deps):

~~~toml
masterror = { version = "0.11.0", features = [
masterror = { version = "0.11.1", features = [
"actix", "serde_json", "openapi",
"sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
Expand Down Expand Up @@ -711,4 +718,3 @@ MSRV = 1.90 (may raise in minor, never in patch).
Apache-2.0 OR MIT, at your option.

</details>

21 changes: 15 additions & 6 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,24 @@
[![Security audit](https://github.com/RAprogramm/masterror/actions/workflows/ci.yml/badge.svg?branch=main&label=Security%20audit)](https://github.com/RAprogramm/masterror/actions/workflows/ci.yml?query=branch%3Amain)
[![Cargo Deny](https://img.shields.io/github/actions/workflow/status/RAprogramm/masterror/ci.yml?branch=main&label=Cargo%20Deny)](https://github.com/RAprogramm/masterror/actions/workflows/ci.yml?query=branch%3Amain)

Небольшая прагматичная модель ошибок для Rust-сервисов с выраженным API. Основной крейт не зависит от веб-фреймворков, а расширения включаются через фичи. Таксономия ошибок стабильна, соответствие HTTP-кодам консервативно, `unsafe` запрещён.
Небольшая прагматичная модель ошибок для Rust-сервисов с выраженным API и
встроенными деривами.
Основной крейт не зависит от веб-фреймворков, а расширения включаются через
фичи. Таксономия ошибок стабильна, соответствие HTTP-кодам консервативно,
`unsafe` запрещён.

## Основные возможности

- Базовые типы: `AppError`, `AppErrorKind`, `AppResult`, `AppCode`, `ErrorResponse`.
- Адаптеры для Axum и Actix (опционально).
- Деривы `#[derive(Error)]`, `#[app_error]`, `#[provide]` для типизированного
телеметрического контекста и прямых конверсий доменных ошибок.
- Адаптеры для Axum и Actix плюс логирование в браузер/`JsValue` для WASM (по
фичам).
- Генерация схем OpenAPI через `utoipa`.
- Конверсии из распространённых библиотек (`sqlx`, `reqwest`, `redis`, `validator`, `config`, `tokio` и др.).
- Готовый прелюдия-модуль, реэкспортирующий наиболее востребованные типы и трейты.
- Конверсии из распространённых библиотек (`sqlx`, `reqwest`, `redis`,
`validator`, `config`, `tokio` и др.).
- Готовый прелюдия-модуль и расширение `turnkey` с собственной таксономией
ошибок.

## Установка

Expand All @@ -28,9 +37,9 @@
~~~toml
[dependencies]
# минимальное ядро
masterror = { version = "0.10.4", default-features = false }
masterror = { version = "0.11.1", default-features = false }
# или с нужными интеграциями
# masterror = { version = "0.10.4", features = [
# masterror = { version = "0.11.1", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
Expand Down
11 changes: 9 additions & 2 deletions README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,21 @@

> 🇷🇺 Читайте README на [русском языке](README.ru.md).

Small, pragmatic error model for API-heavy Rust services.
Small, pragmatic error model for API-heavy Rust services with native derives
and typed telemetry.
Core is framework-agnostic; integrations are opt-in via feature flags.
Stable categories, conservative HTTP mapping, no `unsafe`.

- Core types: `AppError`, `AppErrorKind`, `AppResult`, `AppCode`, `ErrorResponse`
- Optional Axum/Actix integration
- Derive macros: `#[derive(Error)]`, `#[app_error]`, `#[provide]` for domain
mappings and structured telemetry
- Optional Axum/Actix integration and browser/WASM console logging
- Optional OpenAPI schema (via `utoipa`)
- Conversions from `sqlx`, `reqwest`, `redis`, `validator`, `config`, `tokio`
- Turnkey domain taxonomy and helpers (`turnkey` feature)

👉 Explore the new [error-handling wiki](docs/wiki/index.md) for step-by-step
guides, comparisons with `thiserror`/`anyhow`, and troubleshooting recipes.

---

Expand Down
144 changes: 144 additions & 0 deletions docs/wiki/error-crate-comparison.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# When to use `thiserror`, `anyhow`, or `masterror`

Rust gives you multiple complementary error crates. This page compares how they
behave and shows concrete examples.

## Quick summary

| Crate | Primary goal | Typical usage stage |
|--------------|--------------------------------------------------|---------------------------------|
| `thiserror` | Define strongly typed domain errors with `derive`| Library and boundary layers |
| `anyhow` | Prototype quickly with dynamic context | CLI tools, experiments, glue |
| `masterror` | Ship stable API responses with rich metadata | Web backends, public interfaces |

You can mix them: `masterror` re-exports the derive macro so you keep using
`#[derive(Error)]`, and you can attach `anyhow::Error` as context on
`AppError`.

## Example: modelling a domain error

The following snippet derives a typed error using each crate.

```rust
use masterror::{AppCode, AppError, AppErrorKind, Error};
use serde::Deserialize;
use thiserror::Error as ThisError;

#[derive(Debug, Deserialize)]
struct Payload {
flag: bool,
}

#[derive(Debug, Error)]
#[error("invalid payload: {source}")]
#[app_error(kind = AppErrorKind::BadRequest, code = AppCode::new("INVALID_PAYLOAD"))]
struct PayloadError {
#[from]
#[source]
source: serde_json::Error,
}

fn parse_with_masterror(input: &str) -> masterror::AppResult<Payload> {
let payload: Payload = serde_json::from_str(input).map_err(PayloadError::from)?;
Ok(payload)
}

#[derive(Debug, ThisError)]
#[error("invalid payload: {source}")]
struct PlainPayloadError {
#[from]
source: serde_json::Error,
}

fn parse_with_thiserror(input: &str) -> Result<Payload, PlainPayloadError> {
let payload = serde_json::from_str(input)?;
Ok(payload)
}

fn parse_with_anyhow(input: &str) -> Result<Payload, anyhow::Error> {
let payload = serde_json::from_str::<Payload>(input)
.map_err(|err| anyhow::anyhow!("invalid payload: {err}"))?;
Ok(payload)
}

fn convert_anyhow_into_masterror(err: anyhow::Error) -> AppError {
AppError::internal("unexpected parser failure").with_context(err)
}
```

Observations:

- `thiserror` focuses on ergonomic derives and string formatting, but it does
not impose how callers expose the error to clients.
- `anyhow` stores a dynamic error with a backtrace. It is ideal for prototyping
and small CLIs, but it does not convey HTTP status codes or machine-readable
metadata.
- `masterror` is opinionated about API boundaries. By using `#[app_error]`, the
domain error maps to a stable `AppErrorKind` and `AppCode` automatically.

## Mapping errors at service boundaries

Imagine an Axum handler that validates JSON, queries a database, and reaches an
external API. Each crate offers different trade-offs.

```rust
async fn handler_with_anyhow() -> Result<String, anyhow::Error> {
let payload = parse_with_anyhow("{ }")?;
Ok(format!("flag: {}", payload.flag))
}

async fn handler_with_thiserror() -> Result<String, PlainPayloadError> {
let payload = parse_with_thiserror("{ }")?;
Ok(format!("flag: {}", payload.flag))
}

async fn handler_with_masterror() -> masterror::AppResult<String> {
let payload = parse_with_masterror("{ }")?;
Ok(format!("flag: {}", payload.flag))
}
```

- The `anyhow` version surfaces a stringified error and a backtrace. Clients
receive HTTP 500 unless you write custom mapping logic.
- The `thiserror` version returns a typed error, but you still have to convert it
into an HTTP response yourself.
- The `masterror` version already contains the HTTP 400 classification, a stable
`AppCode`, and optional JSON details.

## Attaching context across crates

You can combine the strengths of each crate. Keep `thiserror` derives for rich
messages, wrap the result in `AppError`, and use `anyhow` for debug traces when
needed.

```rust
fn load_configuration(path: &std::path::Path) -> masterror::AppResult<String> {
let contents = std::fs::read_to_string(path).map_err(|err| {
AppError::internal("failed to read configuration")
.with_code(AppCode::new("CONFIG_IO"))
.with_context(anyhow::Error::from(err))
})?;
Ok(contents)
}
```

`AppError` stores the `anyhow::Error` internally without exposing it to clients.
You still emit clean JSON responses, while logs retain the full diagnostic
payload.

## Why choose `masterror`

1. **Stable contract.** `AppErrorKind` and `ErrorResponse` stay consistent across
services, making cross-team collaboration easier.
2. **Framework adapters.** Ready-to-use integrations with Axum, Actix Web,
`utoipa`, `serde_json`, and others remove boilerplate.
3. **Structured metadata.** Attach retry hints, authentication challenges, and
JSON details without building ad-hoc enums.
4. **Derive support.** Reuse the familiar `thiserror` syntax via
`masterror::Error` and augment it with `#[app_error]` rules.
5. **Context preservation.** Store source errors (including `anyhow::Error`) for
logging, while presenting sanitized messages externally.

Use `anyhow` when speed matters more than structure, `thiserror` when crafting
libraries, and `masterror` when you need predictable, well-documented API
responses.
35 changes: 35 additions & 0 deletions docs/wiki/index.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Masterror error-handling wiki

This wiki collects step-by-step guides for building reliable error handling in Rust services.
Each page is intentionally short and focused so you can jump straight to the
section that matches your experience level.

- [Rust error handling basics](rust-error-handling-basics.md)
- [Building applications with `masterror`](masterror-application-guide.md)
- [When to reach for `thiserror`, `anyhow`, or `masterror`](error-crate-comparison.md)
- [Patterns and troubleshooting](patterns-and-troubleshooting.md)

## How the wiki is organised

1. **Start with the basics** if you are new to `Result<T, E>` and the `?` operator.
2. **Follow the application guide** to design domain-specific error types with
consistent wire responses.
3. **Read the comparison** to understand how `masterror` complements `thiserror`
and `anyhow` instead of replacing them outright.
4. **Review patterns and troubleshooting** when you need concrete recipes for
mapping third-party errors, logging, and testing.

Each page contains runnable examples. Copy them into a new binary crate or an
`examples/` folder, run `cargo run`, and experiment.

## Related documentation

- [`README.md`](../README.md) and [`docs.rs/masterror`](https://docs.rs/masterror)
for API reference and feature lists.
- [`masterror-derive`](../../masterror-derive/README.md) to explore the derive
macro internals and advanced formatting capabilities.
- [`masterror-template`](../../masterror-template/README.md) for the shared
template parser used by the derive macros.

Feedback and suggestions are welcome — open an issue or discussion on
[GitHub](https://github.com/RAprogramm/masterror).
Loading
Loading