Skip to main content

gix_error/concrete/
chain.rs

1use crate::write_location;
2use std::fmt::{Debug, Display, Formatter};
3use std::panic::Location;
4use std::sync::Arc;
5
6/// A generic error which represents a linked-list of errors and exposes it with [source()](std::error::Error::source).
7/// It's meant to be the target of a conversion of any [Exn](crate::Exn) error tree.
8///
9/// It's useful for inter-op with other error handling crates like `anyhow` which offer simplified access to the error chain,
10/// and thus is expected to be wrapped in one of their types intead of being used directly.
11pub struct ChainedError {
12    /// The error exposed at this flattened frame, preserving its concrete type for downcasting.
13    pub(crate) err: ErrorHandle,
14    /// The call site captured when the corresponding error frame was created.
15    pub(crate) location: &'static Location<'static>,
16    #[cfg_attr(
17        not(all(feature = "auto-chain-error", not(feature = "tree-error"))),
18        expect(dead_code, reason = "used only by the auto-chain Error representation")
19    )]
20    /// Whether this frame was selected as the probable cause before flattening the error tree, using the root as fallback.
21    pub(crate) is_probable_cause: bool,
22    #[cfg_attr(
23        not(all(feature = "auto-chain-error", not(feature = "tree-error"))),
24        expect(dead_code, reason = "used only by the auto-chain Error representation")
25    )]
26    /// The index of this node's logical parent in the breadth-first flattened chain, or `None` for the root.
27    pub(crate) logical_parent: Option<usize>,
28    /// The next frame in the flattened error chain, kept wrapped to retain its location and subsequent frames.
29    pub(crate) source: Option<Box<ChainedError>>,
30}
31
32impl Debug for ChainedError {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        Debug::fmt(self.err.error(), f)
35    }
36}
37
38impl Display for ChainedError {
39    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40        Display::fmt(self.err.error(), f)?;
41        if !f.alternate() {
42            write_location(f, self.location)?;
43        }
44        Ok(())
45    }
46}
47
48impl std::error::Error for ChainedError {
49    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
50        // Expose the next `ChainedError`, rather than only its inner error, so standard source-chain walkers continue
51        // through the remaining flattened frames and retain each frame's location. Once that synthetic chain ends,
52        // continue with the inner error's native source chain so sources not represented by another frame remain visible.
53        self.source
54            .as_deref()
55            .map(|err| err as &(dyn std::error::Error + 'static))
56            .or_else(|| self.err.error().source())
57    }
58}
59
60/// An owning handle to either an error or one of its borrowed native sources.
61///
62/// Keeping the source-chain root in an [`Arc`] makes every source reachable for the lifetime of the flattened chain.
63/// A handle cannot store both that owner and a reference borrowed from its [`std::error::Error::source()`] chain without
64/// becoming self-referential. Instead, `source_depth` records how many `source()` links lead from `owner` to the error
65/// represented by this handle: zero represents `owner`, one represents `owner.source()`, and so on. [`Self::error()`]
66/// follows that path whenever the borrowed error is needed.
67///
68/// Resolving a handle assumes that an error's source chain remains stable while the owning error is alive, as conventional
69/// [`std::error::Error`] implementations do.
70pub(crate) struct ErrorHandle {
71    /// The error that owns the complete native source chain.
72    owner: Arc<dyn std::error::Error + Send + Sync + 'static>,
73    /// The number of [`std::error::Error::source()`] links to follow from `owner` to reach this handle's error.
74    source_depth: usize,
75}
76
77impl ErrorHandle {
78    pub(crate) fn new(error: Box<dyn std::error::Error + Send + Sync + 'static>) -> Self {
79        ErrorHandle {
80            owner: error.into(),
81            source_depth: 0,
82        }
83    }
84
85    pub(crate) fn error(&self) -> &(dyn std::error::Error + 'static) {
86        let mut error: &(dyn std::error::Error + 'static) = self.owner.as_ref();
87        for _ in 0..self.source_depth {
88            error = error
89                .source()
90                .expect("a captured source path remains stable while its owning error is alive");
91        }
92        error
93    }
94
95    pub(crate) fn source(&self) -> Option<Self> {
96        self.error().source()?;
97        Some(ErrorHandle {
98            owner: Arc::clone(&self.owner),
99            source_depth: self.source_depth + 1,
100        })
101    }
102
103    #[cfg(all(feature = "auto-chain-error", not(feature = "tree-error")))]
104    pub(crate) fn is_native_source(&self) -> bool {
105        self.source_depth > 0
106    }
107}