Macro flex_error::define_error [−][src]
macro_rules! define_error { ( $name:ident $expr:tt ) => { ... }; ( #$derive:tt $name:ident $expr:tt ) => { ... }; ( @with_tracer[ $tracer:ty ] $name:ident $expr:tt ) => { ... }; ( @with_tracer[ $tracer:ty ] #$derive:tt $name:ident $expr:tt ) => { ... }; }
Expand description
define_error!
is the main macro that implements a mini DSL to
define error types using flex-error
. The DSL syntax
is as follows:
define_error! { ErrorName { SubErrorWithFieldsAndErrorSource { field1: Type1, field2: Type2, ... } [ ErrorSource ] | e | { format_args!( "format error message with field1: {}, field2: {}, source: {}", e.field1, e.field2, e.source) }, SubErrorWithFieldsOnly { field1: Type1, field2: Type2, ... } | e | { format_args!( "format error message with field1: {}, field2: {}", e.field1, e.field2) }, SubErrorWithSourceOnly [ ErrorSource ] | e | { format_args!( "format error message with source: {}", e.source) }, SubError | e | { format_args!( "only suberror message") }, } }
Behind the scene, define_error!
does the following:
-
Define an enum with the postfix
Detail
, e.g. an error namedFooError
would have the enumFooErrorDetail
defined. -
Define the error name as a type alias to
ErrorReport<ErrorNameDetail, DefaultTracer>
. e.g.type FooError = ErrorReport<FooErrorDetail, DefaultTracer>;
. -
For each suberror, does the following:
-
Define a variant with the suberror name in the detail enum. e.g. a
Bar
suberror inFooError
becomes aBar
variant inFooErrorDetail
. -
Define a struct with the
Subdetail
postfix. e.g.Bar
would have aBarSubdetail
struct.-
The struct contains all named fields if specified.
-
If an error source is specified, a
source
field is also defined with the typeAsErrorDetail<ErrorSource>
. e.g. a suberror withDisplayError<SourceError>
would have the fieldsource: SourceError
. Because of this, the field namesource
is reserved and should not be present in other detail fields.
-
-
Implement
Display
for the suberror using the provided formatter to format the arguments. The argument type of the formatter is the suberror subdetail struct. -
Define a suberror constructor function in snake case with the postfix
_error
. e.g.Bar
would have the constructor functionbar_error
.-
The function accepts arguments according to the named fields specified.
-
If an error source is specified, the constructor function also accepts a last argument of type
AsErrorSource<ErrorSource>
. e.g. a suberror withDisplayError<SourceError>
would have the last argument of typeSourceError
in the constructor function. -
The function returns the main error type. e.g.
FooError
, which is alias toErrorReport<FooErrorDetail, DefaultTrace>
.
-
-
We can demonstrate the macro expansion of define_error!
with the following example:
// An external error type implementing Display use external_crate::ExternalError; define_error! { FooError { Bar { code: u32 } [ DisplayError<ExternalError> ] | e | { format_args!("Bar error with code {}", e.code) }, Baz { extra: String } | e | { format_args!("General Baz error with extra detail: {}", e.extra) } } }
The above code will be expanded into something like follows:
pub type FooError = Report<FooErrorDetail, DefaultTracer>; #[derive(Debug)] pub enum FooErrorDetail { Bar(BarSubdetail), Baz(BazSubdetail), } #[derive(Debug)] pub struct BarSubdetail { pub code: u32, pub source: ExternalError } #[derive(Debug)] pub struct BazSubdetail { pub extra: String } fn bar_error(code: u32, source: ExternalError) -> FooError { ... } fn baz_error(extra: String) -> FooError { ... } impl Display for BarSubdetail { fn fmt(&self, f: &mut Formatter<'_>) -> Result { let e = self; write!(f, "{}", format_args!("Bar error with code {}", e.code)) } } impl Display for BazSubdetail { fn fmt(&self, f: &mut Formatter<'_>) -> Result { let e = self; write!(f, "{}", format_args!("General Baz error with extra detail: {}", e.code)) } } impl Display for FooErrorDetail { ... }
For the detailed macro expansion, you can use cargo-expand
to expand the Rust module that uses define_error!
to see how the error definition
gets expanded.
Because FooError
is defined as an alias to ErrorReport
,
it automatically implements ErrorSource
and can be used
as a source in other error definitions. For example:
define_error! { QuuxError { Foo { action: String } [ FooError ] | e | { format_args!("error arised from Foo when performing action {}", e.action) }, ... } }
Would be expanded to include the following definitions:
pub struct FooSubdetail { pub action: String, pub source: FooErrorDetail } pub fn foo_error(action: String, source: FooError) { ... }
In the formatter for QuuxErrorDetail::Foo
, we can also see that it does not
need to include the error string from FooError
. This is because the error
tracer already takes care of the source error trace, so the full trace is
automatically tracked inside foo_error
. The outer error only need to
add additional detail about what caused the source error to be raised.