gix-error 0.4.0

A crate of the gitoxide project to provide common errors and error-handling utilities
Documentation
use crate::write_location;
use std::fmt::{Debug, Display, Formatter};
use std::panic::Location;
use std::sync::Arc;

/// A generic error which represents a linked-list of errors and exposes it with [source()](std::error::Error::source).
/// It's meant to be the target of a conversion of any [Exn](crate::Exn) error tree.
///
/// It's useful for inter-op with other error handling crates like `anyhow` which offer simplified access to the error chain,
/// and thus is expected to be wrapped in one of their types intead of being used directly.
pub struct ChainedError {
    /// The error exposed at this flattened frame, preserving its concrete type for downcasting.
    pub(crate) err: ErrorHandle,
    /// The call site captured when the corresponding error frame was created.
    pub(crate) location: &'static Location<'static>,

    #[cfg_attr(
        not(all(feature = "auto-chain-error", not(feature = "tree-error"))),
        expect(dead_code, reason = "used only by the auto-chain Error representation")
    )]
    /// The index of this node's logical parent in the breadth-first flattened chain, or `None` for the root.
    pub(crate) logical_parent: Option<usize>,
    /// The next frame in the flattened error chain, kept wrapped to retain its location and subsequent frames.
    pub(crate) source: Option<Box<ChainedError>>,
}

impl Debug for ChainedError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Debug::fmt(self.err.error(), f)
    }
}

impl Display for ChainedError {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        Display::fmt(self.err.error(), f)?;
        if !f.alternate() {
            write_location(f, self.location)?;
        }
        Ok(())
    }
}

impl std::error::Error for ChainedError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        // Expose the next `ChainedError`, rather than only its inner error, so standard source-chain walkers continue
        // through the remaining flattened frames and retain each frame's location. Once that synthetic chain ends,
        // continue with the inner error's native source chain so sources not represented by another frame remain visible.
        self.source
            .as_deref()
            .map(|err| err as &(dyn std::error::Error + 'static))
            .or_else(|| crate::error::native_source(self.err.error()))
    }
}

/// An owning handle to either an error or one of its borrowed native sources.
///
/// Keeping the source-chain root in an [`Arc`] makes every source reachable for the lifetime of the flattened chain.
/// A handle cannot store both that owner and a reference borrowed from its [`std::error::Error::source()`] chain without
/// becoming self-referential. Instead, `source_depth` records how many native source links, including I/O payloads,
/// lead from `owner` to the error represented by this handle. Zero represents `owner`. [`Self::error()`] follows that
/// path whenever the borrowed error is needed.
///
/// Resolving a handle assumes that an error's source chain remains stable while the owning error is alive, as conventional
/// [`std::error::Error`] implementations do.
pub(crate) struct ErrorHandle {
    /// The error that owns the complete native source chain.
    owner: Arc<dyn std::error::Error + Send + Sync + 'static>,
    /// The number of native source links, including I/O payloads, from `owner` to this handle's error.
    source_depth: usize,
    /// Whether this error retains its frame's location, directly or through transparent marker sources.
    has_frame_location: bool,
}

impl ErrorHandle {
    pub(crate) fn new(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
        ErrorHandle {
            owner: error.into(),
            source_depth: 0,
            has_frame_location: true,
        }
    }

    pub(crate) fn error(&self) -> &(dyn std::error::Error + 'static) {
        let mut error: &(dyn std::error::Error + 'static) = self.owner.as_ref();
        for _ in 0..self.source_depth {
            error = crate::error::native_source(error)
                .expect("a captured source path remains stable while its owning error is alive");
        }
        error
    }

    pub(crate) fn source(&self) -> Option<Self> {
        let error = self.error();
        crate::error::native_source(error)?;
        Some(ErrorHandle {
            owner: Arc::clone(&self.owner),
            source_depth: self.source_depth + 1,
            has_frame_location: self.has_frame_location && crate::error::is_transparent_marker(error),
        })
    }

    #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
    pub(crate) fn has_frame_location(&self) -> bool {
        self.has_frame_location
    }
}