1use std::fmt::Display;
2
3mod backtrace;
4mod context;
5
6mod wrapper;
7pub use wrapper::OpaqueError;
8
9pub trait ErrorContext: private::SealedErrorContext {
22    type Context;
24
25    fn context<M>(self, context: M) -> Self::Context
27    where
28        M: Display + Send + Sync + 'static;
29
30    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
84pub trait ErrorExt: private::SealedErrorExt {
108    fn context<M>(self, context: M) -> OpaqueError
119    where
120        M: Display + Send + Sync + 'static;
121
122    fn with_context<C, F>(self, context: F) -> OpaqueError
135    where
136        C: Display + Send + Sync + 'static,
137        F: FnOnce() -> C;
138
139    fn backtrace(self) -> OpaqueError;
150
151    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}