Skip to main content

oopsie_core/
lib.rs

1//! Core error types and utilities.
2
3#![warn(missing_docs)]
4#![cfg_attr(
5    feature = "unstable-error-generic-member-access",
6    feature(error_generic_member_access)
7)]
8// Doctests that use `#[derive(Oopsie)]` will see the macro emit a `provide`
9// impl when the unstable feature is active; inject the corresponding language
10// feature flag so those doctests compile under `--features unstable`.
11#![cfg_attr(
12    feature = "unstable-error-generic-member-access",
13    doc(test(attr(feature(error_generic_member_access))))
14)]
15
16mod backtrace;
17mod chain;
18mod diagnostic;
19#[cfg(feature = "serde")]
20pub mod erased;
21#[cfg(feature = "extras")]
22pub mod extras;
23mod marker;
24mod spantrace;
25#[cfg(feature = "test-utils")]
26pub mod test_utils;
27mod traits;
28mod welp;
29
30use std::borrow::{Borrow, Cow};
31use std::ops::Deref;
32
33pub use backtrace::{
34    Backtrace, RustBacktrace, clear_rust_backtrace_override, rust_backtrace, rust_panic_backtrace,
35    set_rust_backtrace_override, with_rust_backtrace_override,
36};
37pub use chain::{Chain, ErrorChainExt};
38pub use diagnostic::Diagnostic;
39pub use spantrace::{OptionalSpanTrace, SpanTrace};
40#[cfg(feature = "tracing")]
41pub mod tracing;
42
43pub use traits::*;
44pub use welp::{Welp, WelpOptionExt, WelpResultExt};
45/// Private helpers used by macro-generated code. Not part of the public API.
46#[doc(hidden)]
47pub mod __private {
48    pub use crate::backtrace::CORE_SRC_PATH;
49    /// Autoref probe for capture deduplication.
50    ///
51    /// When the concrete source type implements `Diagnostic`, the high-priority
52    /// `CaptureFromExt` impl is selected and tries to extract existing traces.
53    /// For non-`Diagnostic` sources (e.g., `io::Error`), the low-priority
54    /// `CaptureFromFallback` impl is selected via autoref and does fresh capture.
55    pub struct CaptureProbe<'a, T: ?Sized>(pub &'a T);
56
57    /// High-priority: source implements `Diagnostic` → try extraction.
58    pub trait CaptureFromExt {
59        fn resolve<C: crate::Capturable>(&self) -> C;
60    }
61
62    impl<T: crate::Diagnostic> CaptureFromExt for CaptureProbe<'_, T> {
63        #[inline]
64        #[track_caller]
65        fn resolve<C: crate::Capturable>(&self) -> C {
66            C::capture_or_extract(self.0)
67        }
68    }
69
70    /// Low-priority: source doesn't implement `Diagnostic` → fresh capture.
71    pub trait CaptureFromFallback {
72        fn resolve<C: crate::Capturable>(&self) -> C;
73    }
74
75    impl<T: ?Sized> CaptureFromFallback for &CaptureProbe<'_, T> {
76        #[inline]
77        #[track_caller]
78        fn resolve<C: crate::Capturable>(&self) -> C {
79            C::capture()
80        }
81    }
82
83    /// Accessor-time autoref probe for forwarding a transparent wrapper's
84    /// `Diagnostic` accessors to its source.
85    ///
86    /// Unlike [`CaptureProbe`] (which runs at construction to decide
87    /// extract-vs-capture), this runs when `oopsie_*` is called: a transparent
88    /// variant delegates each accessor to its source's concrete type. When that
89    /// type implements `Diagnostic` the high-priority [`DiagForwardExt`] impl
90    /// forwards the call; otherwise the [`DiagForwardFallback`] impl (selected
91    /// via autoref) yields `None`. Works on stable, where the Provider-API
92    /// [`source_trace`] path would yield `None`.
93    pub struct DiagProbe<'a, T: ?Sized>(pub &'a T);
94
95    /// High-priority: source implements `Diagnostic` → forward the accessor.
96    pub trait DiagForwardExt<'a> {
97        fn fwd_code(&self) -> Option<crate::ErrorCode>;
98        fn fwd_help(&self) -> Option<crate::HelpText>;
99        fn fwd_backtrace(&self) -> Option<&'a crate::Backtrace>;
100        fn fwd_spantrace(&self) -> Option<&'a crate::SpanTrace>;
101        fn fwd_location(&self) -> Option<&'static std::panic::Location<'static>>;
102        fn fwd_exit_code(&self) -> Option<core::num::NonZeroU8>;
103    }
104
105    impl<'a, T: crate::Diagnostic + ?Sized> DiagForwardExt<'a> for DiagProbe<'a, T> {
106        #[inline]
107        fn fwd_code(&self) -> Option<crate::ErrorCode> {
108            self.0.oopsie_error_code()
109        }
110        #[inline]
111        fn fwd_help(&self) -> Option<crate::HelpText> {
112            self.0.oopsie_help_text()
113        }
114        #[inline]
115        fn fwd_backtrace(&self) -> Option<&'a crate::Backtrace> {
116            self.0.oopsie_backtrace()
117        }
118        #[inline]
119        fn fwd_spantrace(&self) -> Option<&'a crate::SpanTrace> {
120            self.0.oopsie_spantrace()
121        }
122        #[inline]
123        fn fwd_location(&self) -> Option<&'static std::panic::Location<'static>> {
124            self.0.oopsie_location()
125        }
126        #[inline]
127        fn fwd_exit_code(&self) -> Option<core::num::NonZeroU8> {
128            self.0.oopsie_exit_code()
129        }
130    }
131
132    /// Low-priority: source doesn't implement `Diagnostic` → nothing to forward.
133    pub trait DiagForwardFallback<'a> {
134        #[inline]
135        fn fwd_code(&self) -> Option<crate::ErrorCode> {
136            None
137        }
138        #[inline]
139        fn fwd_help(&self) -> Option<crate::HelpText> {
140            None
141        }
142        #[inline]
143        fn fwd_backtrace(&self) -> Option<&'a crate::Backtrace> {
144            None
145        }
146        #[inline]
147        fn fwd_spantrace(&self) -> Option<&'a crate::SpanTrace> {
148            None
149        }
150        #[inline]
151        fn fwd_location(&self) -> Option<&'static std::panic::Location<'static>> {
152            None
153        }
154        #[inline]
155        fn fwd_exit_code(&self) -> Option<core::num::NonZeroU8> {
156            None
157        }
158    }
159
160    impl<'a, T: ?Sized> DiagForwardFallback<'a> for &DiagProbe<'a, T> {}
161
162    /// Pull the deepest `T` reachable from a source error via the Provider API.
163    ///
164    /// The generated `provide()` forwards to the source before providing its
165    /// own trace, and std's `Request` is first-wins, so the deepest provider in
166    /// the chain fills the slot — this surfaces the origin-most trace rather
167    /// than a wrap-site one. Trace accessors go through the typed
168    /// [`source_backtrace`] / `source_spantrace` wrappers, which add the
169    /// skip-empty filter on top of this lookup.
170    ///
171    /// Returns `None` without `unstable-error-generic-member-access`: descending
172    /// into a type-erased `dyn Error` source is not portable there, and the
173    /// caller falls back to the wrapper's own field.
174    #[inline]
175    #[must_use]
176    pub fn source_trace<'a, T: 'static>(
177        source: &'a (dyn std::error::Error + 'static),
178    ) -> Option<&'a T> {
179        #[cfg(feature = "unstable-error-generic-member-access")]
180        {
181            core::error::request_ref::<T>(source)
182        }
183        #[cfg(not(feature = "unstable-error-generic-member-access"))]
184        {
185            let _ = source;
186            None
187        }
188    }
189
190    /// [`source_trace`] for `Backtrace`, treating an empty trace as absent:
191    /// a trace that captured no frames is not "available" and must not shadow
192    /// a captured one further out.
193    #[inline]
194    #[must_use]
195    pub fn source_backtrace<'a>(
196        source: &'a (dyn std::error::Error + 'static),
197    ) -> Option<&'a crate::Backtrace> {
198        source_trace::<crate::Backtrace>(source).filter(|bt| bt.is_captured())
199    }
200
201    /// [`source_trace`] for `SpanTrace`, treating an empty trace as absent.
202    #[inline]
203    #[must_use]
204    pub fn source_spantrace<'a>(
205        source: &'a (dyn std::error::Error + 'static),
206    ) -> Option<&'a crate::SpanTrace> {
207        source_trace::<crate::SpanTrace>(source).filter(|st| st.is_captured())
208    }
209
210    pub use crate::marker::{TraceMarker, restore_marker, set_marker};
211
212    /// The exit code a type-erased source declares, reached through the Provider
213    /// API. Returns `None` on stable (descending into a `dyn Error` is not
214    /// portable there).
215    #[inline]
216    #[must_use]
217    pub fn source_exit_code(
218        source: &(dyn std::error::Error + 'static),
219    ) -> Option<core::num::NonZeroU8> {
220        #[cfg(feature = "unstable-error-generic-member-access")]
221        {
222            core::error::request_value::<core::num::NonZeroU8>(source)
223        }
224        #[cfg(not(feature = "unstable-error-generic-member-access"))]
225        {
226            let _ = source;
227            None
228        }
229    }
230
231    /// Build a `NonZeroU8` from a value the macro already validated to be in
232    /// `1..=255`. Panics at const-eval if the invariant is ever broken, so the
233    /// generated code stays `unsafe`-free.
234    #[inline]
235    #[must_use]
236    pub const fn nonzero_u8_unchecked(value: u8) -> core::num::NonZeroU8 {
237        match core::num::NonZeroU8::new(value) {
238            Some(n) => n,
239            None => panic!("exit code must be non-zero"),
240        }
241    }
242
243    #[cfg(feature = "chrono")]
244    pub use chrono;
245}
246
247macro_rules! impl_string_newtypes {
248    ($($(#[$meta:meta])* $ident:ident,)* $(,)?) => { $(
249        #[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
250        #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
251        #[cfg_attr(feature = "serde", serde(transparent))]
252        #[repr(transparent)]
253        $(#[$meta])*
254        pub struct $ident(Cow<'static, str>);
255
256        impl $ident {
257            #[doc = concat!("Build a new `", stringify!($ident), "` from a `&'static str`.")]
258            #[must_use]
259            #[inline]
260            pub const fn from_static(s: &'static str) -> Self {
261                Self(Cow::Borrowed(s))
262            }
263
264            #[doc = concat!("Build a new `", stringify!($ident), "` from a `String`.")]
265            #[must_use]
266            #[inline]
267            pub const fn from_string(s: String) -> Self {
268                Self(Cow::Owned(s))
269            }
270
271            /// Borrow the underlying string.
272            #[must_use]
273            #[inline]
274            pub const fn as_str(&self) -> &str {
275                match &self.0 {
276                    Cow::Borrowed(s) => s,
277                    Cow::Owned(s) => s.as_str(),
278                }
279            }
280
281            #[doc = concat!("Consume the `", stringify!($ident), "` and return the underlying `Cow`.")]
282            #[must_use]
283            #[inline]
284            pub fn into_inner(self) -> Cow<'static, str> {
285                self.0
286            }
287        }
288
289        impl Deref for $ident {
290            type Target = str;
291
292            #[inline]
293            fn deref(&self) -> &Self::Target {
294                self.as_str()
295            }
296        }
297
298        impl Borrow<str> for $ident {
299            #[inline]
300            fn borrow(&self) -> &str {
301                self.as_str()
302            }
303        }
304
305        impl From<&'static str> for $ident {
306            #[inline]
307            fn from(s: &'static str) -> Self {
308                Self::from_static(s)
309            }
310        }
311
312        impl From<String> for $ident {
313            #[inline]
314            fn from(s: String) -> Self {
315                Self::from_string(s)
316            }
317        }
318
319        impl std::fmt::Display for $ident {
320            #[inline]
321            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
322                self.0.fmt(f)
323            }
324        }
325
326    )* };
327}
328
329impl_string_newtypes!(
330    /// An opaque error code, used for programmatic handling and matching.
331    ErrorCode,
332    /// User-facing help text, intended to be shown in diagnostics.
333    HelpText,
334);