Skip to main content

oopsie_core/
diagnostic.rs

1//! Simple trait for errors that expose diagnostic data.
2
3use alloc::boxed::Box;
4use alloc::sync::Arc;
5
6use crate::{Backtrace, ErrorCode, HelpText, SpanTrace};
7
8/// Trait for errors that expose diagnostic data.
9///
10/// Always implemented by `#[derive(Oopsie)]`. Provides a stable mechanism
11/// for extracting backtraces, span traces, error codes, and help text
12/// without relying on the unstable `Provide`/`Request` API.
13pub trait Diagnostic: core::error::Error {
14    /// Returns the backtrace captured when this error was created.
15    #[inline]
16    fn oopsie_backtrace(&self) -> Option<&Backtrace> {
17        None
18    }
19
20    /// Returns the span trace captured when this error was created.
21    #[inline]
22    fn oopsie_spantrace(&self) -> Option<&SpanTrace> {
23        None
24    }
25
26    /// Returns the caller location captured when this error was created.
27    #[inline]
28    fn oopsie_location(&self) -> Option<&'static core::panic::Location<'static>> {
29        None
30    }
31
32    /// Returns the error code associated with this error.
33    #[inline]
34    fn oopsie_error_code(&self) -> Option<ErrorCode> {
35        None
36    }
37
38    /// Returns the help text associated with this error.
39    #[inline]
40    fn oopsie_help_text(&self) -> Option<HelpText> {
41        None
42    }
43
44    /// Returns the process exit code this error should terminate with.
45    ///
46    /// Honored by `Report`'s [`Termination`](std::process::Termination) impl on
47    /// stable. `NonZeroU8` is what [`ExitCode::from`](std::process::ExitCode)
48    /// accepts portably, and zero would denote success on a path that is, by
49    /// construction, a failure.
50    #[inline]
51    fn oopsie_exit_code(&self) -> Option<core::num::NonZeroU8> {
52        None
53    }
54}
55
56impl<T: Diagnostic> Diagnostic for Box<T> {
57    #[inline]
58    fn oopsie_backtrace(&self) -> Option<&Backtrace> {
59        (**self).oopsie_backtrace()
60    }
61
62    #[inline]
63    fn oopsie_spantrace(&self) -> Option<&SpanTrace> {
64        (**self).oopsie_spantrace()
65    }
66
67    #[inline]
68    fn oopsie_location(&self) -> Option<&'static core::panic::Location<'static>> {
69        (**self).oopsie_location()
70    }
71
72    #[inline]
73    fn oopsie_error_code(&self) -> Option<ErrorCode> {
74        (**self).oopsie_error_code()
75    }
76
77    #[inline]
78    fn oopsie_help_text(&self) -> Option<HelpText> {
79        (**self).oopsie_help_text()
80    }
81
82    #[inline]
83    fn oopsie_exit_code(&self) -> Option<core::num::NonZeroU8> {
84        (**self).oopsie_exit_code()
85    }
86}
87
88// `Rc<T>` is intentionally absent: `std` provides `Error` for `Box`/`Arc` but
89// not `Rc`, so `Rc<T>` cannot satisfy this trait's `Error` supertrait — and an
90// `Rc` source can't satisfy `Error::source`'s return type either.
91impl<T: Diagnostic> Diagnostic for Arc<T> {
92    #[inline]
93    fn oopsie_backtrace(&self) -> Option<&Backtrace> {
94        (**self).oopsie_backtrace()
95    }
96
97    #[inline]
98    fn oopsie_spantrace(&self) -> Option<&SpanTrace> {
99        (**self).oopsie_spantrace()
100    }
101
102    #[inline]
103    fn oopsie_location(&self) -> Option<&'static core::panic::Location<'static>> {
104        (**self).oopsie_location()
105    }
106
107    #[inline]
108    fn oopsie_error_code(&self) -> Option<ErrorCode> {
109        (**self).oopsie_error_code()
110    }
111
112    #[inline]
113    fn oopsie_help_text(&self) -> Option<HelpText> {
114        (**self).oopsie_help_text()
115    }
116
117    #[inline]
118    fn oopsie_exit_code(&self) -> Option<core::num::NonZeroU8> {
119        (**self).oopsie_exit_code()
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126    use crate::Capturable;
127    use std::fmt;
128
129    #[derive(Debug)]
130    struct Src {
131        backtrace: Backtrace,
132        spantrace: SpanTrace,
133    }
134
135    impl fmt::Display for Src {
136        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
137            f.write_str("src")
138        }
139    }
140
141    impl std::error::Error for Src {}
142
143    impl Diagnostic for Src {
144        fn oopsie_backtrace(&self) -> Option<&Backtrace> {
145            Some(&self.backtrace)
146        }
147
148        fn oopsie_spantrace(&self) -> Option<&SpanTrace> {
149            Some(&self.spantrace)
150        }
151    }
152
153    #[test]
154    fn box_delegates_diagnostic_accessors() {
155        let src = Src {
156            backtrace: Backtrace::capture(),
157            spantrace: SpanTrace::capture(),
158        };
159        let boxed = Box::new(src);
160        assert!(boxed.oopsie_backtrace().is_some());
161        assert!(boxed.oopsie_spantrace().is_some());
162    }
163
164    #[test]
165    fn box_diagnostic_extraction_reuses_source_frames() {
166        use crate::{RustBacktrace, with_rust_backtrace_override};
167
168        with_rust_backtrace_override(RustBacktrace::Enabled, || {
169            let src = Src {
170                backtrace: Backtrace::capture(),
171                spantrace: SpanTrace::capture(),
172            };
173            let expected_frames = src.backtrace.frames().len();
174            assert!(
175                expected_frames > 0,
176                "backtrace must be enabled for this test to be probative"
177            );
178            let boxed = Box::new(src);
179            let extracted = <(Backtrace, SpanTrace) as Capturable>::capture_or_extract(&boxed);
180            assert_eq!(extracted.0.frames().len(), expected_frames);
181        });
182    }
183
184    #[test]
185    fn arc_delegates_diagnostic_accessors() {
186        let src = Src {
187            backtrace: Backtrace::capture(),
188            spantrace: SpanTrace::capture(),
189        };
190        let arced = Arc::new(src);
191        assert!(arced.oopsie_backtrace().is_some());
192        assert!(arced.oopsie_spantrace().is_some());
193    }
194
195    #[test]
196    fn arc_diagnostic_extraction_reuses_source_frames() {
197        use crate::{RustBacktrace, with_rust_backtrace_override};
198
199        with_rust_backtrace_override(RustBacktrace::Enabled, || {
200            let src = Src {
201                backtrace: Backtrace::capture(),
202                spantrace: SpanTrace::capture(),
203            };
204            let expected_frames = src.backtrace.frames().len();
205            assert!(
206                expected_frames > 0,
207                "backtrace must be enabled for this test to be probative"
208            );
209            let arced = Arc::new(src);
210            let extracted = <(Backtrace, SpanTrace) as Capturable>::capture_or_extract(&arced);
211            assert_eq!(extracted.0.frames().len(), expected_frames);
212        });
213    }
214}