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}