Skip to main content

error2/
root_error.rs

1use crate::{Error2, Location, transform::SourceToTarget};
2
3/// Convenience methods for creating root errors.
4///
5/// `RootError` provides `.build()` and `.fail()` for creating errors that
6/// represent new error origins (not wrapping other errors).
7///
8/// # Root Error Pattern
9///
10/// A root error only has a `backtrace` field (no `source`):
11///
12/// ```
13/// use error2::prelude::*;
14///
15/// #[derive(Debug, Error2)]
16/// pub enum AppError {
17///     #[error2(display("invalid ID: {id}"))]
18///     InvalidId { id: i64, backtrace: Backtrace },
19///
20///     #[error2(display("channel closed"))]
21///     ChannelClosed { backtrace: Backtrace },
22/// }
23/// ```
24///
25/// # Using .build()
26///
27/// Create an error instance:
28///
29/// ```
30/// # use error2::prelude::*;
31/// # #[derive(Debug, Error2)]
32/// # pub enum AppError {
33/// #     #[error2(display("invalid ID: {id}"))]
34/// #     InvalidId { id: i64, backtrace: Backtrace },
35/// # }
36/// # fn validate(id: i64) -> Result<(), AppError> {
37/// if id < 0 {
38///     let error = InvalidId2 { id }.build();
39///     // Use error...
40/// }
41/// # Ok(())
42/// # }
43/// ```
44///
45/// # Using .fail()
46///
47/// Create and return an error in one step:
48///
49/// ```
50/// # use error2::prelude::*;
51/// # #[derive(Debug, Error2)]
52/// # pub enum AppError {
53/// #     #[error2(display("invalid ID: {id}"))]
54/// #     InvalidId { id: i64, backtrace: Backtrace },
55/// # }
56/// fn validate(id: i64) -> Result<(), AppError> {
57///     if id < 0 {
58///         return InvalidId2 { id }.fail();
59///     }
60///     Ok(())
61/// }
62/// ```
63///
64/// # Automatic Location Tracking
65///
66/// Both `.build()` and `.fail()` automatically capture the caller's
67/// location using `#[track_caller]`, which is included in the backtrace.
68///
69/// ```
70/// # use error2::prelude::*;
71/// # #[derive(Debug, Error2)]
72/// # pub enum AppError {
73/// #     #[error2(display("invalid ID: {id}"))]
74/// #     InvalidId { id: i64, backtrace: Backtrace },
75/// # }
76/// use regex::Regex;
77///
78/// let err = InvalidId2 { id: -1 }.build();
79/// let msg = err.backtrace().error_message();
80///
81/// // Full error format with location tracking:
82/// // AppError: invalid ID: -1
83/// //     at /path/to/file.rs:683:33
84///
85/// let re = Regex::new(concat!(
86///     r"(?s)^.+AppError: invalid ID: -1",
87///     r"\n    at .+\.rs:\d+:\d+$",
88/// ))
89/// .unwrap();
90/// assert!(re.is_match(msg.as_ref()));
91/// ```
92pub trait RootError<M, Target: Error2>: SourceToTarget<M, (), (), Target> + Sized {
93    /// Creates a root error instance.
94    ///
95    /// Automatically captures the caller's location.
96    #[inline]
97    #[must_use]
98    #[track_caller]
99    fn build(self) -> Target {
100        self.build_with_location(Location::caller())
101    }
102
103    /// Creates a root error with explicit location.
104    #[inline]
105    #[must_use]
106    fn build_with_location(self, location: Location) -> Target {
107        <Self as SourceToTarget<M, (), (), Target>>::source_to_target(self, (), location)
108    }
109
110    /// Creates and returns a root error as `Err(...)`.
111    ///
112    /// Convenient for returning errors directly.
113    #[inline]
114    #[track_caller]
115    fn fail<T>(self) -> Result<T, Target> {
116        Err(self.build())
117    }
118
119    /// Creates and returns error with explicit location.
120    #[inline]
121    fn fail_with_location<T>(self, location: Location) -> Result<T, Target> {
122        Err(self.build_with_location(location))
123    }
124}
125
126impl<M, Target, C> RootError<M, Target> for C
127where
128    Target: Error2,
129    C: SourceToTarget<M, (), (), Target>,
130{
131}