derail 0.3.0

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

use core::{error::Error as CoreError, fmt, iter, ops::ControlFlow};

use crate::{VisitContext, Visitor, VisitorExt as _};

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

pub use ext::ErrorExt;

/// Trait for types that represent an error condition.
///
/// # Implementing
///
/// In order for [`Visitor`]s and [`Error`]s to work correctly, [`Error`]s must
/// follow several rules in their implementations of various trait methods. It
/// is recommended to use the derive macro to implement these trait methods, as
/// it will ensure these rules are followed.
///
/// It is considered a logic error to implement these trait methods such that
/// these rules are violated. Therefore, `unsafe` code must not rely on the
/// correctness of implementations of this trait.
///
/// The rules for each trait method are described below.
///
/// ## [`Debug::fmt`]
///
/// There are no additional requirements on the implementation of this method.
///
/// ## [`Display::fmt`]
///
/// If this [`Error`] value will be [skipped], its implementation of
/// [`Display::fmt`] must forward to the child, which will follow these rules
/// itself. Otherwise, the below rules must be followed.
///
/// The text written should:
///
/// * Describe the error condition.
/// * Be concise.
/// * Start with a lowercase letter, except in certain situations, such as if an
///   initialism or acronym appears at the start of the text.
///
/// The text written should not:
///
/// * Contain trailing punctuation.
/// * Contain newlines.
/// * Describe the error condition(s) that caused this error condition, if any.
///   Instead, such information should be provided by implementing
///   [`Error::accept`] such that these error conditions are visited as children
///   of this [`Error`].
///
/// ## [`Error::accept`]
///
/// If any [`Visitor`] method returns [`ControlFlow::Break`], further calls to
/// [`Visitor`] methods must not be made for the remainder of the current method
/// call. This also applies to [`VisitorExt`] methods that call [`Visitor`]
/// methods internally.
///
/// Each [`Error`] value must implement exactly one of the following behaviors.
/// Typically, the behavior is chosen on a per-`struct` and per-`enum`-variant
/// basis. The chosen behavior must match that of [`Error::details`].
///
/// ### Exactly zero children
///
/// 1. Call [`Visitor::visit`], passing `self` to `visitee` and `ctx` to `ctx`.
///
/// ### Zero or more children
///
/// 1. Call [`Visitor::visit`], passing `self` to `visitee` and `ctx` to `ctx`.
/// 2. If `self` has at least one child:
///    1. Call [`Visitor::push`].
///    2. Call [`VisitorExt::visit_many`] or [`VisitorExt::visit_map_many`],
///       passing the children to `errors`.
///    3. Call [`Visitor::pop`].
///
/// ### Exactly one child, skipping `self`
///
/// 1. Call [`VisitorExt::visit_many`] or [`VisitorExt::visit_map_many`],
///    passing a single-item iterator yielding the child to `errors`.
///
/// In this situation, `self`'s [`Display::fmt`] implementation must forward to
/// the child's.
///
/// ### Exactly one child, skipping the child
///
/// 1. Call [`Visitor::visit`], passing `self` to `visitee` and `ctx` to `ctx`.
/// 2. If the child has at least one child ([`ErrorExt::has_children`] can be
///    used for detecting this case):
///    1. Call [`Visitor::push`].
///    2. Call [`VisitorExt::visit_children_of`] or
///       [`VisitorExt::visit_map_children_of`], passing the child to `error`.
///    3. Call [`Visitor::pop`].
///
/// ## [`Error::details`]
///
/// Each [`Error`] value must implement exactly one of the following behaviors.
/// Typically, the behavior is chosen on a per-`struct` and per-`enum`-variant
/// basis. The chosen behavior must match that of [`Error::accept`].
///
/// ### Exactly zero children
///
/// 1. Return `self`'s details.
///
/// ### Zero or more children
///
/// 1. Return `self`'s details.
///
/// ### Exactly one child, skipping `self`
///
/// 1. If [`VisitorExt::visit_many`] is used for this child in the corresponding
///    [`Error::accept`] implementation, return the value obtained by calling
///    [`Error::details`] on the child. If [`VisitorExt::visit_map_many`] is
///    used for this child in the corresponding [`Error::accept`]
///    implementation, call [`Error::details`] on the child and pass the value
///    to the function passed to `map_details` in the
///    [`VisitorExt::visit_map_many`] call and return the resulting value.
///
/// ### Exactly one child, skipping the child
///
/// 1. Return `self`'s details.
///
/// [`Debug::fmt`]: fmt::Debug::fmt
/// [`Display::fmt`]: fmt::Display::fmt
/// [`Display`]: fmt::Display
/// [`VisitorExt::visit_children_of`]: crate::VisitorExt::visit_children_of
/// [`VisitorExt::visit_many`]: crate::VisitorExt::visit_many
/// [`VisitorExt::visit_map_children_of`]: crate::VisitorExt::visit_map_children_of
/// [`VisitorExt::visit_map_many`]: crate::VisitorExt::visit_map_many
/// [`VisitorExt`]: crate::VisitorExt
/// [skipped]: Error#exactly-one-child-skipping-self
pub trait Error: fmt::Debug + fmt::Display {
    /// Type that contains details about this error.
    ///
    /// This can be used for providing additional information about an [`Error`]
    /// such as backtraces, help text, application-specific error codes, and
    /// so on.
    ///
    /// For [`Error`]s that have no children, consider making a backtrace
    /// available through [`Error::details`]. Note that a backtrace should be
    /// captured when an [`Error`] is created, not when [`Error::details`] is
    /// called: the former would point to the location where the [`Error`] was
    /// *caused*, and the latter would point to the location where the [`Error`]
    /// was *being inspected*.
    ///
    /// # Design shortcomings
    ///
    /// Ideally, this would be generic over the lifetime of `self` so that
    /// values returned from [`details`](Error::details) had the *option* of
    /// borrowing from `self`. Unfortunately, [traits with generic associated
    /// types are not `dyn`-compatible at the moment][0], and this trait is
    /// designed to be `dyn`-compatible. In the meantime, try to choose or
    /// create concrete types for this associated type whose contents are
    /// [`Copy`] or cheap to [`Clone`] (e.g. via reference-counting pointers).
    ///
    /// [0]: https://github.com/rust-lang/rust/issues/81823
    type Details;

