n0_error/
error.rs

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