derail 0.3.0

An alternative to `core::error::Error`.
Documentation
use core::error::Error as CoreError;
use std::{fmt, iter};

use derail::{CoreCompat, Error, VisitorExt as _};
use derail_macros::Error;
use similar_asserts::assert_eq;

use crate::visitor::{Event, TestVisitor};

#[cfg(feature = "alloc")]
mod impls_alloc;
mod impls_core;

#[derive(Debug, Clone, Error)]
#[derail(
    type Details = D,
    display("{_0}"),
    impl Error where { D: fmt::Debug + Clone },
)]
pub(crate) struct Leaf<D>(
    pub(crate) &'static str,
    #[derail(details)] pub(crate) D,
);

#[derive(Debug, Clone, Error)]
#[derail(
    type Details = D,
    display("{_0}"),
    impl Error where {
        E: Error<Details = D>,
        D: fmt::Debug + Clone,
    },
)]
pub(crate) struct Tree<E, D>(
    pub(crate) &'static str,
    #[derail(details)] pub(crate) D,
    #[derail(children)] pub(crate) Vec<E>,
);

#[test]
fn dyn_safe() {
    #[expect(dead_code)]
    fn inner(_: &dyn Error<Details = ()>) {}
}

fn make_one_many_many() -> impl Error<Details = i32> {
    let list_0_0 = Leaf("leaf_0_0", 2);
    let list_0_1 = Leaf("leaf_0_1", 3);
    let list_0_2 = Leaf("leaf_0_2", 4);
    let list_0 = Tree("tree_0", 1, vec![list_0_0, list_0_1, list_0_2]);

    let list_1_0 = Leaf("leaf_1_0", 6);
    let list_1_1 = Leaf("leaf_1_1", 7);
    let list_1_2 = Leaf("leaf_1_2", 8);
    let list_1 = Tree("tree_1", 5, vec![list_1_0, list_1_1, list_1_2]);

    let list_2_0 = Leaf("leaf_2_0", 10);
    let list_2_1 = Leaf("leaf_2_1", 11);
    let list_2_2 = Leaf("leaf_2_2", 12);
    let list_2 = Tree("tree_2", 9, vec![list_2_0, list_2_1, list_2_2]);

    Tree("tree", 0, vec![list_0, list_1, list_2])
}

#[test]
fn one_many_many() {
    let expected = [
        Event::visit("tree", 0, None::<&str>),
        Event::Push,
        Event::visit("tree_0", 1, Some("tree_1")),
        Event::Push,
        Event::visit("leaf_0_0", 2, Some("leaf_0_1")),
        Event::visit("leaf_0_1", 3, Some("leaf_0_2")),
        Event::visit("leaf_0_2", 4, None::<&str>),
        Event::Pop,
        Event::visit("tree_1", 5, Some("tree_2")),
        Event::Push,
        Event::visit("leaf_1_0", 6, Some("leaf_1_1")),
        Event::visit("leaf_1_1", 7, Some("leaf_1_2")),
        Event::visit("leaf_1_2", 8, None::<&str>),
        Event::Pop,
        Event::visit("tree_2", 9, None::<&str>),
        Event::Push,
        Event::visit("leaf_2_0", 10, Some("leaf_2_1")),
        Event::visit("leaf_2_1", 11, Some("leaf_2_2")),
        Event::visit("leaf_2_2", 12, None::<&str>),
        Event::Pop,
        Event::Pop,
    ];

    let error = make_one_many_many();

    let mut visitor = TestVisitor::new();
    visitor
        .visit_many(iter::once(&error))
        .continue_value()
        .expect("visit should complete");

    assert_eq!(
        expected: expected,
        actual: visitor.events.as_slice(),
    );
}

#[test]
fn one_many_many_no_ascending() {
    let expected = [
        Event::visit("tree", 0, None::<&str>),
        Event::Push,
        Event::visit("tree_0", 1, Some("tree_1")),
        Event::Push,
        Event::visit("leaf_0_0", 2, Some("leaf_0_1")),
        Event::visit("leaf_0_1", 3, Some("leaf_0_2")),
        Event::visit("leaf_0_2", 4, None::<&str>),
    ];

    let error = make_one_many_many();

    let mut visitor = TestVisitor::new();
    visitor.break_on_ascend = true;
    visitor
        .visit_many(iter::once(&error))
        .break_value()
        .expect("visit should not complete");

    assert_eq!(
        expected: expected,
        actual: visitor.events.as_slice(),
    );
}

