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}