eros/
generic_error.rs

1#[cfg(feature = "backtrace")]
2use std::backtrace::Backtrace;
3use std::fmt::{self};
4
5use crate::{str_error::StrError, ErrorUnion};
6
7/// Any error that satisfies this trait's bounds can be used in a `TracedError`
8pub trait AnyError: std::any::Any + std::error::Error + Send + Sync + 'static {}
9
10impl<T> AnyError for T where T: std::error::Error + Send + Sync + 'static {}
11
12impl std::error::Error for Box<dyn AnyError> {}
13
14/// `TracedError` allows adding context to an error throughout the callstack with the `context` or `with_context` methods.
15/// This context may be information such as variable values or ongoing operations while the error occurred.
16/// If the error is handled higher in the stack, then this can be disregarded (no log pollution).
17/// Otherwise you can log it (or panic), capturing all the relevant information in one log.
18///
19/// A backtrace is captured and added to the log if `RUST_BACKTRACE` is set.
20///
21/// Use `TracedError` if the underlying error type does not matter.
22/// Otherwise, the type can be specified with `TracedError<T>`.
23pub struct TracedError<T = Box<dyn AnyError>>
24where
25    T: AnyError,
26{
27    inner: T,
28    #[cfg(feature = "backtrace")]
29    pub(crate) backtrace: Backtrace,
30    #[cfg(feature = "context")]
31    pub(crate) context: Vec<StrError>,
32}
33
34impl TracedError {
35    /// Create a dynamic type erased `TracedError`
36    pub fn boxed<E: AnyError>(source: E) -> Self {
37        TracedError::new(Box::new(source))
38    }
39}
40
41impl<T: AnyError> TracedError<T> {
42    pub fn new(source: T) -> Self {
43        Self {
44            inner: source,
45            #[cfg(feature = "backtrace")]
46            backtrace: Backtrace::capture(),
47            #[cfg(feature = "context")]
48            context: Vec::new(),
49        }
50    }
51
52    /// Converts these `TracedError` into dynamic type erased `TracedError`
53    pub fn traced_dyn(self) -> TracedError {
54        debug_assert!(
55            std::any::TypeId::of::<T>() != std::any::TypeId::of::<Box<dyn AnyError>>(),
56            "traced_dyn() called on already boxed TracedError"
57        );
58
59        TracedError {
60            inner: Box::new(self.inner),
61            #[cfg(feature = "backtrace")]
62            backtrace: self.backtrace,
63            #[cfg(feature = "context")]
64            context: self.context,
65        }
66    }
67
68    /// Converts into the inner type
69    pub fn into_inner(self) -> T {
70        self.inner
71    }
72
73    /// Gets a reference to the inner type
74    pub fn inner(&self) -> &T {
75        &self.inner
76    }
77
78    /// Gets a mutable reference to the inner type
79    pub fn inner_mut(&mut self) -> &mut T {
80        &mut self.inner
81    }
82
83    /// Maps the inner error to another while preserving context and backtrace
84    pub fn map<U, F>(self, f: F) -> TracedError<U>
85    where
86        U: AnyError,
87        F: FnOnce(T) -> U,
88    {
89        TracedError {
90            inner: f(self.inner),
91            #[cfg(feature = "backtrace")]
92            backtrace: self.backtrace,
93            #[cfg(feature = "context")]
94            context: self.context,
95        }
96    }
97
98    /// Adds additional context. This becomes a no-op if the `traced` feature is disabled.
99    #[allow(unused_mut)]
100    #[allow(unused_variables)]
101    pub fn context<C: Into<StrError>>(mut self, context: C) -> Self {
102        #[cfg(feature = "context")]
103        self.context.push(context.into());
104        self
105    }
106
107    /// Adds additional context lazily. This becomes a no-op if the `traced` feature is disabled.
108    #[allow(unused_mut)]
109    #[allow(unused_variables)]
110    pub fn with_context<F, C: Into<StrError>>(mut self, f: F) -> TracedError<T>
111    where
112        F: FnOnce() -> C,
113    {
114        #[cfg(feature = "context")]
115        self.context.push(f().into());
116        self
117    }
118
119    // Note: Even though `std::error::Error` is implemented for Deref.
120    // We still redeclare `source` here to tie the lifetime to this,
121    // rather than another deref
122    /// Returns the lower-level source of this error, if any.
123    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
124        self.inner.source()
125    }
126
127    // pub fn into_parts(self) -> (T, ErrorTrace) {
128    //     let Self {
129    //         inner,
130    //         backtrace,
131    //         context,
132    //     } = self;
133    //     (
134    //         inner,
135    //         ErrorTrace {
136    //             #[cfg(feature = "backtrace")]
137    //             backtrace,
138    //             #[cfg(feature = "context")]
139    //             context,
140    //         },
141    //     )
142    // }
143
144    // pub fn from_parts(inner: T, error_trace: ErrorTrace) -> TracedError<T> {
145    //     TracedError {
146    //         inner,
147    //         #[cfg(feature = "backtrace")]
148    //         backtrace: error_trace.backtrace,
149    //         #[cfg(feature = "context")]
150    //         context: error_trace.context,
151    //     }
152    // }
153}
154
155//************************************************************************//
156
157#[cfg(feature = "anyhow")]
158impl TracedError {
159    pub fn anyhow(error: anyhow::Error) -> TracedError {
160        let mut chain = error.chain();
161        let root = chain.next().unwrap().to_string();
162        #[cfg(feature = "backtrace")]
163        let (root, backtrace) = {
164            let backtrace: &Backtrace = error.backtrace();
165            if matches!(
166                backtrace.status(),
167                std::backtrace::BacktraceStatus::Captured
168            ) {
169                // Since we cannot get a `Backtrace` from a `&Backtrace`, we add it to root instead
170                (
171                    format!("{root}\n\nBacktrace:\n{}", backtrace.to_string()),
172                    Backtrace::disabled(),
173                )
174            } else {
175                (root, Backtrace::capture())
176            }
177        };
178        let root = StrError::Owned(root);
179        #[cfg(feature = "context")]
180        let context = {
181            let mut context = Vec::new();
182            for link in chain {
183                context.push(StrError::Owned(link.to_string()));
184            }
185            context
186        };
187        TracedError {
188            inner: Box::new(root),
189            #[cfg(feature = "backtrace")]
190            backtrace,
191            #[cfg(feature = "context")]
192            context,
193        }
194    }
195}
196
197//************************************************************************//
198
199impl<T: AnyError> fmt::Display for TracedError<T> {
200    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
201        write!(formatter, "{}", self.inner)?;
202        Ok(())
203    }
204}
205
206impl<T: AnyError> fmt::Debug for TracedError<T> {
207    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
208        write!(formatter, "{}", self.inner)?;
209        #[cfg(feature = "context")]
210        {
211            if !self.context.is_empty() {
212                write!(formatter, "\n\nContext:")?;
213                for context_item in self.context.iter() {
214                    write!(formatter, "\n\t- {}", context_item)?;
215                }
216            }
217        }
218        #[cfg(feature = "backtrace")]
219        {
220            use std::backtrace::BacktraceStatus;
221
222            if matches!(self.backtrace.status(), BacktraceStatus::Captured) {
223                write!(formatter, "\n\nBacktrace:\n")?;
224                fmt::Display::fmt(&self.backtrace, formatter)?;
225            }
226        }
227        Ok(())
228    }
229}
230
231// Into `TracedError`
232//************************************************************************//
233
234fn _send_sync_error_assert() {
235    fn is_send<T: Send>(_: &T) {}
236    fn is_sync<T: Sync>(_: &T) {}
237    fn is_error<T: std::error::Error>(_: &T) {}
238
239    let traced_error: TracedError = crate::traced!("");
240    is_send(&traced_error);
241    is_sync(&traced_error);
242    // is_error(&&traced_error);
243    is_error(&&traced_error);
244}
245
246// Note: This is not implemented so something like `TracedError<TracedError<T>>` is not possible.
247// Also this allows us to implement `Context` on `E: AnyError` since it does not conflict with itself.
248// e.g. In `impl<E: AnyError> Context<TracedError<E>> for E` `TracedError<E>` cannot be `E`.
249// Thus `context` can be called directly with no `traced()`/`traced_dyn()` call.
250// impl<T: AnyError> std::error::Error for TracedError<T> {
251//     fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
252//         self.inner.source()
253//     }
254// }
255
256impl<T: AnyError> std::error::Error for &TracedError<T> {
257    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
258        self.inner.source()
259    }
260}
261
262impl<T: AnyError> std::error::Error for &mut TracedError<T> {
263    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
264        self.inner.source()
265    }
266}
267
268//************************************************************************//
269
270// impl<T: AnyError> From<T> for TracedError<T> {
271//     fn from(e: T) -> Self {
272//         TracedError::new(e)
273//     }
274// }
275
276impl<T: AnyError> From<T> for TracedError {
277    #[cfg(feature = "min_specialization")]
278    default fn from(e: T) -> Self {
279        TracedError::boxed(e)
280    }
281
282    #[cfg(not(feature = "min_specialization"))]
283    fn from(e: T) -> Self {
284        TracedError::boxed(e)
285    }
286}
287
288#[cfg(feature = "min_specialization")]
289impl From<Box<dyn AnyError + '_>> for TracedError {
290    fn from(e: Box<dyn AnyError>) -> Self {
291        TracedError::new(e)
292    }
293}
294
295//************************************************************************//
296
297/// Into a type with a dynamic [`TracedError`]
298pub trait TracedDyn<O2> {
299    /// Convert Error to `TracedError` without caring about the underlying type
300    fn traced_dyn(self) -> O2;
301}
302
303impl<E> TracedDyn<TracedError> for E
304where
305    E: AnyError,
306{
307    #[cfg(feature = "min_specialization")]
308    default fn traced_dyn(self) -> TracedError {
309        TracedError::new(Box::new(self))
310    }
311
312    #[cfg(not(feature = "min_specialization"))]
313    fn traced_dyn(self) -> TracedError {
314        TracedError::new(Box::new(self))
315    }
316}
317
318#[cfg(feature = "min_specialization")]
319impl TracedDyn<TracedError> for Box<dyn AnyError + '_> {
320    fn traced_dyn(self) -> TracedError {
321        TracedError::new(self)
322    }
323}
324
325impl<S, E> TracedDyn<Result<S, TracedError>> for Result<S, E>
326where
327    E: AnyError,
328{
329    fn traced_dyn(self) -> Result<S, TracedError> {
330        self.map_err(|e| e.traced_dyn())
331    }
332}
333
334impl<S, E: AnyError> TracedDyn<Result<S, TracedError>> for Result<S, TracedError<E>> {
335    #[cfg(feature = "min_specialization")]
336    default fn traced_dyn(self) -> Result<S, TracedError> {
337        self.map_err(|e| e.traced_dyn())
338    }
339
340    #[cfg(not(feature = "min_specialization"))]
341    fn traced_dyn(self) -> Result<S, TracedError> {
342        self.map_err(|e| e.traced_dyn())
343    }
344}
345
346#[cfg(feature = "min_specialization")]
347impl<S> TracedDyn<Result<S, TracedError<Box<dyn AnyError + '_>>>>
348    for Result<S, TracedError<Box<dyn AnyError + '_>>>
349{
350    fn traced_dyn(self) -> Result<S, TracedError> {
351        self
352    }
353}
354
355//************************************************************************//
356
357/// Into a type with a concrete [`TracedError<E>`] without mapping `E`, see also [`IntoTraced`]
358pub trait Traced<O1> {
359    /// Convert Error to a type containing a [`TracedError`] keeping the underlying type
360    fn traced(self) -> O1;
361}
362
363impl<E> Traced<TracedError<E>> for E
364where
365    E: AnyError,
366{
367    fn traced(self) -> TracedError<E> {
368        TracedError::new(self)
369    }
370}
371impl<S, E> Traced<Result<S, TracedError<E>>> for Result<S, E>
372where
373    E: AnyError,
374{
375    fn traced(self) -> Result<S, TracedError<E>> {
376        self.map_err(|e| e.traced())
377    }
378}
379
380// impl<S, E> Traced<Result<S, TracedError<E>>> for Result<S, TracedError<E>>
381// where
382//     E: AnyError,
383// {
384//     fn traced(self) -> Result<S, TracedError<E>> {
385//         self
386//     }
387// }
388
389impl<S, E> Traced<Result<S, TracedError<E>>> for Result<S, ErrorUnion<(TracedError<E>,)>>
390where
391    E: AnyError,
392{
393    fn traced(self) -> Result<S, TracedError<E>> {
394        self.map_err(|e| e.into_inner())
395    }
396}
397
398/// Into a type with a concrete [`TracedError<E2>`] mapping to `E2` from `E1`, see also [`Traced`]
399// Dev Note: We cannot fully replace [`Traced`] with this since with multiple chains -
400// ```rust
401// error
402//   .into_traced()
403//   .context("Some context")
404//   .union()?;
405// ```
406// The target type becomes undeterminable for the compiler.
407pub trait IntoTraced<O1> {
408    /// Convert Error to a type containing a [`TracedError`] mapping the underlying type
409    fn into_traced(self) -> O1;
410}
411
412impl<E1, E2> IntoTraced<TracedError<E2>> for E1
413where
414    E1: AnyError,
415    E2: AnyError,
416    E1: Into<E2>,
417{
418    fn into_traced(self) -> TracedError<E2> {
419        TracedError::new(self.into())
420    }
421}
422impl<S, E1, E2> IntoTraced<Result<S, TracedError<E2>>> for Result<S, E1>
423where
424    E1: AnyError,
425    E2: AnyError,
426    E1: Into<E2>,
427{
428    fn into_traced(self) -> Result<S, TracedError<E2>> {
429        self.map_err(|e| e.into_traced())
430    }
431}
432
433impl<S, E1, E2> IntoTraced<Result<S, TracedError<E2>>> for Result<S, TracedError<E1>>
434where
435    E1: AnyError,
436    E2: AnyError,
437    E1: Into<E2>,
438{
439    fn into_traced(self) -> Result<S, TracedError<E2>> {
440        self.map_err(|e| e.map(|e| e.into()))
441    }
442}
443
444impl<S, E> IntoTraced<Result<S, TracedError<E>>> for Result<S, ErrorUnion<(TracedError<E>,)>>
445where
446    E: AnyError,
447{
448    fn into_traced(self) -> Result<S, TracedError<E>> {
449        self.map_err(|e| e.into_inner())
450    }
451}
452
453//************************************************************************//
454
455// /// An opaque type holding both the context and the backtrace derived from a [`TracedError`].
456// // Dev Not: This is needed since something something like
457// // ```rust
458// // impl<T, U> From<TracedError<T>> for TracedError<U>
459// // where
460// //     T: Into<U>,
461// //     U: AnyError,
462// // {
463// //     fn from(err: TracedError<T>) -> Self {
464// //         TracedError {
465// //             inner: err.inner.into(),
466// //             #[cfg(feature = "backtrace")]
467// //             backtrace: err.backtrace,
468// //             #[cfg(feature = "context")]
469// //             context: err.context,
470// //         }
471// //     }
472// // }
473// // ```
474// // is not possible since it conflicts with `impl<T> From<T> for T;` from core.
475// // We should leave this opaque for backward compatible api considerations.
476// #[derive(Debug)]
477// pub struct ErrorTrace {
478//     #[cfg(feature = "backtrace")]
479//     backtrace: Backtrace,
480//     #[cfg(feature = "context")]
481//     context: Vec<StrError>,
482// }
483
484//************************************************************************//
485
486#[cfg(feature = "min_specialization")]
487#[cfg(all(feature = "context", feature = "backtrace"))]
488#[cfg(test)]
489mod test {
490    use crate::{Context, ErrorUnion, StrError, TracedError};
491
492    #[test]
493    fn adding_context_to_union() {
494        let concrete_traced_error: TracedError<std::io::Error> = TracedError::new(
495            std::io::Error::new(std::io::ErrorKind::AddrInUse, "Address in use"),
496        );
497        let concrete_union_error: ErrorUnion<(
498            TracedError<std::io::Error>,
499            i32,
500            TracedError<StrError>,
501        )> = ErrorUnion::new(concrete_traced_error);
502        let result: Result<
503            (),
504            ErrorUnion<(TracedError<std::io::Error>, i32, TracedError<StrError>)>,
505        > = Err(concrete_union_error).context("Context 1");
506        let concrete_union_error = result.unwrap_err();
507        let concrete_traced_error: TracedError<std::io::Error> =
508            match concrete_union_error.to_enum() {
509                crate::E3::A(traced_error) => traced_error,
510                _ => panic!("Wrong type"),
511            };
512        assert_eq!(
513            concrete_traced_error.context,
514            vec![StrError::from("Context 1")]
515        );
516    }
517}