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

## [Unreleased]

### Added
- _Nothing yet._

## [0.5.7] - 2025-09-29

### Added
- `masterror::error::template` module providing a parsed representation of
`#[error("...")]` strings and a formatter hook for future custom derives.
Expand All @@ -17,6 +22,11 @@ All notable changes to this project will be documented in this file.
- `masterror::Error` now uses the in-tree derive, removing the dependency on
`thiserror` while keeping the same runtime behaviour and diagnostics.

### Documentation
- Documented formatter trait usage across README.md, README.ru.md and the
`masterror::error` module, noting compatibility with `thiserror` v2 and
demonstrating programmatic `TemplateFormatter` inspection.

## [0.5.6] - 2025-09-28

### Tests
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.5.6"
version = "0.5.7"
rust-version = "1.90"
edition = "2024"
license = "MIT OR Apache-2.0"
Expand Down
94 changes: 87 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ Stable categories, conservative HTTP mapping, no `unsafe`.

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

# with Axum/Actix + JSON + integrations
# masterror = { version = "0.5.6", features = [
# masterror = { version = "0.5.7", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
Expand Down Expand Up @@ -152,6 +152,86 @@ assert_eq!(wrapped.to_string(), "I/O failed: disk offline");
- `masterror::error::template::ErrorTemplate` parses `#[error("...")]`
strings, exposing literal and placeholder segments so custom derives can be
implemented without relying on `thiserror`.
- `TemplateFormatter` mirrors `thiserror`'s formatter detection so existing
derives that relied on hexadecimal, pointer or exponential renderers keep
compiling.

#### Formatter traits

Placeholders default to `Display` (`{value}`) but can opt into richer
formatters via the same specifiers supported by `thiserror` v2. Unsupported
formatters surface a compile error that mirrors `thiserror`'s diagnostics.

| Specifier | `core::fmt` trait | Example output |
|------------------|----------------------------|------------------------|
| _default_ | `core::fmt::Display` | `value` |
| `:?` / `:#?` | `core::fmt::Debug` | `Struct { .. }` / multi-line |
| `:x` / `:#x` | `core::fmt::LowerHex` | `0x2a` |
| `:X` / `:#X` | `core::fmt::UpperHex` | `0x2A` |
| `:p` / `:#p` | `core::fmt::Pointer` | `0x1f00` / `0x1f00` |
| `:b` / `:#b` | `core::fmt::Binary` | `101010` / `0b101010` |
| `:o` / `:#o` | `core::fmt::Octal` | `52` / `0o52` |
| `:e` / `:#e` | `core::fmt::LowerExp` | `1.5e-2` |
| `:E` / `:#E` | `core::fmt::UpperExp` | `1.5E-2` |

~~~rust
use core::ptr;

use masterror::Error;

#[derive(Debug, Error)]
#[error(
"debug={payload:?}, hex={id:#x}, ptr={ptr:p}, bin={mask:#b}, \
oct={mask:o}, lower={ratio:e}, upper={ratio:E}"
)]
struct FormattedError {
id: u32,
payload: String,
ptr: *const u8,
mask: u8,
ratio: f32,
}

let err = FormattedError {
id: 0x2a,
payload: "hello".into(),
ptr: ptr::null(),
mask: 0b1010_0001,
ratio: 0.15625,
};

let rendered = err.to_string();
assert!(rendered.contains("debug=\"hello\""));
assert!(rendered.contains("hex=0x2a"));
assert!(rendered.contains("ptr=0x0"));
assert!(rendered.contains("bin=0b10100001"));
assert!(rendered.contains("oct=241"));
assert!(rendered.contains("lower=1.5625e-1"));
assert!(rendered.contains("upper=1.5625E-1"));
~~~

~~~rust
use masterror::error::template::{ErrorTemplate, TemplateFormatter};

let template = ErrorTemplate::parse("{code:#x} → {payload:?}").expect("parse");
let mut placeholders = template.placeholders();

let code = placeholders.next().expect("code placeholder");
assert!(matches!(
code.formatter(),
TemplateFormatter::LowerHex { alternate: true }
));

let payload = placeholders.next().expect("payload placeholder");
assert_eq!(
payload.formatter(),
TemplateFormatter::Debug { alternate: false }
);
~~~

> **Compatibility with `thiserror` v2:** the derive understands the extended
> formatter set introduced in `thiserror` 2.x and reports identical diagnostics
> for unsupported specifiers, so migrating existing derives is drop-in.

