masterror
Фреймворк-независимые типы ошибок для приложений со стабильными кодами, отображениями HTTP/gRPC и встроенной телеметрией
Что такое masterror?
masterror — это рабочее пространство для обработки ошибок в сервисах на Rust, которым нужно больше, чем Display и source(). Там, где thiserror ограничивается генерацией реализаций трейтов, а anyhow — распространением ошибок со стёртым типом, masterror доводит ошибку до самой транспортной границы:
AppError— насыщенное значение ошибки с семантической категорией (AppErrorKind), стабильным машиночитаемым кодом (AppCode), опциональным безопасным публичным сообщением, структурированными метаданными и транспортными подсказками (Retry-After,WWW-Authenticate).- Консервативные отображения HTTP и gRPC — каждый вид и код детерминированно отображается в HTTP-статус, дискриминант
tonic::Codeи URItypeпо RFC 7807. - Типизированная телеметрия — метаданные хранятся как типизированные поля (строки, целые числа, числа с плавающей точкой, длительности, IP-адреса, UUID, JSON) с политиками редактирования для каждого поля, а не как самодельные
String-карты. - Нативные derive-макросы —
#[derive(Error)]повторяет синтаксисthiserror, а#[app_error(...)]и#[derive(Masterror)]встраивают доменные ошибки вAppErrorс кодами, категориями, редактированием и таблицами отображений. - Редактирование по замыслу — источники никогда не сериализуются для клиентов; сообщения, детали и поля метаданных могут быть удалены, захешированы или замаскированы на границе.
Без unsafe, с зафиксированным MSRV и поддержкой no_std при отключённой функции std, включённой по умолчанию.
Какую проблему он решает
| Задача | thiserror | anyhow | masterror |
|---|---|---|---|
Derive для Display / source() | Да | — | Да (тот же синтаксис) |
| Распространение со стёртым типом и контекстом | — | Да | Да (.ctx() / .context()) |
| Стабильные машиночитаемые коды ошибок | Вручную | Вручную | AppCode, часть wire-контракта |
| Отображение в HTTP-статус | Вручную | Вручную | AppErrorKind::http_status(), стабильная таблица |
| Отображение в gRPC-статус | Вручную | Вручную | CODE_MAPPINGS, конверсия в tonic::Status |
RFC 7807 problem+json | Вручную | Вручную | ProblemJson::from_app_error |
| Структурированные типизированные метаданные | — | — | Metadata + конструкторы field::* |
| Редактирование секретов на границе | — | — | MessageEditPolicy, FieldRedaction |
| Эмиссия tracing / metrics / backtrace | — | — | За флагами возможностей, автоматически при создании |
Enum, произведённый thiserror, говорит, что случилось. masterror дополнительно решает, что увидит клиент (статус, код, безопасное сообщение, problem+json), что увидят операторы (структурированные поля, события tracing, счётчики) и что никогда не утечёт (источники, отредактированные поля).
Основные возможности
| Область | Что вы получаете |
|---|---|
| Базовая таксономия | AppError, AppErrorKind (23 стабильные категории), AppCode (коды в SCREAMING_SNAKE_CASE, поддержка собственных кодов), AppResult<T> |
| Derive-макросы | #[derive(Error)], #[derive(Masterror)], #[app_error(...)], #[masterror(...)], провайдеры телеметрии #[provide(...)] |
| Управление потоком | ensure! / fail! — типизированные ранние возвраты без выделения памяти |
| Контекст | ResultExt::ctx / ResultExt::context, билдер Context с отслеживанием вызывающей стороны |
| Wire-полезные нагрузки | ErrorResponse (устаревший JSON), ProblemJson (RFC 7807) с подсказками повторных попыток и аутентификации |
| Транспорты | Axum IntoResponse, Actix ResponseError/Responder, tonic::Status, WASM JsValue, схема OpenAPI |
| Интеграции | sqlx, redis, reqwest, validator, config, tokio, teloxide, init data Telegram Mini Apps, Turnkey |
| Наблюдаемость | События tracing, счётчики metrics, ленивый захват backtrace, цветной вывод в терминале, DisplayMode (prod/staging/local) |
Быстрый пример
use masterror::{AppError, AppErrorKind, AppResult, ProblemJson, field};
fn find_user(id: u64) -> AppResult<()> {
masterror::ensure!(id != 0, AppError::bad_request("id must be non-zero"));
Err(AppError::not_found("user not found")
.with_field(field::u64("user_id", id))
.with_field(field::str("request_id", "abc123")))
}
let err = find_user(42).unwrap_err();
assert_eq!(err.kind, AppErrorKind::NotFound);
assert_eq!(err.kind.http_status(), 404);
let problem = ProblemJson::from_app_error(err);
assert_eq!(problem.status, 404);
assert_eq!(problem.code.as_str(), "NOT_FOUND");
assert_eq!(problem.grpc.expect("grpc").name, "NOT_FOUND");
Или объявите доменную ошибку один раз и позвольте derive-макросу выполнить отображение:
use masterror::{AppCode, AppError, AppErrorKind, Error};
#[derive(Debug, Error)]
#[error("missing flag: {name}")]
#[app_error(kind = AppErrorKind::BadRequest, code = AppCode::BadRequest, message)]
struct MissingFlag {
name: &'static str
}
let app: AppError = MissingFlag { name: "feature" }.into();
assert!(matches!(app.kind, AppErrorKind::BadRequest));
Крейты рабочего пространства
| Крейт | Роль |
|---|---|
masterror | Основные типы ошибок, метаданные, транспорты, интеграции, прелюдия |
masterror-derive | Процедурные макросы за #[derive(Error)] и #[derive(Masterror)] (подключается автоматически) |
masterror-template | Общий парсер шаблонов #[error("...")] |
Документация
Начало работы
- Начало работы — установка, первые ошибки, макросы, первый derive
- Флаги возможностей — полный справочник флагов с зависимостями
Основные концепции
- Виды и коды ошибок — таксономия, таблицы HTTP/gRPC, problem+json
- Derive-макросы —
#[derive(Error)],#[derive(Masterror)]и их атрибуты - Контекст и метаданные —
Context,ResultExt, поля, редактирование, цепочки
Интеграции
- Веб-фреймворки — Axum, Actix, tonic
- Интеграции — sqlx, redis, reqwest, validator и другие
- Наблюдаемость — tracing, metrics, бэктрейсы, режимы отображения
Продвинутое
- Без std — работа без стандартной библиотеки
- Лучшие практики — паттерны для сервисов и библиотек
- Миграция — переход с
thiserror/anyhow