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 named FooError would have the enum FooErrorDetail 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 in FooError becomes a Bar variant in FooErrorDetail.

    • Define a struct with the Subdetail postfix. e.g. Bar would have a BarSubdetail struct.

      • The struct contains all named fields if specified.

      • If an error source is specified, a source field is also defined with the type AsErrorDetail<ErrorSource>. e.g. a suberror with DisplayError<SourceError> would have the field source: SourceError. Because of this, the field name source 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 function bar_error.

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.