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 and core::fmt::Display for MyError.

      • If the "std" feature is enabled on the flex-error crate, it will generate an impl block for std::error::Error.

      • Implement ErrorSource<DefaultTracer> for MyError, with MyErrorDetail being the Detail type, and MyError being the Source 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 implementing ErrorSource<DefaultTracer>, define a source 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 implementing ErrorSource, define a source 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.