Skip to main content

charon_error/
result_ext.rs

1//! Extension trait for `Result` and `Option` to integrate with [`ErrorReport`](crate::ErrorReport).
2//!
3//! Provides [`change_context`](ResultExt::change_context),
4//! [`error_attach`](ResultExt::error_attach), and
5//! [`unwrap_error`](ResultExt::unwrap_error) methods.
6
7use std::error::Error;
8
9use crate::StringError;
10use crate::{ErrorAttachment, ErrorReport, ErrorSensitivityLabel};
11
12/// Extension trait for `Result` and `Option` that integrates with [`ErrorReport`].
13///
14/// Provides methods to wrap errors with context, attach diagnostic data,
15/// and unwrap with rich panic messages.
16///
17/// Implemented for:
18/// - `Result<T, E>` where `E: Error`
19/// - `Result<T, ErrorReport>`
20/// - `Option<T>`
21///
22/// # Example
23///
24/// ```rust,no_run
25/// use charon_error::prelude::*;
26///
27/// fn read_config() -> ResultER<String> {
28///     std::fs::read_to_string("config.toml")
29///         .change_context(StringError::new("failed to read config"))
30/// }
31/// ```
32///
33/// Attaching diagnostic data to errors:
34///
35/// ```rust,no_run
36/// use charon_error::prelude::*;
37///
38/// fn read_file(path: &str) -> ResultER<String> {
39///     std::fs::read_to_string(path)
40///         .change_context(StringError::new("failed to read file"))
41///         .error_attach(
42///             "file_path",
43///             ErrorSensitivityLabel::Public(ErrorAttachment::String(path.to_owned())),
44///         )
45/// }
46/// ```
47pub trait ResultExt {
48    /// Type of the [`Ok`] value in the [`Result`].
49    type Ok;
50
51    /// Wrap the error in a new [`ErrorReport`] (or push onto an existing one)
52    /// with the given higher-level error as context.
53    #[track_caller]
54    fn change_context<E: Error + Sized + Send + Sync + 'static>(
55        self,
56        error: E,
57    ) -> core::result::Result<Self::Ok, ErrorReport>;
58
59    /// Attach labeled data to the error report on the `Err` path.
60    #[track_caller]
61    fn error_attach<S: Into<String>>(
62        self,
63        key_name: S,
64        attachment: ErrorSensitivityLabel<ErrorAttachment>,
65    ) -> Result<Self::Ok, ErrorReport>;
66
67    /// Shorthand to attach a public string to the error report.
68    #[track_caller]
69    fn error_attach_public_string<S: Into<String>>(
70        self,
71        key_name: S,
72        attachment: String,
73    ) -> Result<Self::Ok, ErrorReport>;
74
75    /// Unwrap the value, panicking with a full [`ErrorReport`] on `Err`/`None`.
76    #[track_caller]
77    fn unwrap_error(self) -> Self::Ok;
78
79    /// Unwrap the value with a custom message, panicking with a full [`ErrorReport`].
80    #[track_caller]
81    fn expect_error(self, msg: &str) -> Self::Ok;
82}
83
84impl<T, E> ResultExt for core::result::Result<T, E>
85where
86    E: Error + Sized + Send + Sync + 'static,
87{
88    type Ok = T;
89
90    #[track_caller]
91    fn change_context<R: Error + Sized + Send + Sync + 'static>(
92        self,
93        error: R,
94    ) -> Result<T, ErrorReport> {
95        // Can't use `map_err` as `#[track_caller]` is unstable on closures
96        match self {
97            Ok(ok) => Ok(ok),
98            Err(err) => Err(ErrorReport::from_error(err).push_error(error)),
99        }
100    }
101
102    #[track_caller]
103    fn error_attach<S: Into<String>>(
104        self,
105        key_name: S,
106        attachment: ErrorSensitivityLabel<ErrorAttachment>,
107    ) -> Result<T, ErrorReport> {
108        // Can't use `map_err` as `#[track_caller]` is unstable on closures
109        match self {
110            Ok(ok) => Ok(ok),
111            Err(err) => Err(ErrorReport::from_error(err).attach(key_name, attachment)),
112        }
113    }
114
115    #[track_caller]
116    fn error_attach_public_string<S: Into<String>>(
117        self,
118        key_name: S,
119        attachment: String,
120    ) -> Result<T, ErrorReport> {
121        // Can't use `map_err` as `#[track_caller]` is unstable on closures
122        match self {
123            Ok(ok) => Ok(ok),
124            Err(err) => {
125                Err(ErrorReport::from_error(err).attach_public_string(key_name, attachment))
126            }
127        }
128    }
129
130    #[track_caller]
131    fn unwrap_error(self) -> T {
132        match self {
133            Ok(t) => t,
134            Err(e) => std::panic::panic_any(ErrorReport::from_error(e).push_error(
135                StringError::new("Called `ResultExt::unwrap_error()` on an `Err` value"),
136            )),
137        }
138    }
139
140    #[track_caller]
141    fn expect_error(self, msg: &str) -> T {
142        match self {
143            Ok(t) => t,
144            Err(e) => std::panic::panic_any(
145                ErrorReport::from_error(e)
146                    .push_error(StringError::new(msg))
147                    .attach(
148                        "function_call",
149                        ErrorSensitivityLabel::Public(ErrorAttachment::String(
150                            "ResultExt::expect_error".to_owned(),
151                        )),
152                    ),
153            ),
154        }
155    }
156}
157
158impl<T> ResultExt for core::result::Result<T, ErrorReport> {
159    type Ok = T;
160
161    #[track_caller]
162    fn change_context<R: Error + Sized + Send + Sync + 'static>(
163        self,
164        error: R,
165    ) -> Result<T, ErrorReport> {
166        // Can't use `map_err` as `#[track_caller]` is unstable on closures
167        match self {
168            Ok(ok) => Ok(ok),
169            Err(err) => Err(err.push_error(error)),
170        }
171    }
172
173    #[track_caller]
174    fn error_attach<S: Into<String>>(
175        self,
176        key_name: S,
177        attachment: ErrorSensitivityLabel<ErrorAttachment>,
178    ) -> Result<T, ErrorReport> {
179        // Can't use `map_err` as `#[track_caller]` is unstable on closures
180        match self {
181            Ok(ok) => Ok(ok),
182            Err(err) => Err(err.attach(key_name, attachment)),
183        }
184    }
185
186    #[track_caller]
187    fn error_attach_public_string<S: Into<String>>(
188        self,
189        key_name: S,
190        attachment: String,
191    ) -> Result<T, ErrorReport> {
192        // Can't use `map_err` as `#[track_caller]` is unstable on closures
193        match self {
194            Ok(ok) => Ok(ok),
195            Err(err) => Err(err.attach(
196                key_name,
197                ErrorSensitivityLabel::Public(ErrorAttachment::String(attachment)),
198            )),
199        }
200    }
201
202    #[track_caller]
203    fn unwrap_error(self) -> T {
204        match self {
205            Ok(t) => t,
206            Err(e) => std::panic::panic_any(e.push_error(StringError::new(
207                "Called `ResultExt::unwrap_error()` on an `Err` value",
208            ))),
209        }
210    }
211
212    #[track_caller]
213    fn expect_error(self, msg: &str) -> T {
214        match self {
215            Ok(t) => t,
216            Err(e) => std::panic::panic_any(e.push_error(StringError::new(msg)).attach(
217                "function_call",
218                ErrorSensitivityLabel::Public(ErrorAttachment::String(
219                    "ResultExt::expect_error".to_owned(),
220                )),
221            )),
222        }
223    }
224}
225
226impl<T> ResultExt for std::option::Option<T> {
227    type Ok = T;
228
229    #[track_caller]
230    fn change_context<R: Error + Sized + Send + Sync + 'static>(
231        self,
232        error: R,
233    ) -> Result<T, ErrorReport> {
234        // Can't use `map_err` as `#[track_caller]` is unstable on closures
235        match self {
236            Some(ok) => Ok(ok),
237            None => Err(ErrorReport::from_error(StringError::new(
238                "Called `Option::change_context()` (Part of trait: `ResultExt`) on a `None` value",
239            ))
240            .push_error(error)),
241        }
242    }
243
244    #[track_caller]
245    fn error_attach<S: Into<String>>(
246        self,
247        key_name: S,
248        attachment: ErrorSensitivityLabel<ErrorAttachment>,
249    ) -> Result<T, ErrorReport> {
250        // Can't use `map_err` as `#[track_caller]` is unstable on closures
251        match self {
252            Some(ok) => Ok(ok),
253            None => Err(ErrorReport::from_error(StringError::new(
254                "Called `Option::error_attach()` (Part of trait: `ResultExt`) on a `None` value",
255            ))
256            .attach(key_name, attachment)),
257        }
258    }
259
260    #[track_caller]
261    fn error_attach_public_string<S: Into<String>>(
262        self,
263        key_name: S,
264        attachment: String,
265    ) -> Result<T, ErrorReport> {
266        // Can't use `map_err` as `#[track_caller]` is unstable on closures
267        match self {
268            Some(ok) => Ok(ok),
269            None => Err(ErrorReport::from_error(StringError::new(
270                "Called `Option::error_attach_public_string()` (Part of trait: `ResultExt`) on a `None` value",
271            ))
272            .attach(
273                key_name,
274                ErrorSensitivityLabel::Public(ErrorAttachment::String(attachment)),
275            )),
276        }
277    }
278
279    #[track_caller]
280    fn unwrap_error(self) -> T {
281        match self {
282            Some(t) => t,
283            None => std::panic::panic_any(ErrorReport::from_error(StringError::new(
284                "Called `Option::unwrap_error()` (Part of trait: `ResultExt`) on a `None` value",
285            ))),
286        }
287    }
288
289    #[track_caller]
290    fn expect_error(self, msg: &str) -> T {
291        match self {
292            Some(t) => t,
293            None => std::panic::panic_any(ErrorReport::from_error(StringError::new(msg)).attach(
294                "function_call",
295                ErrorSensitivityLabel::Public(ErrorAttachment::String(
296                    "ResultExt::expect_error".to_owned(),
297                )),
298            )),
299        }
300    }
301}