Skip to main content

charon_error/
result_ext.rs

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