```rust
use masterror::error::template::{ErrorTemplate, TemplateIdentifier};
Expand Down Expand Up @@ -262,13 +342,13 @@ assert_eq!(resp.status, 401);
Minimal core:

~~~toml
masterror = { version = "0.5.6", default-features = false }
masterror = { version = "0.5.7", default-features = false }
~~~

API (Axum + JSON + deps):

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

~~~toml
masterror = { version = "0.5.6", features = [
masterror = { version = "0.5.7", features = [
"actix", "serde_json", "openapi",
"sqlx", "reqwest", "redis", "validator", "config", "tokio"
] }
Expand Down
96 changes: 91 additions & 5 deletions README.ru.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,13 @@

~~~toml
[dependencies]
masterror = { version = "0.5.2", default-features = false }
masterror = { version = "0.5.7", default-features = false }
# или с нужными интеграциями
# masterror = { version = "0.5.2", features = [
# masterror = { version = "0.5.7", features = [
# "axum", "actix", "openapi", "serde_json",
# "sqlx", "reqwest", "redis", "validator",
# "config", "tokio", "multipart", "teloxide",
# "telegram-webapp-sdk", "frontend", "turnkey"
# "sqlx", "sqlx-migrate", "reqwest", "redis",
# "validator", "config", "tokio", "multipart",
# "teloxide", "telegram-webapp-sdk", "frontend", "turnkey"
# ] }
~~~

Expand Down Expand Up @@ -66,15 +66,101 @@ fn do_work(flag: bool) -> AppResult<()> {
## Дополнительные интеграции

- `sqlx` — классификация `sqlx::Error` по видам ошибок.
- `sqlx-migrate` — обработка `sqlx::migrate::MigrateError` как базы данных.
- `reqwest` — перевод сетевых/HTTP-сбоев в доменные категории.
- `redis` — корректная обработка ошибок кеша.
- `validator` — преобразование `ValidationErrors` в валидационные ошибки API.
- `config` — типизированные ошибки конфигурации.
- `tokio` — маппинг таймаутов (`tokio::time::error::Elapsed`).
- `multipart` — обработка ошибок извлечения multipart в Axum.
- `teloxide` — маппинг `teloxide_core::RequestError` в доменные категории.
- `telegram-webapp-sdk` — обработка ошибок валидации данных Telegram WebApp.
- `frontend` — логирование в браузере и преобразование в `JsValue` для WASM.
- `turnkey` — расширение таксономии для Turnkey SDK.

## Форматирование шаблонов `#[error]`

Шаблон `#[error("...")]` по умолчанию использует `Display`, но любая
подстановка может запросить другой форматтер. `masterror::Error` понимает тот же
набор спецификаторов, что и `thiserror` v2: `:?`, `:x`, `:X`, `:p`, `:b`, `:o`,
`:e`, `:E`, а также их версии с `#` для альтернативного вывода. Неподдержанные
форматтеры приводят к диагностике на этапе компиляции, совпадающей с
`thiserror`.

| Спецификатор | Трейт | Пример результата |
|------------------|-------------------------|--------------------------|
| _по умолчанию_ | `core::fmt::Display` | `value` |
| `:?` / `:#?` | `core::fmt::Debug` | `Struct { .. }` / многострочный |
| `:x` / `:#x` | `core::fmt::LowerHex` | `0x2a` |
| `:X` / `:#X` | `core::fmt::UpperHex` | `0x2A` |
| `:p` / `:#p` | `core::fmt::Pointer` | `0x1f00` / `0x1f00` |
| `:b` / `:#b` | `core::fmt::Binary` | `101010` / `0b101010` |
| `:o` / `:#o` | `core::fmt::Octal` | `52` / `0o52` |
| `:e` / `:#e` | `core::fmt::LowerExp` | `1.5e-2` |
| `:E` / `:#E` | `core::fmt::UpperExp` | `1.5E-2` |

~~~rust
use core::ptr;

use masterror::Error;

#[derive(Debug, Error)]
#[error(
"debug={payload:?}, hex={id:#x}, ptr={ptr:p}, bin={mask:#b}, \
oct={mask:o}, lower={ratio:e}, upper={ratio:E}"
)]
struct FormatterDemo {
id: u32,
payload: String,
ptr: *const u8,
mask: u8,
ratio: f32,
}

let err = FormatterDemo {
id: 0x2a,
payload: "hello".into(),
ptr: ptr::null(),
mask: 0b1010_0001,
ratio: 0.15625,
};

let rendered = err.to_string();
assert!(rendered.contains("debug=\"hello\""));
assert!(rendered.contains("hex=0x2a"));
assert!(rendered.contains("ptr=0x0"));
assert!(rendered.contains("bin=0b10100001"));
assert!(rendered.contains("oct=241"));
assert!(rendered.contains("lower=1.5625e-1"));
assert!(rendered.contains("upper=1.5625E-1"));
~~~

