Skip to main content

oopsie_core/
diagnostic.rs

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