-
-
Notifications
You must be signed in to change notification settings - Fork 0
Derive Macros en
masterror ships two derives via the bundled masterror-derive crate:
-
#[derive(Error)]— a drop-in replacement forthiserror::Error(same#[error],#[from],#[source],#[backtrace]attributes) extended with#[app_error(...)]conversions and#[provide(...)]telemetry. -
#[derive(Masterror)]— builds on the same syntax and wires a domain error directly intomasterror::Errorwith metadata, redaction policy and transport mapping tables via#[masterror(...)].
Both are re-exported from the root: use masterror::{Error, Masterror};.
The template drives the generated Display implementation. Placeholders reference fields by name ({field}), tuple index ({0}) or explicit arguments. Parsing is handled by the shared masterror-template crate and mirrors thiserror semantics.
use masterror::Error;
#[derive(Debug, Error)]
#[error("{kind}: {message}")]
struct NamedError {
kind: &'static str,
message: &'static str
}
#[derive(Debug, Error)]
#[error("{0} -> {1:?}")]
struct TupleError(&'static str, u8);Placeholders support the full formatter palette — {x:?}, {x:#?}, {x:x}, {x:#X}, {x:b}, {x:o}, {x:e}, {x:E}, {x:p} — and display-only specs such as {value:>8} or {ratio:.3} are forwarded verbatim. For programmatic template inspection, masterror::error::template exposes ErrorTemplate, TemplateFormatter and TemplateFormatterKind.
Templates accept named and positional arguments, including expressions on self and field projections with the .field shortcut:
use masterror::Error;
#[derive(Debug, Error)]
#[error("{formatted}", formatted = self.message.to_uppercase())]
struct FormatArgExpressionError {
message: &'static str
}
#[derive(Debug, Error)]
#[error("{}, {label}, {}", label = self.label, self.first, self.second)]
struct MixedImplicitArgsError {
label: &'static str,
first: &'static str,
second: &'static str
}
#[derive(Debug, Error)]
#[error("{value}", value = .value)]
struct FieldShortcutError {
value: &'static str
}use masterror::Error;
#[derive(Debug, Error)]
#[error("inner failure")]
struct Inner;
// Forwards Display and source() to the single wrapped field
#[derive(Debug, Error)]
#[error(transparent)]
struct Wrapper(#[from] Inner);
// Delegate rendering to a function: fields first, formatter last
fn render(count: &usize, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "count={count}")
}
#[derive(Debug, Error)]
#[error(fmt = crate::render)]
struct CustomFormat {
count: usize
}transparent requires exactly one field and cannot be combined with fmt or a template string. fmt = path points at a function receiving references to all fields plus the Formatter.
| Attribute | Effect |
|---|---|
#[source] |
Field is returned from source(). Option<E> is supported. |
#[from] |
Generates From<FieldType> for the wrapper; implies #[source] on the same field. |
#[backtrace] |
Field holds a std::backtrace::Backtrace (or Option<Backtrace>) surfaced via error introspection, or delegates to the source's backtrace when combined with #[source]. |
Inference: a field literally named source is treated as the source automatically, and a field of type std::backtrace::Backtrace (or Option<Backtrace>) is picked up as the backtrace without an attribute.
Enums accept per-variant #[error] and per-variant #[from]/#[source]/#[backtrace]:
use masterror::Error;
#[derive(Debug, Error)]
#[error("leaf failure")]
struct LeafError;
#[derive(Debug, Error)]
enum EnumError {
#[error("unit failure")]
Unit,
#[error("{code}")]
Code {
code: u16,
#[source]
cause: LeafError
},
#[error(transparent)]
Wrapped(#[from] LeafError)
}Records how the derived error translates into AppError/AppCode. Options: kind (required), code (optional), message (flag), no_source (flag).
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));
let code: AppCode = MissingFlag { name: "other" }.into();
assert_eq!(code, AppCode::BadRequest);-
kind = ...selects theAppErrorKind; generatesFrom<T> for AppError. -
code = ...additionally generatesFrom<T> for AppCode. -
messageforwards theDisplayoutput as the public message; omit it to keep the message internal. -
no_sourceskips source attachment and drops the domain error during conversion (for types that are notSend + Sync + 'static).
The generated From<T> for AppError attaches the original domain error as the source: it stays downcastable via downcast_ref, appears in chain()/root_cause(), and its #[provide] data is forwarded through the AppError on toolchains with error_generic_member_access. Source attachment requires T: Send + Sync + 'static.
Enums choose a mapping per variant, and the derive still emits a single From<Enum> for AppError.
Exposes typed context through std::error::Request (nightly error_generic_member_access; compiled in automatically when available). Option fields only register a provider when populated:
use masterror::{AppCode, AppErrorKind, Error};
#[derive(Clone, Debug, PartialEq, Eq)]
struct TelemetrySnapshot {
name: &'static str,
value: u64
}
#[derive(Debug, Error)]
#[error("structured telemetry {snapshot:?}")]
#[app_error(kind = AppErrorKind::Service, code = AppCode::Service)]
struct StructuredTelemetryError {
#[provide(ref = TelemetrySnapshot, value = TelemetrySnapshot)]
snapshot: TelemetrySnapshot
}Consumers extract the snapshot with std::error::request_ref::<TelemetrySnapshot>(&err) on the domain error, or on the converted AppError — the conversion forwards providers through the attached source.
#[derive(Masterror)] generates Display, std::error::Error, From<T> for masterror::Error and compile-time transport mapping tables, all configured by one #[masterror(...)] attribute:
use masterror::{
AppCode, AppErrorKind, Error, Masterror, MessageEditPolicy, mapping::HttpMapping
};
#[derive(Debug, Masterror)]
#[error("user {user_id} missing flag {flag}")]
#[masterror(
code = AppCode::NotFound,
category = AppErrorKind::NotFound,
message,
redact(message, fields("user_id" = hash)),
telemetry(
Some(masterror::field::str("user_id", user_id.clone())),
attempt.map(|value| masterror::field::u64("attempt", value))
),
map.grpc = 5,
map.problem = "https://errors.example.com/not-found"
)]
struct MissingFlag {
user_id: String,
flag: &'static str,
attempt: Option<u64>,
#[source]
source: Option<std::io::Error>
}
let err = MissingFlag {
user_id: "alice".into(),
flag: "beta",
attempt: Some(2),
source: None
};
let converted: Error = err.into();
assert_eq!(converted.code, AppCode::NotFound);
assert_eq!(converted.kind, AppErrorKind::NotFound);
assert_eq!(converted.edit_policy, MessageEditPolicy::Redact);
assert!(converted.metadata().get("user_id").is_some());
assert_eq!(
MissingFlag::HTTP_MAPPING,
HttpMapping::new(AppCode::NotFound, AppErrorKind::NotFound)
);| Option | Meaning |
|---|---|
code = AppCode::... |
Public machine-readable code |
category = AppErrorKind::... |
Semantic category (drives HTTP status) |
message |
Expose the formatted Display output as the safe public message |
redact(message) |
Set MessageEditPolicy::Redact so transports strip the message |
redact(fields("name" = hash, "card" = last4)) |
Override per-field metadata policies: hash, last4, redact, none
|
telemetry(expr, ...) |
Expressions evaluating to Option<masterror::Field>; populated fields are inserted into Metadata. Use telemetry() for none |
map.grpc = <i32> |
gRPC status code (matches tonic::Code discriminants) |
map.problem = "<uri>" |
RFC 7807 type URI |
For structs the derive emits associated constants; for enums it emits an array and slices aggregating the per-variant mappings:
| Shape | Constants |
|---|---|
| Struct |
T::HTTP_MAPPING: HttpMapping, T::GRPC_MAPPING: Option<GrpcMapping>, T::PROBLEM_MAPPING: Option<ProblemMapping>
|
| Enum |
T::HTTP_MAPPINGS: [HttpMapping; N], T::GRPC_MAPPINGS: &'static [GrpcMapping], T::PROBLEM_MAPPINGS: &'static [ProblemMapping]
|
The descriptor types live in masterror::mapping (HttpMapping::status() derives the HTTP code from the kind; GrpcMapping::status() returns the i32; ProblemMapping::type_uri() returns the URI).
#[from], #[source] and #[backtrace] keep working under #[derive(Masterror)]; sources and captured backtraces are attached to the resulting masterror::Error automatically, and Arc-wrapped sources are reused without extra cloning.
| Need | Use |
|---|---|
Display + source + From, thiserror-style |
#[derive(Error)] |
Also convert into AppError/AppCode
|
#[derive(Error)] + #[app_error(...)]
|
Typed context via std::error::Request
|
add #[provide(...)]
|
| Metadata, redaction policy, gRPC/problem+json tables |
#[derive(Masterror)] + #[masterror(...)]
|
See also: Getting Started · Error Kinds and Codes · Context and Metadata · Migration
🇬🇧 English | 🇷🇺 Русский | 🇰🇷 한국어
Getting Started
Core Concepts
Integrations
Advanced
Начало работы
Основы
Интеграции
Продвинутое
시작하기
핵심 개념
통합
고급