#[test]
fn one_many_many_children() {
    let expected = [
        Event::visit("tree_0", 1, Some("tree_1")),
        Event::Push,
        Event::visit("leaf_0_0", 2, Some("leaf_0_1")),
        Event::visit("leaf_0_1", 3, Some("leaf_0_2")),
        Event::visit("leaf_0_2", 4, None::<&str>),
        Event::Pop,
        Event::visit("tree_1", 5, Some("tree_2")),
        Event::Push,
        Event::visit("leaf_1_0", 6, Some("leaf_1_1")),
        Event::visit("leaf_1_1", 7, Some("leaf_1_2")),
        Event::visit("leaf_1_2", 8, None::<&str>),
        Event::Pop,
        Event::visit("tree_2", 9, None::<&str>),
        Event::Push,
        Event::visit("leaf_2_0", 10, Some("leaf_2_1")),
        Event::visit("leaf_2_1", 11, Some("leaf_2_2")),
        Event::visit("leaf_2_2", 12, None::<&str>),
        Event::Pop,
    ];

    let error = make_one_many_many();
    let mut visitor = TestVisitor::new();
    visitor
        .visit_children_of(&error)
        .continue_value()
        .expect("visit should complete");

    assert_eq!(
        expected: expected,
        actual: visitor.events.as_slice(),
    );
}

fn make_many_one() -> Vec<impl Error<Details = i32>> {
    let leaf_0 = Leaf("leaf_0", 1);
    let leaf_1 = Leaf("leaf_1", 3);
    let leaf_2 = Leaf("leaf_2", 5);

    let tree_0 = Tree("tree_0", 0, vec![leaf_0]);
    let tree_1 = Tree("tree_1", 2, vec![leaf_1]);
    let tree_2 = Tree("tree_2", 4, vec![leaf_2]);

    vec![tree_0, tree_1, tree_2]
}

#[test]
fn many_one() {
    let expected = [
        Event::visit("tree_0", 0, Some("tree_1")),
        Event::Push,
        Event::visit("leaf_0", 1, None::<&str>),
        Event::Pop,
        Event::visit("tree_1", 2, Some("tree_2")),
        Event::Push,
        Event::visit("leaf_1", 3, None::<&str>),
        Event::Pop,
        Event::visit("tree_2", 4, None::<&str>),
        Event::Push,
        Event::visit("leaf_2", 5, None::<&str>),
        Event::Pop,
    ];

    let errors = make_many_one();

    let mut visitor = TestVisitor::new();
    visitor
        .visit_many(&errors)
        .continue_value()
        .expect("visit should complete");

    assert_eq!(
        expected: expected,
        actual: visitor.events.as_slice(),
    );
}

macro_rules! core_error {
    ($name:ident, $msg:literal $(,)?) => {
        core_error!(@ $name, $msg, None);
    };

    ($name:ident, $msg:literal, $source:expr $(,)?) => {
        core_error!(@ $name, $msg, Some($source));
    };

    (@ $name:ident, $msg:literal, $source:expr $(,)?) => {
        #[derive(Debug)]
        struct $name;

        impl fmt::Display for $name {
            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(f, $msg)
            }
        }

        impl CoreError for $name {
            fn source(&self) -> Option<&(dyn CoreError + 'static)> {
                $source
            }
        }
    };
}

core_error!(
    EncabulatorMalfunction,
    "encabulator malfunctioned",
    &MarzelvaneStuck,
);

core_error!(MarzelvaneStuck, "marzelvane stuck", &WaneshaftDiscombobulated);

core_error!(WaneshaftDiscombobulated, "waneshaft discombobulated");

#[test]
fn from_core() {
    let expected = [
        Event::visit("encabulator malfunctioned", (), None::<&str>),
        Event::Push,
        Event::visit("marzelvane stuck", (), None::<&str>),
        Event::Push,
        Event::visit("waneshaft discombobulated", (), None::<&str>),
        Event::Pop,
        Event::Pop,
    ];

    let error = CoreCompat::from(EncabulatorMalfunction);

    let mut visitor = TestVisitor::new();
    visitor
        .visit_many(iter::once(&error))
        .continue_value()
        .expect("visit should complete");

    assert_eq!(
        expected: expected,
        actual: visitor.events.as_slice(),
    );
}