n0_error/
error.rs

1use std::fmt::{self, Formatter};
2
3use crate::{AnyError, Location, Meta, backtrace_enabled};
4
5/// Output style for rendering error sources in a [`Report`].
6#[derive(Debug, Copy, Clone)]
7pub enum SourceFormat {
8    /// Renders sources inline on a single line.
9    OneLine,
10    /// Renders sources on multiple lines
11    MultiLine {
12        /// If `true` include call-site info for each source.
13        location: bool,
14    },
15}
16
17/// Trait implemented by errors produced by this crate.
18///
19/// It extends `std::error::Error` semantics with optional error metadata,
20/// and a `source` method where sources may *also* provide metadata.
21pub trait StackError: fmt::Display + fmt::Debug + Send + Sync {
22    /// Returns this error as a std error reference.
23    fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static);
24
25    /// Returns this error as a std error.
26    fn into_std(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync>;
27
28    /// Returns this error as a `dyn StackError`.
29    fn as_dyn(&self) -> &dyn StackError;
30
31    /// Returns metadata captured at creation time, if available.
32    fn meta(&self) -> Option<&Meta>;
33
34    /// Returns the next source in the chain, if any.
35    fn source(&self) -> Option<ErrorRef<'_>>;
36
37    /// Returns the next source in the chain, if any.
38    fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result;
39
40    /// Returns whether this error is transparent and should be skipped in reports.
41    fn is_transparent(&self) -> bool;
42
43    /// Returns this error as an [`ErrorRef`].
44    ///
45    /// See [`ErrorRef`] for details.
46    fn as_ref(&self) -> ErrorRef<'_> {
47        ErrorRef::Stack(self.as_dyn())
48    }
49
50    /// Returns an iterator over this error followed by its sources.
51    fn stack(&self) -> Chain<'_> {
52        Chain::stack(self.as_ref())
53    }
54
55    /// Returns an iterator over sources of this error (skipping `self`).
56    fn sources(&self) -> Chain<'_> {
57        Chain::sources(self.as_ref())
58    }
59
60    /// Returns a [`Report`] to output the error with configurable formatting.
61    fn report(&self) -> Report<'_> {
62        Report::new(self.as_dyn())
63    }
64}
65
66/// Extension methods for [`StackError`]s that are [`Sized`].
67pub trait StackErrorExt: StackError + Sized + 'static {
68    /// Converts the error into [`AnyError`].
69    ///
70    /// This preserves the error's location metadata.
71    #[track_caller]
72    fn into_any(self) -> AnyError {
73        AnyError::from_stack(self)
74    }
75
76    /// Converts the error into [`AnyError`], and adds additional error context.
77    #[track_caller]
78    fn context(self, context: impl fmt::Display) -> AnyError {
79        self.into_any().context(context)
80    }
81}
82
83impl<T: StackError + Sized + 'static> StackErrorExt for T {}
84
85/// Reference to an error which can either be a std error or a stack error.
86///
87/// This provides a unified interface to either a std or a stack error. If it's a
88/// stack error it allows to access the error metadata captured at the call site.
89#[derive(Copy, Clone, Debug)]
90pub enum ErrorRef<'a> {
91    /// Std error (no location info).
92    Std(&'a (dyn std::error::Error + 'static), Option<&'a Meta>),
93    /// StackError (has location info).
94    Stack(&'a dyn StackError),
95}
96
97impl<'a> ErrorRef<'a> {
98    /// Creates a [`ErrorRef`] for a std error.
99    pub fn std(err: &'a (dyn std::error::Error + 'static)) -> ErrorRef<'a> {
100        ErrorRef::Std(err, None)
101    }
102
103    pub(crate) fn std_with_meta(
104        err: &'a (dyn std::error::Error + 'static),
105        meta: &'a Meta,
106    ) -> ErrorRef<'a> {
107        ErrorRef::Std(err, Some(meta))
108    }
109
110    /// Creates a [`ErrorRef`] for a `StackError`.
111    pub fn stack(err: &dyn StackError) -> ErrorRef<'_> {
112        ErrorRef::Stack(err)
113    }
114
115    /// Returns `true` if this error is transparent (i.e. directly forwards to its source).
116    pub fn is_transparent(&self) -> bool {
117        match self {
118            ErrorRef::Std(_, _) => false,
119            ErrorRef::Stack(error) => error.is_transparent(),
120        }
121    }
122
123    /// Returns the error as a std error.
124    pub fn as_std(self) -> &'a dyn std::error::Error {
125        match self {
126            ErrorRef::Std(error, _) => error,
127            ErrorRef::Stack(error) => error.as_std(),
128        }
129    }
130
131    /// Returns the next source in the source chain as a [`ErrorRef`].
132    pub fn source(self) -> Option<ErrorRef<'a>> {
133        match self {
134            Self::Std(error, _) => error.source().map(ErrorRef::std),
135            Self::Stack(error) => StackError::source(error),
136        }
137    }
138
139    /// Returns the location where this error was created, if available.
140    pub fn meta(&self) -> Option<&Meta> {
141        match self {
142            ErrorRef::Std(_, meta) => *meta,
143            ErrorRef::Stack(error) => error.meta(),
144        }
145    }
146
147    /// Formats the captured location, if present.
148    pub(crate) fn fmt_location(&self, f: &mut Formatter) -> fmt::Result {
149        fmt_location(self.meta().and_then(|m| m.location()), f)
150    }
151
152    /// Formats the error message.
153    pub fn fmt_message(&self, f: &mut Formatter) -> fmt::Result {
154        match self {
155            ErrorRef::Std(error, _) => fmt::Display::fmt(error, f),
156            ErrorRef::Stack(error) => error.fmt_message(f),
157        }
158    }
159
160    /// Downcast this error object by reference.
161    pub fn downcast_ref<T: std::error::Error + 'static>(self) -> Option<&'a T> {
162        match self {
163            ErrorRef::Std(error, _) => error.downcast_ref(),
164            ErrorRef::Stack(error) => error.as_std().downcast_ref(),
165        }
166    }
167}
168
169impl<'a> fmt::Display for ErrorRef<'a> {
170    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
171        match self {
172            Self::Std(error, _) => write!(f, "{error}"),
173            Self::Stack(error) => write!(f, "{error}"),
174        }
175    }
176}
177
178/// A [`Report`] customizes how an error is displayed.
179#[derive(Clone, Copy)]
180pub struct Report<'a> {
181    inner: &'a dyn StackError,
182    location: bool,
183    sources: Option<SourceFormat>,
184}
185
186impl<'a> Report<'a> {
187    pub(crate) fn new(inner: &'a dyn StackError) -> Self {
188        let location = backtrace_enabled();
189        Self {
190            inner,
191            location,
192            sources: None,
193        }
194    }
195
196    /// Enables all available details.
197    pub fn full(mut self) -> Self {
198        let location = backtrace_enabled();
199        self.location = location;
200        self.sources = Some(SourceFormat::MultiLine { location });
201        self
202    }
203
204    /// Sets whether location info is printed.
205    ///
206    /// Note that location info is only captured if environment variable
207    /// `RUST_BACKTRACE` is set to `1` or `full`.
208    pub fn location(mut self, value: bool) -> Self {
209        self.location = value;
210        self
211    }
212
213    /// Prints the error's sources.
214    pub fn sources(mut self, format: Option<SourceFormat>) -> Self {
215        self.sources = format;
216        self
217    }
218}
219
220impl<'a> fmt::Display for Report<'a> {
221    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
222        match self.sources {
223            None => {
224                let item = self
225                    .inner
226                    .stack()
227                    .find(|item| !item.is_transparent())
228                    .unwrap_or_else(|| self.inner.as_ref());
229                item.fmt_message(f)?;
230            }
231            Some(format) => {
232                let mut stack = self
233                    .inner
234                    .stack()
235                    .filter(|s| self.location || !s.is_transparent())
236                    .peekable();
237                let mut is_first = true;
238                while let Some(item) = stack.next() {
239                    match format {
240                        SourceFormat::OneLine => {
241                            if !is_first {
242                                write!(f, ": ")?;
243                            }
244                            item.fmt_message(f)?;
245                        }
246                        SourceFormat::MultiLine { location } => {
247                            if !is_first {
248                                write!(f, "    ")?;
249                            }
250                            item.fmt_message(f)?;
251                            if location {
252                                item.fmt_location(f)?;
253                            }
254                            if stack.peek().is_some() {
255                                writeln!(f)?;
256                                if is_first {
257                                    writeln!(f, "Caused by:")?;
258                                }
259                            }
260                        }
261                    }
262                    is_first = false;
263                }
264            }
265        }
266        Ok(())
267    }
268}
269
270fn fmt_location(location: Option<&Location>, f: &mut Formatter) -> fmt::Result {
271    if let Some(location) = location {
272        write!(f, " ({})", location)?;
273    }
274    Ok(())
275}
276
277/// Iterator over the sources of an error.
278pub struct Chain<'a> {
279    item: Option<ErrorRef<'a>>,
280    skip: bool,
281}
282
283impl<'a> Chain<'a> {
284    /// Creates a chain over `self` and its sources.
285    fn stack(item: ErrorRef<'a>) -> Self {
286        Self {
287            item: Some(item),
288            skip: false,
289        }
290    }
291
292    /// Creates a chain over only the sources, skipping `self`.
293    fn sources(item: ErrorRef<'a>) -> Self {
294        Self {
295            item: Some(item),
296            skip: true,
297        }
298    }
299}
300
301impl<'a> Iterator for Chain<'a> {
302    type Item = ErrorRef<'a>;
303    fn next(&mut self) -> Option<Self::Item> {
304        loop {
305            let item = self.item?;
306            self.item = item.source();
307            if self.skip {
308                self.skip = false;
309            } else {
310                return Some(item);
311            }
312        }
313    }
314}
315
316macro_rules! impl_stack_error_for_std_error {
317    ($ty:ty) => {
318        impl StackError for $ty {
319            fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
320                self
321            }
322
323            fn into_std(self: Box<Self>) -> Box<dyn std::error::Error + Send + Sync> {
324                self
325            }
326
327            fn as_dyn(&self) -> &dyn StackError {
328                self
329            }
330
331            fn meta(&self) -> Option<&Meta> {
332                None
333            }
334
335            fn source(&self) -> Option<ErrorRef<'_>> {
336                std::error::Error::source(self).map(ErrorRef::std)
337            }
338
339            fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340                fmt::Display::fmt(self, f)
341            }
342
343            fn is_transparent(&self) -> bool {
344                false
345            }
346        }
347    };
348}
349
350impl_stack_error_for_std_error!(std::io::Error);
351impl_stack_error_for_std_error!(std::fmt::Error);
352impl_stack_error_for_std_error!(std::str::Utf8Error);
353impl_stack_error_for_std_error!(std::string::FromUtf8Error);
354impl_stack_error_for_std_error!(std::net::AddrParseError);
355impl_stack_error_for_std_error!(std::array::TryFromSliceError);
356
357// impl StackError for Box<dyn StackError> {
358//     fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
359//         StackError::as_std(&**self)
360//     }
361
362//     fn as_dyn(&self) -> &dyn StackError {
363//         StackError::as_dyn(&**self)
364//     }
365
366//     fn meta(&self) -> Option<&Meta> {
367//         StackError::meta(&**self)
368//     }
369
370//     fn source(&self) -> Option<ErrorRef<'_>> {
371//         StackError::source(&**self)
372//     }
373
374//     fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
375//         StackError::fmt_message(&**self, f)
376//     }
377
378//     fn is_transparent(&self) -> bool {
379//         StackError::is_transparent(&**self)
380//     }
381// }
382
383// impl StackError for std::sync::Arc<dyn StackError> {
384//     fn as_std(&self) -> &(dyn std::error::Error + Send + Sync + 'static) {
385//         StackError::as_std(&**self)
386//     }
387
388//     fn as_dyn(&self) -> &dyn StackError {
389//         StackError::as_dyn(&**self)
390//     }
391
392//     fn meta(&self) -> Option<&Meta> {
393//         StackError::meta(&**self)
394//     }
395
396//     fn source(&self) -> Option<ErrorRef<'_>> {
397//         StackError::source(&**self)
398//     }
399
400//     fn fmt_message(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401//         StackError::fmt_message(&**self, f)
402//     }
403
404//     fn is_transparent(&self) -> bool {
405//         StackError::is_transparent(&**self)
406//     }
407// }