`masterror::error::template::ErrorTemplate` позволяет разобрать шаблон и
программно проверить запрошенные форматтеры:

~~~rust
use masterror::error::template::{ErrorTemplate, TemplateFormatter};

let template = ErrorTemplate::parse("{code:#x} → {payload:?}").expect("parse");
let mut placeholders = template.placeholders();

let code = placeholders.next().expect("code placeholder");
assert!(matches!(
code.formatter(),
TemplateFormatter::LowerHex { alternate: true }
));

let payload = placeholders.next().expect("payload placeholder");
assert_eq!(
payload.formatter(),
TemplateFormatter::Debug { alternate: false }
);
~~~

> **Совместимость с `thiserror` v2.** Доступные спецификаторы, сообщения об
> ошибках и поведение совпадают с `thiserror` 2.x, поэтому миграция с
> `thiserror::Error` на `masterror::Error` не требует переписывать шаблоны.

## Лицензия

Проект распространяется по лицензии Apache-2.0 или MIT на ваш выбор.
80 changes: 80 additions & 0 deletions README.template.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,86 @@ assert_eq!(wrapped.to_string(), "I/O failed: disk offline");
- `masterror::error::template::ErrorTemplate` parses `#[error("...")]`
strings, exposing literal and placeholder segments so custom derives can be
implemented without relying on `thiserror`.
- `TemplateFormatter` mirrors `thiserror`'s formatter detection so existing
derives that relied on hexadecimal, pointer or exponential renderers keep
compiling.

#### Formatter traits

Placeholders default to `Display` (`{value}`) but can opt into richer
formatters via the same specifiers supported by `thiserror` v2. Unsupported
formatters surface a compile error that mirrors `thiserror`'s diagnostics.

| Specifier | `core::fmt` trait | Example output |
|------------------|----------------------------|------------------------|
| _default_ | `core::fmt::Display` | `value` |
| `:?` / `:#?` | `core::fmt::Debug` | `Struct { .. }` / multi-line |
| `:x` / `:#x` | `core::fmt::LowerHex` | `0x2a` |
| `:X` / `:#X` | `core::fmt::UpperHex` | `0x2A` |
| `:p` / `:#p` | `core::fmt::Pointer` | `0x1f00` / `0x1f00` |
| `:b` / `:#b` | `core::fmt::Binary` | `101010` / `0b101010` |
| `:o` / `:#o` | `core::fmt::Octal` | `52` / `0o52` |
| `:e` / `:#e` | `core::fmt::LowerExp` | `1.5e-2` |
| `:E` / `:#E` | `core::fmt::UpperExp` | `1.5E-2` |

~~~rust
use core::ptr;

use masterror::Error;

#[derive(Debug, Error)]
#[error(
"debug={payload:?}, hex={id:#x}, ptr={ptr:p}, bin={mask:#b}, \
oct={mask:o}, lower={ratio:e}, upper={ratio:E}"
)]
struct FormattedError {
id: u32,
payload: String,
ptr: *const u8,
mask: u8,
ratio: f32,
}

let err = FormattedError {
id: 0x2a,
payload: "hello".into(),
ptr: ptr::null(),
mask: 0b1010_0001,
ratio: 0.15625,
};

let rendered = err.to_string();
assert!(rendered.contains("debug=\"hello\""));
assert!(rendered.contains("hex=0x2a"));
assert!(rendered.contains("ptr=0x0"));
assert!(rendered.contains("bin=0b10100001"));
assert!(rendered.contains("oct=241"));
assert!(rendered.contains("lower=1.5625e-1"));
assert!(rendered.contains("upper=1.5625E-1"));
~~~

~~~rust
use masterror::error::template::{ErrorTemplate, TemplateFormatter};

let template = ErrorTemplate::parse("{code:#x} → {payload:?}").expect("parse");
let mut placeholders = template.placeholders();

let code = placeholders.next().expect("code placeholder");
assert!(matches!(
code.formatter(),
TemplateFormatter::LowerHex { alternate: true }
));

let payload = placeholders.next().expect("payload placeholder");
assert_eq!(
payload.formatter(),
TemplateFormatter::Debug { alternate: false }
);
~~~

> **Compatibility with `thiserror` v2:** the derive understands the extended
> formatter set introduced in `thiserror` 2.x and reports identical diagnostics
> for unsupported specifiers, so migrating existing derives is drop-in.

```rust
use masterror::error::template::{ErrorTemplate, TemplateIdentifier};
Expand Down
Loading
Loading