Skip to main content

error2/boxed/
mod.rs

1mod root_err;
2mod std_err;
3
4use std::{
5    any::TypeId,
6    error::Error,
7    fmt::{self, Debug, Display, Formatter},
8};
9
10use self::{root_err::RootErr, std_err::StdErr};
11use crate::{Backtrace, Error2, Location, kind::ErrorKind, private, transform::SourceToTarget};
12
13/// Type-erased error with automatic backtrace tracking.
14///
15/// `BoxedError2` provides anyhow-like ergonomics with type erasure,
16/// allowing you to work with errors without specifying concrete types.
17///
18/// # Features
19///
20/// - **Type Erasure**: Store any error implementing `Error2` or `std::error::Error`
21/// - **Downcasting**: Retrieve the original error type
22/// - **Backtrace**: Access complete error chain and locations
23///
24/// # Conversion Control
25///
26/// Use `Via*` types to control how errors are wrapped:
27///
28/// ```
29/// use error2::prelude::*;
30///
31/// fn example() -> Result<(), BoxedError2> {
32///     // Wrap std::error::Error
33///     std::fs::read_to_string("file.txt").context(ViaStd)?;
34///
35///     Ok(())
36/// }
37/// ```
38///
39/// See [`ViaRoot`], [`ViaStd`], and [`ViaErr2`] for details.
40///
41/// # Downcasting
42///
43/// Retrieve the original error type:
44///
45/// ```
46/// use std::io;
47///
48/// use error2::{kind::ErrorKind, prelude::*};
49///
50/// # fn example() -> Result<(), BoxedError2> {
51/// let err = std::io::Error::from(std::io::ErrorKind::NotFound);
52/// let boxed = BoxedError2::from_std(err);
53///
54/// match boxed.downcast_ref::<io::Error>() {
55///     Some(ErrorKind::Std { source, .. }) => {
56///         println!("IO error: {}", source);
57///     }
58///     _ => {}
59/// }
60/// # Ok(())
61/// # }
62/// ```
63pub struct BoxedError2 {
64    source: Box<dyn Error2 + Send + Sync + 'static>,
65}
66
67impl BoxedError2 {
68    #[inline]
69    const fn source_ref(&self) -> &(dyn Error + Send + Sync + 'static) {
70        &*self.source
71    }
72
73    #[inline]
74    const fn source_mut(&mut self) -> &mut (dyn Error + Send + Sync + 'static) {
75        &mut *self.source
76    }
77
78    #[inline]
79    fn source(self) -> Box<dyn Error + Send + Sync + 'static> {
80        self.source
81    }
82
83    /// Checks if this is a root error (not wrapping another error).
84    #[inline]
85    pub fn is_root(&self) -> bool {
86        self.source_ref().is::<RootErr>()
87    }
88
89    fn generic_is_root<T: Error + 'static>() -> bool {
90        TypeId::of::<RootErr>() == TypeId::of::<T>()
91    }
92
93    /// Checks if the boxed error contains an error of type `T`.
94    #[inline]
95    pub fn is<T: Error + 'static>(&self) -> bool {
96        debug_assert!(!Self::generic_is_root::<T>());
97        let source = self.source_ref();
98
99        source.is::<StdErr<T>>() || source.is::<T>()
100    }
101
102    /// Attempts to downcast to a reference of type `T`.
103    ///
104    /// Returns `Some(ErrorKind)` if the error is of type `T`.
105    #[inline]
106    pub fn downcast_ref<T: Error + 'static>(&self) -> Option<ErrorKind<&T, &Backtrace>> {
107        debug_assert!(!Self::generic_is_root::<T>());
108        let source = self.source_ref();
109
110        if let Some(StdErr { source, backtrace }) = source.downcast_ref::<StdErr<T>>() {
111            Some(ErrorKind::Std { source, backtrace })
112        } else if let Some(source) = source.downcast_ref::<T>() {
113            Some(ErrorKind::Err2 { source })
114        } else {
115            None
116        }
117    }
118
119    /// Attempts to downcast to a mutable reference of type `T`.
120    #[inline]
121    pub fn downcast_mut<T: Error + 'static>(
122        &mut self,
123    ) -> Option<ErrorKind<&mut T, &mut Backtrace>> {
124        debug_assert!(!Self::generic_is_root::<T>());
125        let source = self.source_ref();
126
127        if source.is::<StdErr<T>>() {
128            let StdErr { source, backtrace } =
129                self.source_mut().downcast_mut::<StdErr<T>>().unwrap();
130            Some(ErrorKind::Std { source, backtrace })
131        } else if source.is::<T>() {
132            let source = self.source_mut().downcast_mut::<T>().unwrap();
133            Some(ErrorKind::Err2 { source })
134        } else {
135            None
136        }
137    }
138
139    /// Attempts to downcast to an owned value of type `T`.
140    ///
141    /// Returns `Ok(ErrorKind)` if successful, or `Err(self)` on failure.
142    #[inline]
143    pub fn downcast<T: Error + 'static>(self) -> Result<ErrorKind<T, Backtrace>, Self> {
144        debug_assert!(!Self::generic_is_root::<T>());
145        let source = self.source_ref();
146
147        if source.is::<StdErr<T>>() {
148            let StdErr { source, backtrace } = *self.source().downcast::<StdErr<T>>().unwrap();
149            Ok(ErrorKind::Std { source, backtrace })
150        } else if source.is::<T>() {
151            let source = *self.source().downcast::<T>().unwrap();
152            Ok(ErrorKind::Err2 { source })
153        } else {
154            Err(self)
155        }
156    }
157}
158
159impl Display for BoxedError2 {
160    #[inline]
161    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
162        Display::fmt(&self.source, f)
163    }
164}
165
166impl Debug for BoxedError2 {
167    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
168        if f.alternate() {
169            Debug::fmt(&self.source, f)
170        } else {
171            Display::fmt(&self.source, f)?;
172            write!(f, "\n\n")?;
173
174            let m = self.backtrace().error_message();
175            Display::fmt(&m, f)
176        }
177    }
178}
179
180impl Error for BoxedError2 {
181    #[inline]
182    fn source(&self) -> Option<&(dyn Error + 'static)> {
183        if self.is_root() {
184            None
185        } else {
186            Some(&*self.source)
187        }
188    }
189}
190
191impl Error2 for BoxedError2 {
192    #[inline]
193    fn backtrace(&self) -> &Backtrace {
194        self.source.backtrace()
195    }
196
197    #[inline]
198    fn backtrace_mut(&mut self) -> &mut Backtrace {
199        self.source.backtrace_mut()
200    }
201}
202
203impl BoxedError2 {
204    /// Creates a `BoxedError2` from a root error message.
205    ///
206    /// Use for errors that don't wrap other errors.
207    #[track_caller]
208    #[inline]
209    pub fn from_root<R>(root: R) -> BoxedError2
210    where
211        R: Display + Debug + Send + Sync + 'static,
212    {
213        Self::from_root_with_location(root, Location::caller())
214    }
215
216    /// Creates a root error with explicit location.
217    pub fn from_root_with_location<R>(root: R, location: Location) -> BoxedError2
218    where
219        R: Display + Debug + Send + Sync + 'static,
220    {
221        let mut error = BoxedError2 {
222            source: Box::new(RootErr::new(root)),
223        };
224
225        crate::push_error(&mut error, location);
226
227        error
228    }
229
230    /// Creates a `BoxedError2` from a `std::error::Error`.
231    ///
232    /// Wraps standard library or third-party errors.
233    #[track_caller]
234    #[inline]
235    pub fn from_std<T>(source: T) -> BoxedError2
236    where
237        T: Error + Send + Sync + 'static,
238    {
239        Self::from_std_with_location(source, Location::caller())
240    }
241
242    /// Creates from std::error::Error with explicit location.
243    pub fn from_std_with_location<T>(source: T, location: Location) -> BoxedError2
244    where
245        T: Error + Send + Sync + 'static,
246    {
247        if (&source as &(dyn Error + Send + Sync)).is::<BoxedError2>() {
248            let mut e =
249                <dyn Error + Send + Sync>::downcast::<BoxedError2>(Box::new(source)).unwrap();
250
251            e.backtrace_mut().push_location(location);
252            *e
253        } else {
254            let mut error = BoxedError2 {
255                source: Box::new(StdErr::new(source)),
256            };
257
258            crate::push_error(&mut error, location);
259
260            error
261        }
262    }
263
264    /// Creates a `BoxedError2` from an `Error2` type.
265    ///
266    /// Preserves the original error's backtrace.
267    #[track_caller]
268    #[inline]
269    pub fn from_err2<T>(source: T) -> BoxedError2
270    where
271        T: Error2 + Send + Sync + 'static,
272    {
273        Self::from_err2_with_location(source, Location::caller())
274    }
275
276    /// Creates from Error2 with explicit location.
277    pub fn from_err2_with_location<T>(source: T, location: Location) -> BoxedError2
278    where
279        T: Error2 + Send + Sync + 'static,
280    {
281        if (&source as &(dyn Error + Send + Sync)).is::<BoxedError2>() {
282            let mut e =
283                <dyn Error + Send + Sync>::downcast::<BoxedError2>(Box::new(source)).unwrap();
284
285            e.backtrace_mut().push_location(location);
286            *e
287        } else {
288            let mut error = BoxedError2 {
289                source: Box::new(source),
290            };
291
292            crate::push_error(&mut error, location);
293
294            error
295        }
296    }
297}
298
299/// Wrapper for creating root errors with [`BoxedError2`].
300///
301/// Use with `.context()` to create errors from non-Error types
302/// like strings or custom messages.
303///
304/// # Example
305///
306/// ```
307/// use error2::prelude::*;
308///
309/// fn validate(value: i32) -> Result<(), BoxedError2> {
310///     if value < 0 {
311///         // Create a root error from a string message
312///         ViaRoot("validation failed: negative value").fail()?;
313///     }
314///     Ok(())
315/// }
316///
317/// # fn main() -> Result<(), BoxedError2> {
318/// # assert!(validate(10).is_ok());
319/// # assert!(validate(-5).is_err());
320/// # Ok(())
321/// # }
322/// ```
323pub struct ViaRoot<M>(pub M)
324where
325    M: Display + Debug + Send + Sync + 'static;
326
327impl<M> SourceToTarget<private::ViaFull, (), (), BoxedError2> for ViaRoot<M>
328where
329    M: Display + Debug + Send + Sync + 'static,
330{
331    #[inline]
332    fn source_to_target(self, _source: (), location: Location) -> BoxedError2 {
333        BoxedError2::from_root_with_location(self.0, location)
334    }
335}
336
337/// Wrapper for converting `std::error::Error` to [`BoxedError2`].
338///
339/// Use with `.context()` to wrap standard library or third-party errors.
340///
341/// # Example
342///
343/// ```
344/// use std::fs;
345///
346/// use error2::prelude::*;
347///
348/// fn read_file() -> Result<String, BoxedError2> {
349///     fs::read_to_string("file.txt").context(ViaStd)
350/// }
351/// ```
352pub struct ViaStd;
353
354impl<T> SourceToTarget<private::ViaFull, T, T, BoxedError2> for ViaStd
355where
356    T: Error + Send + Sync + 'static,
357{
358    #[inline]
359    fn source_to_target(self, source: T, location: Location) -> BoxedError2 {
360        BoxedError2::from_std_with_location(source, location)
361    }
362}
363
364/// Wrapper for converting `Error2` types to [`BoxedError2`].
365///
366/// Use with `.context()` to wrap Error2-based errors, preserving
367/// the original backtrace.
368///
369/// # Example
370///
371/// ```
372/// use error2::prelude::*;
373///
374/// # #[derive(Debug, Error2)]
375/// # #[error2(display("inner error"))]
376/// # struct InnerError { backtrace: Backtrace }
377/// # fn inner() -> Result<(), InnerError> { Ok(()) }
378/// fn outer() -> Result<(), BoxedError2> {
379///     inner().context(ViaErr2)?;
380///     Ok(())
381/// }
382/// ```
383pub struct ViaErr2;
384
385impl<T> SourceToTarget<private::ViaFull, T, T, BoxedError2> for ViaErr2
386where
387    T: Error2 + Send + Sync + 'static,
388{
389    #[inline]
390    fn source_to_target(self, source: T, location: Location) -> BoxedError2 {
391        BoxedError2::from_err2_with_location(source, location)
392    }
393}