Macro flex_error::define_error [−][src]
macro_rules! define_error {
($name : ident { $($suberrors : tt) * }) => { ... };
(#[doc = $doc : literal] $(#[$attr : meta]) * $name : ident
{ $($suberrors : tt) * }) => { ... };
($(#[$attr : meta]) * $name : ident { $($suberrors : tt) * }) => { ... };
(@ with_tracer [$tracer : ty] $name : ident, { $($suberrors : tt) * }) => { ... };
(@ with_tracer [$tracer : ty] $(#[$attr : meta]) * $name : ident, @ suberrors
{ $($suberrors : 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")
},
}
}
Macro Expansion
Behind the scene, for an error named MyError
, define_error!
does the following:
-
Define a struct in the form
ⓘpub struct MyError(pub MyErrorDetail, pub flex_error::DefaultTracer)
-
Define an enum in the form
ⓘpub enum MyError { ... }
For each suberror named
MySubError
, does the following:-
Define a variant in
MyError
in the formⓘpub enum MyError { ..., MySubError(MySubErrorSubdetail) ,... }
-
Implement
core::fmt::Debug
andcore::fmt::Display
forMyError
. -
If the
"std"
feature is enabled on theflex-error
crate, it will generate animpl
block forstd::error::Error
. -
Implement
ErrorSource<DefaultTracer>
forMyError
, withMyErrorDetail
being theDetail
type, andMyError
being theSource
type. -
Implement the following helper methods in
impl MyError {...}
:-
pub fn detail(&self) -> &MyErrorDetail
-
pub fn trace(&self) -> flex_error::DefaultTracer
-
pub fn add_trace<E: Display>(self, e: &E) -> MyError
-
-
-
Define a struct in the form
ⓘpub struct MySubErrorSubdetail { ... }
-
For each field named
my_field: MyFieldType
, define a struct field in the formⓘstruct MySubErrorSubdetail { ..., pub my_field: MyFieldType, ... }
-
If the sub-error has an error source
MySource
implementingErrorSource<DefaultTracer>
, define asource
field in the formⓘstruct MySubErrorSubdetail { ..., pub source: MySource::Detail }
-
Implement
core::fmt::Display
in the formⓘimpl Display for MySubErrorSubdetail { ... }
-
-
Define a snake-cased error constructor method in
MyError
in the formⓘimpl MyError { pub fn my_sub_error(...) -> MyError { ... } }
-
For each field named
my_field: MyFieldType
, define a function argument in the formⓘfn my_sub_error(..., my_field: MyFieldType, ...)
-
If the sub-error has an error source
MySource
implementingErrorSource
, define asource
field in the formⓘfn my_sub_error(..., source: MySource::Detail)
-
-
Formatter
For each sub-error definition, a formatter needs to be provided using the closure syntax. For example, the following definition:
MyError {
MySubError
{ code: u32 }
[ MySource ]
| e | { format_args!("error with code {}", e.code) },
...
}
will include the following expansion:
impl ::core::fmt::Display for MySubErrorSubdetail {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
let e = self;
write!(f, "{}", format_args!("error with code {}", e.code))
}
}
Note that there is no need to manually display the error source, as the source is already automatically traced by the error tracer.
If a sub-error do not have any field, we can write a simpler form of the formatter like:
MyError {
MySubError
| _ | { "something went wrong" },
...
}
Example Definition
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 struct FooError(pub FooErrorDetail, pub flex_error::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 ::core::fmt::Display for BarSubdetail {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
let e = self;
write!(f, "{}", format_args!("Bar error with code {}", e.code))
}
}
impl ::core::fmt::Display for BazSubdetail {
fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::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.
Since FooError
implements ErrorSource
, it can be used
directly as a error 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.
Attributes
define_error!
supports adding attributes to the generated error types.
First doc
Attribute
If the first attribute is a doc
attribute, it is attached to the main error struct. For example:
define_error! {
/// Documentation for MyError
MyError { ... }
}
will include the following expansion:
#[doc = "Documentation for MyError"]
pub struct MyError(pub MyErrorDetail, pub flex_error::DefaultTracer);
Common Attributes
For all other attributes defined on the main error type, they are defined on the error detail and _sub-errors type. For example:
define_error! {
#[derive(Debug, Clone)]
MyError {
Foo
{ ... }
| _ | { "foo error" },
Bar
{ ... }
| _ | { "bar error" },
}
}
will include the following expansion:
pub struct MyError(pub MyErrorDetail, pub flex_error::DefaultTracer);
#[derive(Debug, Clone)]
pub enum MyErrorDetail { ... }
#[derive(Debug, Clone)]
pub struct FooSubdetail { ... }
#[derive(Debug, Clone)]
pub struct BarSubdetail { ... }
Note that we do not add the derive
attribute to the main error struct
MyError
. This is because the DefaultTracer
type is opaque, and auto deriving traits like Clone
on it is
generally not supported.
If you need the main error type to implement certain traits,
you can instead define your own custom impl
definition for it.
Sub Attributes
We can also define custom attributes for only the sub-error. In that case, the attribute is given to the sub-detail type. For example:
define_error! {
MyError {
/// Documentation for Foo
#[derive(Clone)]
Foo
{ ... }
| _ | { "foo error" },
...
}
}
will include the following expansion:
#[doc = "Documentation for Foo"]
#[derive(Clone)]
pub struct FooSubdetail { ... }
Note that if no attribute is given to the main error,
the #[derive(Debug)]
trait is added by default.
So there is no need to derive it again in the
sub-errors.