pseudo_backtrace/
lib.rs

1#![no_std]
2#![warn(missing_docs)]
3#![doc = "README.md"]
4
5pub use pseudo_backtrace_derive::StackError;
6
7/// One layer in a stack of chained errors.
8#[derive(Debug, Clone)]
9pub enum ErrorDetail<'a> {
10    /// A stacked error
11    Stacked(&'a dyn StackError),
12    /// A [core::error::Error]. [StackError] will not backtraces any further
13    End(&'a dyn core::error::Error),
14}
15
16impl<'a> ErrorDetail<'a> {
17    /// Returns the underlying error for this stack layer.
18    pub fn source(&'a self) -> &'a dyn core::error::Error {
19        match self {
20            ErrorDetail::Stacked(stack_error) => stack_error,
21            ErrorDetail::End(error) => error,
22        }
23    }
24
25    /// Returns the recorded source location when available.
26    pub fn location(&self) -> Option<&'static core::panic::Location<'static>> {
27        match self {
28            ErrorDetail::Stacked(stack_error) => Some(stack_error.location()),
29            _ => None,
30        }
31    }
32}
33
34impl<'a, E> From<&'a E> for ErrorDetail<'a>
35where
36    E: StackError + Sized,
37{
38    fn from(stack_error: &'a E) -> Self {
39        ErrorDetail::Stacked(stack_error)
40    }
41}
42
43/// Error types that can report a stack trace-like chain.
44pub trait StackError: core::error::Error {
45    /// Returns the source location of this error.
46    fn location(&self) -> &'static core::panic::Location<'static>;
47    /// Returns the next detail in the stack.
48    fn next<'a>(&'a self) -> Option<ErrorDetail<'a>>;
49    /// Creates an iterator over this error's stack details.
50    fn iter<'a>(&'a self) -> Iter<'a>
51    where
52        Self: Sized,
53    {
54        Iter::new(self)
55    }
56}
57
58/// Iterator over individual error stack entries.
59#[derive(Debug, Clone)]
60pub struct Iter<'a> {
61    source: Option<ErrorDetail<'a>>,
62}
63
64impl<'a> Iter<'a> {
65    const fn new<E>(source: &'a E) -> Self
66    where
67        E: StackError,
68    {
69        Iter {
70            source: Some(ErrorDetail::Stacked(source)),
71        }
72    }
73}
74
75impl<'a> Iterator for Iter<'a> {
76    type Item = ErrorDetail<'a>;
77
78    fn next(&mut self) -> Option<Self::Item> {
79        match self.source.take() {
80            Some(detail) => {
81                if let ErrorDetail::Stacked(stack_error) = &detail {
82                    self.source = stack_error.next();
83                };
84                Some(detail)
85            }
86            None => None,
87        }
88    }
89}
90
91/// Wrapper that records the call-site for any `core::error::Error` and exposes it as a [`StackError`].
92///
93/// This is useful when you already have an error type that implements [`core::error::Error`] but cannot be modified to derive [`StackError`].
94///
95/// # Examples
96/// ```
97/// # extern crate std;
98/// use pseudo_backtrace::{LocatedError, StackError};
99///
100/// fn assert_stack_error<T:StackError>(){}
101///     
102/// assert_stack_error::<LocatedError<std::io::Error>>();
103/// ```
104#[derive(Debug)]
105pub struct LocatedError<E> {
106    source: E,
107    location: &'static core::panic::Location<'static>,
108}
109
110impl<E> LocatedError<E> {
111    /// Returns the inner value
112    pub fn into_inner(self) -> E {
113        self.source
114    }
115}
116
117impl<E> core::fmt::Display for LocatedError<E>
118where
119    E: core::fmt::Display,
120{
121    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
122        self.source.fmt(f)
123    }
124}
125
126impl<E> core::error::Error for LocatedError<E>
127where
128    E: core::error::Error,
129{
130    fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
131        self.source.source()
132    }
133}
134
135impl<E> StackError for LocatedError<E>
136where
137    E: core::error::Error,
138{
139    fn location(&self) -> &'static core::panic::Location<'static> {
140        self.location
141    }
142
143    fn next<'a>(&'a self) -> Option<ErrorDetail<'a>> {
144        self.source.source().map(|e| ErrorDetail::End(e))
145    }
146}
147
148impl<E> From<E> for LocatedError<E> {
149    #[track_caller]
150    fn from(value: E) -> Self {
151        LocatedError {
152            source: value,
153            location: core::panic::Location::caller(),
154        }
155    }
156}
157
158/// Formatter for a single stack layer that remembers its index.
159#[derive(Debug, Clone)]
160pub struct StackWriter<'a> {
161    layer: usize,
162    source: ErrorDetail<'a>,
163}
164
165impl<'a> StackWriter<'a> {
166    /// Returns the zero-based layer index for this entry.
167    pub fn layer(&self) -> usize {
168        self.layer
169    }
170    /// Returns the error detail captured for this layer.
171    pub fn detail(&'a self) -> ErrorDetail<'a> {
172        self.source.clone()
173    }
174}
175
176impl<'a> core::fmt::Display for StackWriter<'a> {
177    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
178        match self.source {
179            ErrorDetail::Stacked(stack_error) => {
180                write!(
181                    f,
182                    "{}: {}, at {}",
183                    self.layer,
184                    stack_error,
185                    stack_error.location()
186                )
187            }
188            ErrorDetail::End(error) => {
189                write!(f, "{}: {}", self.layer, error,)
190            }
191        }
192    }
193}
194
195/// Iterator adapter that yields formatted stack entries.
196#[derive(Debug, Clone)]
197pub struct StackChain<'a> {
198    layer: usize,
199    source: ErrorDetail<'a>,
200}
201
202impl<'a> Iterator for StackChain<'a> {
203    type Item = StackWriter<'a>;
204    fn next(&mut self) -> Option<Self::Item> {
205        if self.layer == usize::MAX {
206            return None;
207        }
208
209        let out = StackWriter {
210            layer: self.layer,
211            source: self.source.clone(),
212        };
213
214        match self.source {
215            ErrorDetail::Stacked(stack_error) => {
216                if let Some(next) = stack_error.next() {
217                    self.source = next;
218                    self.layer += 1;
219                } else {
220                    self.layer = usize::MAX;
221                }
222            }
223            ErrorDetail::End(_) => {
224                self.layer = usize::MAX;
225            }
226        }
227
228        Some(out)
229    }
230}
231
232impl<'a> core::fmt::Display for StackChain<'a> {
233    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
234        for w in self.clone() {
235            writeln!(f, "{}", w)?;
236        }
237
238        Ok(())
239    }
240}
241
242/// Convenience helpers for types implementing [`StackError`].
243pub trait StackErrorExt: StackError {
244    /// Returns a [`StackChain`] that walks this error stack from the top.
245    fn to_chain<'a>(&'a self) -> StackChain<'a>
246    where
247        Self: Sized,
248    {
249        StackChain {
250            layer: 0,
251            source: ErrorDetail::from(self),
252        }
253    }
254
255    /// Returns the deepest [`ErrorDetail`] in the chain.
256    fn last<'a>(&'a self) -> ErrorDetail<'a>
257    where
258        Self: Sized,
259    {
260        let mut detail = ErrorDetail::from(self);
261        while let Some(next) = self.next() {
262            detail = next;
263        }
264        detail
265    }
266}
267
268impl<E: StackError> StackErrorExt for E {}