Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Контекст и метаданные

masterror заменяет контекст, склеенный из строк (format!("failed to X: {e}")), тремя структурированными механизмами: билдером Context, типизированными полями Metadata и политиками редактирования, применяемыми на транспортной границе.

ResultExt: продвижение внешних ошибок

ResultExt реализован для каждого Result<T, E>, где E: Error + Send + Sync + 'static, и предлагает два метода:

.context(msg) — в стиле anyhow

Оборачивает ошибку сообщением; исходная ошибка становится источником:

use masterror::ResultExt;

fn read_config() -> Result<String, std::io::Error> {
    Err(std::io::Error::from(std::io::ErrorKind::NotFound))
}

let err = read_config().context("Failed to read config file").unwrap_err();
assert!(err.source_ref().is_some());

Если нижележащая ошибка уже является masterror::Error, .context() сохраняет её классификацию: вид, код, метаданные, политика редактирования, подсказка повторной попытки и детали переносятся, заменяется только сообщение, а исходная ошибка сохраняется как источник.

.ctx(|| Context) — полный контроль

use masterror::{AppErrorKind, Context, ResultExt, field};

fn validate() -> Result<(), std::io::Error> {
    Err(std::io::Error::other("boom"))
}

let err = validate()
    .ctx(|| {
        Context::new(AppErrorKind::Validation)
            .with(field::str("phase", "validate"))
            .redact(true)
            .track_caller()
    })
    .unwrap_err();

assert_eq!(err.kind, AppErrorKind::Validation);
assert!(err.metadata().get("phase").is_some());

Замыкание вычисляется только на пути ошибки.

Билдер Context

МетодЭффект
Context::new(kind)Целевая категория; AppCode по умолчанию берётся из канонического отображения для этого вида
.code(AppCode)Переопределить публичный код
.category(kind)Сменить категорию; код остаётся синхронизированным, если не был переопределён
.with(field)Прикрепить Field метаданных
.redact(bool)Переключить редактирование сообщения (MessageEditPolicy::Redact / Preserve)
.redact_field(name, FieldRedaction)Переопределить политику редактирования именованного поля
.track_caller()Записать место вызова в метаданные caller.file, caller.line, caller.column

Поля метаданных

Metadata — отсортированная карта типизированных полей с inline-размещением (0–4 поля остаются на стеке). Создавайте поля через модуль masterror::field:

КонструкторВариант FieldValue
field::str("key", value)Str(Cow<'static, str>)
field::i64("key", -1)I64
field::u64("key", 42)U64
field::f64("key", 0.5)F64
field::bool("key", true)Bool
field::uuid("key", uuid)Uuid
field::duration("key", dur)Duration
field::ip("key", addr)Ip (v4 или v6)
field::json("key", json!({...}))Json (требует функции serde_json)

Прикрепляйте поля при создании ошибок или через Context:

use core::time::Duration;
use masterror::{AppError, FieldValue, field};

let err = AppError::service("downstream degraded")
    .with_field(field::str("request_id", "abc123"))
    .with_field(field::duration("elapsed", Duration::from_millis(1500)))
    .with_field(field::u64("attempt", 2));

assert_eq!(err.metadata().len(), 3);
assert_eq!(err.metadata().get("attempt"), Some(&FieldValue::U64(2)));

for (name, value) in err.metadata().iter() {
    println!("{name}={value}");
}

with_fields(iter) расширяет карту из итератора, with_metadata(meta) заменяет контейнер, а Metadata::insert возвращает прежнее значение при перезаписи ключа.

Политики редактирования

Политика сообщения: MessageEditPolicy

Preserve (по умолчанию) сохраняет публичное сообщение; Redact велит транспортам удалить его. Устанавливается через .redactable() на ошибке, .redact(true) на Context или redact(message) в #[masterror(...)]:

use masterror::{AppError, MessageEditPolicy, ProblemJson};

let err = AppError::internal("db-3 credentials rejected").redactable();
assert_eq!(err.edit_policy, MessageEditPolicy::Redact);

let problem = ProblemJson::from_app_error(err);
assert!(problem.detail.is_none());

Политика поля: FieldRedaction

Каждое поле несёт собственную политику, применяемую при сериализации метаданных в ProblemJson:

ПолитикаЭффект на публичную нагрузку
NoneЗначение сохраняется как есть
RedactПоле удаляется целиком
HashЗначение заменяется дайджестом SHA-256
Last4Маскируется всё, кроме последних четырёх символов
use masterror::{AppError, FieldRedaction, field};

let err = AppError::internal("payment failed")
    .with_field(field::str("card_number", "4111111111111111"))
    .redact_field("card_number", FieldRedaction::Last4);

Имена, похожие на секреты, автоматически получают безопасное значение по умолчанию при создании поля: имена, содержащие password, secret, authorization, cookie, session, jwt, bearer, otp, pin, по умолчанию получают Redact; имена в духе токенов и ключей (api_token, refresh_token, key, apikey) — Hash; сегменты карт/счетов в сочетании с числовым сегментом (card_number, iban_no, account_id) — Last4. Распознавание регистронезависимо. Явный redact_field/with_redaction всегда имеет приоритет.

Цепочки ошибок

Ошибки сохраняют полную причинную цепочку. chain() итерирует от самой ошибки до первопричины; root_cause() сразу переходит к самой глубокой ошибке:

use masterror::AppError;

let io_err = std::io::Error::other("disk offline");
let app_err = AppError::internal("db down").with_context(io_err);

let chain: Vec<_> = app_err.chain().collect();
assert_eq!(chain.len(), 2);
assert_eq!(app_err.root_cause().to_string(), "disk offline");

with_context(...) — предпочтительный способ прикрепить вышестоящую ошибку: он принимает владеемые ошибки или разделяемые значения Arc<dyn Error + Send + Sync> и переиспользует существующие аллокации. with_source(...) / with_source_arc(...) — их низкоуровневые эквиваленты.

Даункастинг

Проверяйте прикреплённый источник через is и downcast_ref/downcast_mut:

use masterror::AppError;

let io_err = std::io::Error::other("disk offline");
let err = AppError::internal("boom").with_context(io_err);

assert!(err.is::<std::io::Error>());

if let Some(io) = err.downcast_ref::<std::io::Error>() {
    assert_eq!(io.to_string(), "disk offline");
}
  • is::<E>()true, когда непосредственный источник имеет тип E (не обходит всю цепочку).
  • downcast_ref::<E>() — заимствует источник как E.
  • downcast::<E>() / downcast_mut::<E>() — пока заглушки (downcast всегда возвращает Err(self), downcast_mut всегда возвращает None), поэтому предпочитайте downcast_ref.

Для более глубоких проверок обходите chain() и вызывайте source.is::<E>() / source.downcast_ref::<E>() на каждом элементе.

Бэктрейсы

С функцией backtrace метод err.backtrace() возвращает лениво захваченный std::backtrace::Backtrace, учитывающий RUST_BACKTRACE, а with_backtrace(bt) прикрепляет явно захваченный. Бэктрейсы разделяются через Arc при переоборачивании ошибок, поэтому цепочки .context() не выполняют повторный захват.


См. также: Начало работы · Виды и коды ошибок · Derive-макросы · Наблюдаемость · Лучшие практики