#error #hierarchy #macro #tree

macro hierrorchy

Proc macro library to simplify the definition of error hierarchies

1 unstable release

0.1.0 Dec 23, 2024

#1070 in Rust patterns


Used in gitbox

MPL-2.0 license

19KB
211 lines

Hierrorchy

Hierrorchy is a proc-macro library to simplify the creation of error hierarchies (hence the name), in a tree-like structure.

This crate is based on two concepts:

As nodes are "just" containers for other errors, they are enums with a variant for each type of error they can contain; while leaves, which must be as open as possible, are structs.

Examples

Example of an error leaf

Error leaves are declared by adding an attribute to a struct definition (see hierrorchy::error_leaf documentation for details on its configuration):

use hierrorchy::error_leaf;

#[error_leaf("My error")]
struct MyError {}

The attribute adds the implementation of std::fmt::Display and std::error::Error, thus writing the snippet of code above is equivalent to wrinting the following code:

#[derive(Debug)]
struct MyError{}

impl std::fmt::Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", "My error")
    }
}

impl std::error::Error for MyError {}

As you can see from the snippet above, hierrorchy::error_leaf adds the attribute for deriving the std::fmt::Debug implementation, as it is required by std::error::Error.

Example of an error node

Error nodes are declared by the function-like macro hierrorchy::error_node:

use hierrorchy::{error_leaf,error_node};
use std::error::Error;

#[error_leaf("My error")]
struct MyError {}

error_node! { type MyErrorNode<MyError> = "my error node" }

This snippet is equivalent to:

use hierrorchy::error_leaf;
use std::error::Error;

#[error_leaf("My error")]
struct MyError {}

#[derive(Debug)]
enum MyErrorNode {
    Variant0(MyError),
}

impl std::fmt::Display for MyErrorNode {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "my error node: {}", &self.source().expect("MyErrorNode always has a source"))
    }
}

impl std::error::Error for MyErrorNode {
   fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
       match self {
           Self::Variant0(e) => Some(e),
        }
    }
}

impl From<MyError> for MyErrorNode {
    fn from(value: MyError) -> Self {
        Self::Variant0(value)
    }
}

As it can be seen in the snippet above, hierrorchy::error_node also implements std::convert::Froms for each variant of the node, allowing to leverage the ? operator in functions which return a std::result::Result.

Complete example

use hierrorchy::{error_leaf,error_node};
use rand::prelude::*;
use std::error::Error;
use std::process::exit;

fn main() {
    if let Err(e) = entrypoint() {
        eprintln!("{}", e);
        exit(1);
    }
}

fn entrypoint() -> Result<(), MyErrorNode> {
    check_boolean()?;
    let value = rand::random::<i32>();
    check_value(value)?;
    Ok(())
}

fn check_boolean() -> Result<(), MyFirstErrorLeaf> {
    if rand::random() {
        Err(MyFirstErrorLeaf {})
    } else {
       Ok(())
    }
}

fn check_value(value: i32) -> Result<(), MySecondErrorLeaf> {
    if value % 2 == 0 {
       Err(MySecondErrorLeaf { value })
    } else {
        Ok(())
    }
}

#[error_leaf("first check failed")]
struct MyFirstErrorLeaf {}

#[error_leaf(format!("second check failed: value is {}", self.value))]
struct MySecondErrorLeaf {
    value: i32,
}

error_node! { type MyErrorNode<MyFirstErrorLeaf, MySecondErrorLeaf> = "error node" }

Dependencies

~210–650KB
~15K SLoC