rama_error/ext/
mod.rs

1use std::fmt::Display;
2
3mod backtrace;
4mod context;
5
6mod wrapper;
7pub use wrapper::OpaqueError;
8
9/// Extends the `Result` and `Option` types with methods for adding context to errors.
10///
11/// See the [module level documentation](crate::error) for more information.
12///
13/// # Examples
14///
15/// ```
16/// use rama_error::ErrorContext;
17///
18/// let result = "hello".parse::<i32>().context("parse integer");
19/// assert_eq!("parse integer\r\n ↪ invalid digit found in string", result.unwrap_err().to_string());
20/// ```
21pub trait ErrorContext: private::SealedErrorContext {
22    /// The resulting contexct type after adding context to the contained error.
23    type Context;
24
25    /// Add a static context to the contained error.
26    fn context<M>(self, context: M) -> Self::Context
27    where
28        M: Display + Send + Sync + 'static;
29
30    /// Lazily add a context to the contained error, if it exists.
31    fn with_context<C, F>(self, context: F) -> Self::Context
32    where
33        C: Display + Send + Sync + 'static,
34        F: FnOnce() -> C;
35}
36
37impl<T, E> ErrorContext for Result<T, E>
38where
39    E: std::error::Error + Send + Sync + 'static,
40{
41    type Context = Result<T, OpaqueError>;
42
43    fn context<M>(self, context: M) -> Self::Context
44    where
45        M: Display + Send + Sync + 'static,
46    {
47        self.map_err(|error| error.context(context))
48    }
49
50    fn with_context<C, F>(self, context: F) -> Self::Context
51    where
52        C: Display + Send + Sync + 'static,
53        F: FnOnce() -> C,
54    {
55        self.map_err(|error| error.context(context()))
56    }
57}
58
59impl<T> ErrorContext for Option<T> {
60    type Context = Result<T, OpaqueError>;
61
62    fn context<M>(self, context: M) -> Self::Context
63    where
64        M: Display + Send + Sync + 'static,
65    {
66        match self {
67            Some(value) => Ok(value),
68            None => Err(wrapper::MessageError("Option is None").context(context)),
69        }
70    }
71
72    fn with_context<C, F>(self, context: F) -> Self::Context
73    where
74        C: Display + Send + Sync + 'static,
75        F: FnOnce() -> C,
76    {
77        match self {
78            Some(value) => Ok(value),
79            None => Err(wrapper::MessageError("Option is None").with_context(context)),
80        }
81    }
82}
83
84/// Extends the `Error` type with methods for working with errorss.
85///
86/// See the [module level documentation](crate::error) for more information.
87///
88/// # Examples
89///
90/// ```
91/// use rama_error::{BoxError, ErrorExt, ErrorContext};
92///
93/// #[derive(Debug)]
94/// struct CustomError;
95///
96/// impl std::fmt::Display for CustomError {
97///  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
98///    write!(f, "Custom error")
99///  }
100/// }
101///
102/// impl std::error::Error for CustomError {}
103///
104/// let error = CustomError.context("whoops");
105/// assert_eq!(error.to_string(), "whoops\r\n ↪ Custom error");
106/// ```
107pub trait ErrorExt: private::SealedErrorExt {
108    /// Wrap the error in a context.
109    ///
110    /// # Examples
111    ///
112    /// ```
113    /// use rama_error::ErrorExt;
114    ///
115    /// let error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!").context("do I/O");
116    /// assert_eq!(error.to_string(), "do I/O\r\n ↪ oh no!");
117    /// ```
118    fn context<M>(self, context: M) -> OpaqueError
119    where
120        M: Display + Send + Sync + 'static;
121
122    /// Lazily wrap the error with a context.
123    ///
124    /// # Examples
125    ///
126    /// ```
127    /// use rama_error::ErrorExt;
128    ///
129    /// let error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!").with_context(|| format!(
130    ///    "do I/O ({})", 42,
131    /// ));
132    /// assert_eq!(error.to_string(), "do I/O (42)\r\n ↪ oh no!");
133    /// ```
134    fn with_context<C, F>(self, context: F) -> OpaqueError
135    where
136        C: Display + Send + Sync + 'static,
137        F: FnOnce() -> C;
138
139    /// Add a [`Backtrace`][std::backtrace::Backtrace] to the error.
140    ///
141    /// # Examples
142    ///
143    /// ```
144    /// use rama_error::ErrorExt;
145    ///
146    /// let error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!").backtrace();
147    /// println!("{}", error);
148    /// ```
149    fn backtrace(self) -> OpaqueError;
150
151    /// Convert the error into an [`OpaqueError`].
152    ///
153    /// # Examples
154    ///
155    /// ```
156    /// use rama_error::ErrorExt;
157    ///
158    /// let error = std::io::Error::new(std::io::ErrorKind::Other, "oh no!").into_opaque();
159    /// assert_eq!(error.to_string(), "oh no!");
160    /// ```
161    fn into_opaque(self) -> OpaqueError;
162}
163
164impl<Error: std::error::Error + Send + Sync + 'static> ErrorExt for Error {
165    fn context<M>(self, context: M) -> OpaqueError
166    where
167        M: Display + Send + Sync + 'static,
168    {
169        OpaqueError::from_std(context::ContextError {
170            context,
171            error: self,
172        })
173    }
174
175    fn with_context<C, F>(self, context: F) -> OpaqueError
176    where
177        C: Display + Send + Sync + 'static,
178        F: FnOnce() -> C,
179    {
180        OpaqueError::from_std(context::ContextError {
181            context: context(),
182            error: self,
183        })
184    }
185
186    fn backtrace(self) -> OpaqueError {
187        OpaqueError::from_std(backtrace::BacktraceError::new(self))
188    }
189
190    fn into_opaque(self) -> OpaqueError {
191        OpaqueError::from_std(self)
192    }
193}
194
195mod private {
196    pub trait SealedErrorContext {}
197
198    impl<T, E> SealedErrorContext for Result<T, E> where E: std::error::Error + Send + Sync + 'static {}
199    impl<T> SealedErrorContext for Option<T> {}
200
201    pub trait SealedErrorExt {}
202
203    impl<Error: std::error::Error + Send + Sync + 'static> SealedErrorExt for Error {}
204}
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::BoxError;
210
211    #[test]
212    fn message_error_context() {
213        let error = wrapper::MessageError("foo").context("context");
214        assert_eq!(error.to_string(), "context\r\n ↪ foo");
215    }
216
217    #[test]
218    fn box_error_context() {
219        let error = Box::new(wrapper::MessageError("foo"));
220        let error = error.context("context");
221        assert_eq!(error.to_string(), "context\r\n ↪ foo");
222    }
223
224    #[derive(Debug)]
225    struct CustomError;
226
227    impl std::fmt::Display for CustomError {
228        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
229            write!(f, "Custom error")
230        }
231    }
232
233    impl std::error::Error for CustomError {}
234
235    #[derive(Debug)]
236    struct WrapperError(BoxError);
237
238    impl std::fmt::Display for WrapperError {
239        fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
240            write!(f, "Wrapper error")
241        }
242    }
243
244    impl std::error::Error for WrapperError {
245        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
246            if let Some(err) = self.0.source() {
247                return Some(err);
248            }
249            let err = self.0.as_ref();
250            Some(err as &(dyn std::error::Error + 'static))
251        }
252    }
253
254    #[test]
255    fn test_wrapper_error_source() {
256        let error = WrapperError(Box::new(CustomError))
257            .context("foo")
258            .backtrace();
259        let source = std::error::Error::source(&error).unwrap();
260        assert!(source.downcast_ref::<CustomError>().is_some());
261    }
262
263    #[test]
264    fn test_chain_error_source() {
265        let error = OpaqueError::from_boxed(
266            CustomError
267                .context("foo")
268                .context("bar")
269                .backtrace()
270                .context("baz")
271                .into_boxed(),
272        );
273        let source = std::error::Error::source(&error).unwrap();
274        assert!(source.is::<CustomError>());
275    }
276
277    #[test]
278    fn custom_error_backtrace() {
279        let error = CustomError;
280        let error = error.backtrace();
281
282        assert!(error.to_string().starts_with("Initial error\r\n ↪"));
283    }
284}