derail 0.3.0

An alternative to `core::error::Error`.
Documentation
//! The [`Visitor`] and [`VisitorExt`] traits and related items.

use core::{fmt, ops::ControlFlow};

use crate::Error;

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

pub use ext::VisitorExt;

/// Trait for visiting [`Error`] forests.
pub trait Visitor {
    /// The [`Error::Details`] this [`Visitor`] expects.
    type Details;

    /// Called when a node is discovered in a forest.
    ///
    /// The default implementation of this method always returns
    /// [`ControlFlow::Continue`].
    #[expect(unused_variables)]
    fn visit(
        &mut self,
        visitee: &dyn Error<Details = Self::Details>,
        ctx: VisitContext<'_, Self::Details>,
    ) -> ControlFlow<()> {
        ControlFlow::Continue(())
    }

    /// Called when descending a level in a tree.
    ///
    /// The default implementation of this method always returns
    /// [`ControlFlow::Continue`].
    fn push(&mut self) -> ControlFlow<()> {
        ControlFlow::Continue(())
    }

    /// Called when ascending a level in a tree.
    ///
    /// The default implementation of this method always returns
    /// [`ControlFlow::Continue`].
    fn pop(&mut self) -> ControlFlow<()> {
        ControlFlow::Continue(())
    }
}

/// Context provided to [`Visitor::visit`].
#[non_exhaustive]
pub struct VisitContext<'a, D> {
    /// The next sibling node, if any.
    pub(crate) next_sibling: Option<&'a dyn Error<Details = D>>,
}

impl<D> VisitContext<'_, D> {
    /// Create a new, empty [`VisitContext`].
    #[must_use]
    pub(crate) fn new() -> Self {
        Self {
            next_sibling: None,
        }
    }

    /// Get the next sibling node, if any.
    #[must_use]
    pub fn next_sibling(&self) -> Option<&dyn Error<Details = D>> {
        self.next_sibling
    }
}

impl<D> fmt::Debug for VisitContext<'_, D> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Context")
            .field("next_sibling", &self.next_sibling)
            .finish_non_exhaustive()
    }
}