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}