Attribute Macro err_tree

Source
#[err_tree]
Available on crate feature derive only.
Expand description

Implements a type as an error tree.

The struct must define Error and be annotated with #[err_tree] above any attributes relying on a full field definition. The type must then be internally constructed with Self::_tree to capture extra error information in a hidden field.

Any derive such as [Clone] that relies on all fields being present must occur after the #[err_tree] macro. The _err_tree_pkg field will otherwise be added late and break the derivation.

§Self::_tree

This is an internal-use constructor that takes all struct fields in order. Use #[track_caller] on any functions calling Self::_tree to store the callsite correctly. Open an issue or PR if this hidden field degrades a struct’s API (aside from requiring a constructor method).

§Example
use bare_err_tree::{err_tree, tree_unwrap};

#[err_tree]
#[derive(Debug)]
struct Foo {
    num: i32,
}

impl Foo {
    #[track_caller]
    pub fn new(num: i32) -> Self {
        Foo::_tree(num)
    }
}

impl Error for Foo {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        ...
    }
}
impl Display for Foo {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        ...
    }
}

§Field Annotations

The macro needs annotations for underlying source fields.

§Single Item
  • tree_err: Mark a field as a ErrTree implementing Error.
  • dyn_err: Mark a field as a generic Error.
§Collection

*_iter_err works on any type with a .iter() method returning its items.

  • tree_iter_err: Mark a field as a collection of ErrTree implementing Errors.
  • dyn_iter_err: Mark a field as a collection of generic Errors.

*_iter_err does not allocate for arrays with a known length. The derive_alloc feature enables generation of allocating code to support dynamically sized collections.

§Example
use bare_err_tree::{err_tree, tree_unwrap, AsErrTree, ErrTree};

#[err_tree]
#[derive(Debug)]
struct Foo {
    #[dyn_err]
    io_err: std::io::Error,
    #[dyn_iter_err]
    extra_io_errs: [std::io::Error; 5],
}

impl Foo {
    #[track_caller]
    pub fn new(io_err: std::io::Error, extra_io_errs: [std::io::Error; 5]) -> Self {
        Foo::_tree(io_err, extra_io_errs)
    }
}

impl Error for Foo {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        ...
    }
}
impl Display for Foo {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        ...
    }
}

fn main() {
    // Make a Foo of all EOF errors
    let eof_gen = || std::io::Error::from(std::io::ErrorKind::UnexpectedEof);
    let err = Foo::new(eof_gen(), std::array::from_fn(|_| eof_gen()));

    // Confirm exactly six sources from annotation
    err.as_err_tree(&mut |tree| {
        let sources = tree.sources();
        assert_eq!(sources.count(), 6);
    });
}

§Generating a Wrapper

#[err_tree(WRAPPER)] will generate a wrapper struct for storing metadata. Enums need this form, as a hidden field cannot be added to the enum. WRAPPER provides From both ways and Deref/DerefMut to be maximally transparent. Some derives are automatically re-derived for the wrapper; any other traits that need to be implemented for the wrapper can be written manually.

§Wrapper automatic re-derives

Eq, PartialEq, Ord, PartialOrd, Clone, Hash, Default.

§Enum Example
use bare_err_tree::{err_tree, tree_unwrap};

// Generates `FooWrap<T: Debug>`
#[err_tree(FooWrap)]
#[derive(Debug)]
enum Foo<T: Debug> {
    Val(T),
    #[dyn_err]
    Single(std::io::Error),
    #[dyn_iter_err]
    Many([std::io::Error; 5]),
}

impl<T: Debug> Error for Foo<T> {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        ...
    }
}
impl<T: Debug> Display for Foo<T> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        ...
    }
}

fn main() {
    let wrapped = FooWrap::from(Foo::Val(8_i32));
    assert!(matches!(*wrapped, Foo::Val(8_i32)));
}

§Full Usage Example:

use bare_err_tree::{err_tree, tree_unwrap};

#[err_tree]
#[derive(Debug)]
struct Foo {
    #[dyn_err]
    io_err: std::io::Error,
    #[dyn_iter_err]
    extra_io_errs: [std::io::Error; 5],
}

impl Foo {
    #[track_caller]
    pub fn new(io_err: std::io::Error, extra_io_errs: [std::io::Error; 5]) -> Self {
        Foo::_tree(io_err, extra_io_errs)
    }
}

impl Error for Foo {
    fn source(&self) -> Option<&(dyn Error + 'static)> {
        Some(&self.io_err)
    }
}
impl Display for Foo {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        ...
    }
}

/// Always return the error with tree formatting support
pub fn always_fail() -> Result<(), Foo> {
    Err(Foo::new(
        ...
    ))
}

const MAX_DEPTH: usize = 10;
const MAX_CHARS: usize = MAX_DEPTH * 6;

pub fn main() {
    let result = always_fail();

    /// Fancy display panic with a maximum tree depth of 10 errors
    tree_unwrap::<MAX_CHARS, _, _>(result);
}