Skip to main content

ohno/
error_ext.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use std::backtrace::Backtrace as StdBacktrace;
5use std::error::Error as StdError;
6
7/// Extension trait providing additional functionality for ohno error types.
8///
9/// This trait is automatically implemented by `#[derive(Error)]` and `#[ohno::error]`.
10/// It provides convenient methods for error handling, backtrace access, and error chain traversal.
11///
12/// # Examples
13///
14/// ```rust
15/// use ohno::ErrorExt;
16///
17/// #[ohno::error]
18/// #[from(std::io::Error)]
19/// struct NetworkError;
20///
21/// #[ohno::error]
22/// #[from(NetworkError)]
23/// struct ServiceError;
24///
25/// let io_error = std::io::Error::new(std::io::ErrorKind::ConnectionAborted, "connection aborted");
26/// let network_err = NetworkError::from(io_error);
27/// let service_err = ServiceError::from(network_err);
28/// let io_error = service_err.find_source::<std::io::Error>().unwrap();
29/// ```
30pub trait ErrorExt: StdError {
31    /// Returns the formatted error message without backtrace.
32    ///
33    /// Provides a clean, user-friendly error message excluding backtrace information.
34    /// Ideal for user interfaces, logs, or when backtrace details are not needed.
35    fn message(&self) -> String;
36
37    /// Returns a reference to the captured backtrace.
38    ///
39    /// Provides access to the stack trace captured when the error was created.
40    /// Use [`has_backtrace()`](Self::has_backtrace) to check if backtrace was captured.
41    ///
42    /// # Backtrace Capture
43    ///
44    /// Controlled by environment variables:
45    /// - `RUST_BACKTRACE=1` enables basic backtrace
46    /// - `RUST_BACKTRACE=full` enables full backtrace with all frames
47    fn backtrace(&self) -> &StdBacktrace;
48
49    /// Returns `true` if the error has a captured backtrace.
50    ///
51    /// Convenience method equivalent to checking if backtrace status is [`Captured`](std::backtrace::BacktraceStatus::Captured).
52    fn has_backtrace(&self) -> bool {
53        self.backtrace().status() == std::backtrace::BacktraceStatus::Captured
54    }
55
56    /// Finds the first source error of the specified type in the error chain.
57    ///
58    /// Walks through the error's source chain and returns the first error that matches type `T`.
59    /// Only searches the **source chain**, not the current error itself.
60    fn find_source<T: StdError + 'static>(&self) -> Option<&T> {
61        self.find_source_with(|_| true)
62    }
63
64    /// Finds the first source error of the specified type that matches the given predicate.
65    ///
66    /// Walks through the error's source chain and returns the first error that matches type `T`
67    /// and satisfies the provided search predicate. Only searches the **source chain**, not the
68    /// current error itself.
69    fn find_source_with<T: StdError + 'static>(&self, search: impl Fn(&T) -> bool) -> Option<&T> {
70        let mut source = self.source();
71        while let Some(err) = source {
72            if let Some(target) = err.downcast_ref::<T>()
73                && search(target)
74            {
75                return Some(target);
76            }
77            source = err.source();
78        }
79        None
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use std::backtrace::BacktraceStatus;
86
87    use super::*;
88    use crate::backtrace::Backtrace;
89
90    #[ohno::error]
91    struct TestError;
92
93    #[cfg_attr(miri, ignore)] // unsupported operation: `GetCurrentDirectoryW` not available when isolation is enabled
94    #[test]
95    fn force_backtrace_capture() {
96        let mut error = TestError::new();
97        error.0.data.backtrace = Backtrace::force_capture();
98
99        assert!(error.has_backtrace());
100        let backtrace = error.backtrace();
101        assert!(backtrace.status() == BacktraceStatus::Captured);
102        let display = format!("{error}");
103        assert!(display.starts_with("TestError\n\nBacktrace:\n"));
104    }
105
106    #[test]
107    fn no_backtrace_capture() {
108        let mut error = TestError::new();
109        error.0.data.backtrace = Backtrace::disabled();
110        assert!(!error.has_backtrace());
111        assert!(error.backtrace().status() == BacktraceStatus::Disabled);
112        let display = format!("{error}");
113        assert_eq!(display, "TestError");
114    }
115}