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
16 changes: 16 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file.

## [Unreleased]

## [0.12.0] - 2025-10-29

### Added
- Introduced typed `Metadata` storage with `Field`/`FieldValue` builders and helper functions in `field::*`.
- Captured error sources and backtraces inside the new `app_error::Error` container, exposing `MessageEditPolicy` to control redaction.

### Changed
- Replaced the legacy `AppError` struct with the richer `Error` model carrying `AppCode`, metadata, retry/auth hints and transport policy.
- Updated response mapping and constructors to preserve machine-readable codes without extra allocations.

### Documentation
- Refreshed crate docs, README (EN/RU) and examples to highlight metadata helpers and the new error contract.

### Tests
- Added regression coverage ensuring codes, metadata and sources survive conversions without unnecessary cloning.

## [0.11.2] - 2025-10-28

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

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

5 changes: 4 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "masterror"
version = "0.11.2"
version = "0.12.0"
rust-version = "1.90"
edition = "2024"
license = "MIT OR Apache-2.0"
Expand Down Expand Up @@ -110,6 +110,9 @@ telegram-webapp-sdk = { version = "0.2", optional = true }
wasm-bindgen = { version = "0.2", optional = true }
js-sys = { version = "0.3", optional = true }
serde-wasm-bindgen = { version = "0.6", optional = true }
uuid = { version = "1", default-features = false, features = [
"std"
] }

[dev-dependencies]
serde_json = "1"
Expand Down
23 changes: 14 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ 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`
- Core types: `AppError`, `AppErrorKind`, `AppResult`, `AppCode`, `ErrorResponse`, `Metadata`
- 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`)
- Structured metadata helpers via `field::*` builders
- Conversions from `sqlx`, `reqwest`, `redis`, `validator`, `config`, `tokio`
- Turnkey domain taxonomy and helpers (`turnkey` feature)

Expand All @@ -36,9 +37,9 @@ guides, comparisons with `thiserror`/`anyhow`, and troubleshooting recipes.

~~~toml
[dependencies]
masterror = { version = "0.11.2", default-features = false }
masterror = { version = "0.12.0", default-features = false }
# or with features:
# masterror = { version = "0.11.2", features = [
# masterror = { version = "0.12.0", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
Expand All @@ -59,6 +60,7 @@ masterror = { version = "0.11.2", default-features = false }
- **Framework-agnostic.** No assumptions, no `unsafe`, MSRV pinned.
- **Opt-in integrations.** Zero default features; you enable what you need.
- **Clean wire contract.** `ErrorResponse { status, code, message, details?, retry?, www_authenticate? }`.
- **Typed telemetry.** `Metadata` preserves structured key/value context without `String` maps.
- **One log at boundary.** Log once with `tracing`.
- **Less boilerplate.** Built-in conversions, compact prelude, and the
native `masterror::Error` derive with `#[from]` / `#[error(transparent)]`
Expand All @@ -73,10 +75,10 @@ masterror = { version = "0.11.2", default-features = false }
~~~toml
[dependencies]
# lean core
masterror = { version = "0.11.2", default-features = false }
masterror = { version = "0.12.0", default-features = false }

# with Axum/Actix + JSON + integrations
# masterror = { version = "0.11.2", features = [
# masterror = { version = "0.12.0", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
Expand All @@ -95,10 +97,13 @@ masterror = { version = "0.11.2", default-features = false }
Create an error:

~~~rust
use masterror::{AppError, AppErrorKind};
use masterror::{AppError, AppErrorKind, field};

let err = AppError::new(AppErrorKind::BadRequest, "Flag must be set");
assert!(matches!(err.kind, AppErrorKind::BadRequest));
let err_with_meta = AppError::service("downstream")
.with_field(field::str("request_id", "abc123"));
assert_eq!(err_with_meta.metadata().len(), 1);
~~~

With prelude:
Expand Down Expand Up @@ -632,13 +637,13 @@ assert_eq!(resp.status, 401);
Minimal core:

~~~toml
masterror = { version = "0.11.2", default-features = false }
masterror = { version = "0.12.0", default-features = false }
~~~

API (Axum + JSON + deps):

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