    /// Accept a [`Visitor`], causing it to traverse `self`'s tree.
    fn accept(
        &self,
        visitor: &mut dyn Visitor<Details = Self::Details>,
        ctx: VisitContext<'_, Self::Details>,
    ) -> ControlFlow<()>;

    /// Get this [`Error`]'s details.
    ///
    /// See the documentation of [`Error::Details`] for more information.
    fn details(&self) -> Self::Details;
}

/// Wraps a [`core::error::Error`] type so that it implements [`Error`].
///
/// This type's [`Error::accept`] implementation will visit `self` and its
/// children by calling [`core::error::Error::source`] recursively, and its
/// [`Display::fmt`] implementation directly calls `E`'s [`Display::fmt`]
/// implementation.
///
/// [`Display::fmt`]: fmt::Display::fmt
#[derive(Debug, Clone, Copy)]
pub struct CoreCompat<E>(pub E);

impl<E> fmt::Display for CoreCompat<E>
where
    E: fmt::Display,
{
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        self.0.fmt(f)
    }
}

impl<E> Error for CoreCompat<E>
where
    E: CoreError,
{
    type Details = ();

    fn accept(
        &self,
        mut visitor: &mut dyn Visitor<Details = Self::Details>,
        ctx: VisitContext<'_, Self::Details>,
    ) -> ControlFlow<()> {
        visitor.visit(self, ctx)?;

        if let Some(source) = self.0.source() {
            visitor.push()?;
            visitor.visit_many(iter::once(&CoreCompat(source)))?;
            visitor.pop()?;
        }

        ControlFlow::Continue(())
    }

    fn details(&self) -> Self::Details {}
}

impl<E> From<E> for CoreCompat<E>
where
    E: CoreError,
{
    fn from(other: E) -> Self {
        CoreCompat(other)
    }
}