~~~toml
masterror = { version = "0.11.2", features = [
masterror = { version = "0.12.0", features = [
"actix", "serde_json", "openapi",
"sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
Expand Down
13 changes: 9 additions & 4 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,13 @@

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

- Базовые типы: `AppError`, `AppErrorKind`, `AppResult`, `AppCode`, `ErrorResponse`.
- Базовые типы: `AppError`, `AppErrorKind`, `AppResult`, `AppCode`, `ErrorResponse`, `Metadata`.
- Деривы `#[derive(Error)]`, `#[app_error]`, `#[provide]` для типизированного
телеметрического контекста и прямых конверсий доменных ошибок.
- Адаптеры для Axum и Actix плюс логирование в браузер/`JsValue` для WASM (по
фичам).
- Генерация схем OpenAPI через `utoipa`.
- Структурированные поля `Metadata` через билдеры `field::*`.
- Конверсии из распространённых библиотек (`sqlx`, `reqwest`, `redis`,
`validator`, `config`, `tokio` и др.).
- Готовый прелюдия-модуль и расширение `turnkey` с собственной таксономией
Expand All @@ -37,9 +38,9 @@
~~~toml
[dependencies]
# минимальное ядро
masterror = { version = "0.11.2", default-features = false }
masterror = { version = "0.12.0", default-features = false }
# или с нужными интеграциями
# masterror = { version = "0.11.2", features = [
# masterror = { version = "0.12.0", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
Expand All @@ -54,10 +55,13 @@ masterror = { version = "0.11.2", default-features = false }
Создание ошибки вручную:

~~~rust
use masterror::{AppError, AppErrorKind};
use masterror::{AppError, AppErrorKind, field};

let err = AppError::new(AppErrorKind::BadRequest, "Флаг должен быть установлен");
assert!(matches!(err.kind, AppErrorKind::BadRequest));
let err_with_meta = AppError::service("downstream")
.with_field(field::str("request_id", "abc123"));
assert_eq!(err_with_meta.metadata().len(), 1);
~~~

Использование прелюдии:
Expand All @@ -82,6 +86,7 @@ fn do_work(flag: bool) -> AppResult<()> {
- `validator` — преобразование `ValidationErrors` в валидационные ошибки API.
- `config` — типизированные ошибки конфигурации.
- `tokio` — маппинг таймаутов (`tokio::time::error::Elapsed`).
- `metadata` — типизированные поля `Metadata` без аллокаций строк.
- `multipart` — обработка ошибок извлечения multipart в Axum.
- `teloxide` — маппинг `teloxide_core::RequestError` в доменные категории.
- `telegram-webapp-sdk` — обработка ошибок валидации данных Telegram WebApp.
Expand Down
9 changes: 7 additions & 2 deletions README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ 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`
- Core types: `AppError`, `AppErrorKind`, `AppResult`, `AppCode`, `ErrorResponse`, `Metadata`
- 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`)
- Structured metadata helpers via `field::*` builders
- Conversions from `sqlx`, `reqwest`, `redis`, `validator`, `config`, `tokio`
- Turnkey domain taxonomy and helpers (`turnkey` feature)

Expand Down Expand Up @@ -56,6 +57,7 @@ masterror = { version = "{{CRATE_VERSION}}", default-features = false }
- **Framework-agnostic.** No assumptions, no `unsafe`, MSRV pinned.
- **Opt-in integrations.** Zero default features; you enable what you need.
- **Clean wire contract.** `ErrorResponse { status, code, message, details?, retry?, www_authenticate? }`.
- **Typed telemetry.** `Metadata` preserves structured key/value context without `String` maps.
- **One log at boundary.** Log once with `tracing`.
- **Less boilerplate.** Built-in conversions, compact prelude, and the
native `masterror::Error` derive with `#[from]` / `#[error(transparent)]`
Expand Down Expand Up @@ -89,10 +91,13 @@ masterror = { version = "{{CRATE_VERSION}}", default-features = false }
Create an error:

~~~rust
use masterror::{AppError, AppErrorKind};
use masterror::{AppError, AppErrorKind, field};

let err = AppError::new(AppErrorKind::BadRequest, "Flag must be set");
assert!(matches!(err.kind, AppErrorKind::BadRequest));
let err_with_meta = AppError::service("downstream")
.with_field(field::str("request_id", "abc123"));
assert_eq!(err_with_meta.metadata().len(), 1);
~~~

With prelude:
Expand Down
7 changes: 6 additions & 1 deletion src/app_error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
//! [`AppErrorKind`].
//! - **Optional message:** human-readable, safe-to-expose text. Do not put
//! secrets here.
//! - **Structured metadata:** attach typed key/value pairs for diagnostics via
//! [`Metadata`].
//! - **No panics:** all helpers avoid `unwrap/expect`.
//! - **Transport-agnostic:** mapping to HTTP lives in `kind.rs` and
//! `convert/*`.
Expand Down Expand Up @@ -59,8 +61,11 @@

mod constructors;
mod core;
mod metadata;

pub use core::{AppError, AppResult};
pub use core::{AppError, AppResult, Error, MessageEditPolicy};

pub use metadata::{Field, FieldValue, Metadata, field};

#[cfg(test)]
mod tests;
9 changes: 3 additions & 6 deletions src/app_error/constructors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,9 @@ impl AppError {
/// assert!(err.message.is_none());
/// ```
pub fn database(msg: Option<Cow<'static, str>>) -> Self {
Self {
kind: AppErrorKind::Database,
message: msg,
retry: None,
www_authenticate: None
}
let mut err = Self::bare(AppErrorKind::Database);
err.message = msg;
err
}

/// Build a `Database` error with a message.
Expand Down
Loading
Loading