1use std::error::Error;
104use std::fmt::Debug;
105use std::fmt::{self, Display};
106
107pub mod prelude {
109 pub use crate::{
110 in_context_of, in_context_of_with, wrap_in_context_of, wrap_in_context_of_with,
111 ErrorContext, ErrorNoContext, MapErrorNoContext, ResultErrorWhile, ResultErrorWhileWrap,
112 ToErrorNoContext, WithContext, WrapContext,
113 };
114}
115
116pub trait WithContext<C> {
118 type ContextError;
119 fn with_context(self, context: C) -> Self::ContextError;
120}
121
122pub trait ResultErrorWhile<C> {
124 type ContextError;
125 fn error_while(self, context: C) -> Self::ContextError;
126 fn error_while_with<F>(self, context: F) -> Self::ContextError
127 where
128 F: FnOnce() -> C;
129}
130
131impl<O, E, C> ResultErrorWhile<C> for Result<O, E>
132where
133 E: WithContext<C, ContextError = E>,
134{
135 type ContextError = Self;
136 fn error_while(self, context: C) -> Self {
137 self.map_err(|e| e.with_context(context))
138 }
139
140 fn error_while_with<F>(self, context: F) -> Self::ContextError
141 where
142 F: FnOnce() -> C,
143 {
144 self.map_err(|e| e.with_context(context()))
145 }
146}
147
148#[derive(Debug)]
150pub struct ErrorNoContext<E>(pub E);
151
152impl<E> Display for ErrorNoContext<E>
153where
154 E: Display,
155{
156 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
157 self.0.fmt(f)
158 }
159}
160
161impl<E> Error for ErrorNoContext<E>
162where
163 E: Error,
164{
165 fn description(&self) -> &str {
166 self.0.description()
167 }
168
169 fn source(&self) -> Option<&(dyn Error + 'static)> {
170 self.0.source()
171 }
172}
173
174impl<E, C> WithContext<C> for ErrorNoContext<E> {
175 type ContextError = ErrorContext<E, C>;
176 fn with_context(self, context: C) -> ErrorContext<E, C> {
177 ErrorContext {
178 error: self.0,
179 context,
180 }
181 }
182}
183
184pub trait ToErrorNoContext<T> {
186 fn to_root_cause(self) -> ErrorNoContext<T>;
187}
188
189impl<T> ToErrorNoContext<T> for T {
190 fn to_root_cause(self) -> ErrorNoContext<Self> {
191 ErrorNoContext(self)
192 }
193}
194
195pub trait MapErrorNoContext<O, E> {
197 fn map_error_context(self) -> Result<O, ErrorNoContext<E>>;
198}
199
200impl<O, E> MapErrorNoContext<O, E> for Result<O, E> {
201 fn map_error_context(self) -> Result<O, ErrorNoContext<E>> {
202 self.map_err(ToErrorNoContext::to_root_cause)
203 }
204}
205
206#[derive(Debug)]
208pub struct ErrorContext<E, C> {
209 pub error: E,
210 pub context: C,
211}
212
213impl<E, C> Display for ErrorContext<E, C>
214where
215 E: Display,
216 C: Display,
217{
218 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
219 write!(f, "while {} got error: {}", self.context, self.error)
220 }
221}
222
223impl<E, C> Error for ErrorContext<E, C>
224where
225 E: Error,
226 C: Display + Debug,
227{
228 fn description(&self) -> &str {
229 self.error.description()
230 }
231
232 fn source(&self) -> Option<&(dyn Error + 'static)> {
233 self.error.source()
234 }
235}
236
237impl<E, C, C2> WithContext<C2> for ErrorContext<E, C> {
238 type ContextError = ErrorContext<ErrorContext<E, C>, C2>;
239 fn with_context(self, context: C2) -> ErrorContext<ErrorContext<E, C>, C2> {
240 ErrorContext {
241 error: self,
242 context,
243 }
244 }
245}
246
247pub trait WrapContext<C> {
249 type ContextError;
250 fn wrap_context(self, context: C) -> Self::ContextError;
251}
252
253impl<E, C> WrapContext<C> for E {
254 type ContextError = ErrorContext<E, C>;
255 fn wrap_context(self, context: C) -> ErrorContext<E, C> {
256 ErrorContext {
257 error: self,
258 context,
259 }
260 }
261}
262
263pub trait ResultErrorWhileWrap<O, E, C> {
265 fn wrap_error_while(self, context: C) -> Result<O, ErrorContext<E, C>>;
266 fn wrap_error_while_with<F>(self, context: F) -> Result<O, ErrorContext<E, C>>
267 where
268 F: FnOnce() -> C;
269}
270
271impl<O, E, C> ResultErrorWhileWrap<O, E, C> for Result<O, E>
272where
273 E: WrapContext<C, ContextError = ErrorContext<E, C>>,
274{
275 fn wrap_error_while(self, context: C) -> Result<O, ErrorContext<E, C>> {
276 self.map_err(|e| e.wrap_context(context))
277 }
278
279 fn wrap_error_while_with<F>(self, context: F) -> Result<O, ErrorContext<E, C>>
280 where
281 F: FnOnce() -> C,
282 {
283 self.map_err(|e| e.wrap_context(context()))
284 }
285}
286
287pub fn in_context_of<O, E, C, CE, B>(context: C, body: B) -> Result<O, CE>
289where
290 E: WithContext<C, ContextError = CE>,
291 B: FnOnce() -> Result<O, E>,
292{
293 body().map_err(|e| e.with_context(context))
294}
295
296pub fn in_context_of_with<O, E, C, CE, F, M, B>(context: F, body: B) -> Result<O, CE>
298where
299 F: FnOnce() -> C,
300 E: WithContext<C, ContextError = CE>,
301 B: FnOnce() -> Result<O, E>,
302{
303 body().map_err(|e| e.with_context(context()))
304}
305
306pub fn wrap_in_context_of<O, E, C, B>(context: C, body: B) -> Result<O, ErrorContext<E, C>>
308where
309 E: WrapContext<C, ContextError = ErrorContext<E, C>>,
310 B: FnOnce() -> Result<O, E>,
311{
312 body().map_err(|e| e.wrap_context(context))
313}
314
315pub fn wrap_in_context_of_with<O, E, C, F, B>(
317 context: F,
318 body: B,
319) -> Result<O, ErrorContext<E, C>>
320where
321 F: FnOnce() -> C,
322 E: WrapContext<C, ContextError = ErrorContext<E, C>>,
323 B: FnOnce() -> Result<O, E>,
324{
325 body().map_err(|e| e.wrap_context(context()))
326}
327
328#[cfg(test)]
329mod tests {
330 use super::prelude::*;
331 use assert_matches::*;
332 use std::io;
333
334 #[derive(Debug)]
335 enum FooError {
336 Foo {
337 context: Vec<String>,
338 },
339 Bar {
340 num: i32,
341 context: Vec<String>,
342 },
343 IoError {
344 error: io::Error,
345 context: Vec<String>,
346 },
347 }
348
349 impl WithContext<String> for FooError {
350 type ContextError = Self;
351 fn with_context(mut self, message: String) -> Self {
352 match self {
353 FooError::Foo {
354 ref mut context, ..
355 } => context.push(message),
356 FooError::Bar {
357 ref mut context, ..
358 } => context.push(message),
359 FooError::IoError {
360 ref mut context, ..
361 } => context.push(message),
362 }
363 self
364 }
365 }
366
367 impl From<ErrorContext<io::Error, String>> for FooError {
368 fn from(error_context: ErrorContext<io::Error, String>) -> FooError {
369 FooError::IoError {
370 error: error_context.error,
371 context: vec![error_context.context],
372 }
373 }
374 }
375
376 #[test]
377 fn test_in_type_context() {
378 let err: Result<(), FooError> = Err(FooError::Foo {
379 context: Vec::new(),
380 });
381 assert_matches!(err.error_while("doing stuff".to_string()), Err(FooError::Foo { context }) => assert_eq!(context, vec!["doing stuff".to_string()]));
382
383 let err: Result<(), FooError> = Err(FooError::Bar {
384 num: 1,
385 context: Vec::new(),
386 });
387 assert_matches!(err.error_while("doing stuff".to_string()), Err(FooError::Bar { num: 1, context }) => assert_eq!(context, vec!["doing stuff".to_string()]));
388 }
389
390 #[test]
391 fn test_wrapped_context() {
392 use std::io::{Error, ErrorKind};
393 let err: Result<(), Error> = Err(Error::new(ErrorKind::Other, "oh no!"));
394
395 assert_eq!(
396 err.wrap_error_while("doing stuff".to_string())
397 .unwrap_err()
398 .to_string(),
399 "while doing stuff got error: oh no!"
400 );
401 }
402
403 #[test]
404 fn test_wrapped_context_nested() {
405 use std::io::{Error, ErrorKind};
406 let err: Result<(), Error> = Err(Error::new(ErrorKind::Other, "file is no good"));
407
408 assert_eq!(
409 err.wrap_error_while("opening file".to_string())
410 .wrap_error_while("processing fish sticks".to_string())
411 .unwrap_err()
412 .to_string(),
413 "while processing fish sticks got error: while opening file got error: file is no good"
414 );
415 }
416
417 #[test]
418 fn test_in_context_of_type_context() {
419 let err = in_context_of("doing stuff".to_string(), || {
420 let err: Result<(), FooError> = Err(FooError::Foo {
421 context: Vec::new(),
422 });
423 err
424 });
425
426 assert_matches!(err.error_while("doing other stuff".to_string()), Err(FooError::Foo { context: c }) => assert_eq!(c, vec!["doing stuff".to_string(), "doing other stuff".to_string()]));
427 }
428
429 #[test]
430 fn test_wrap_in_context_of_type_context() {
431 fn foo() -> Result<(), FooError> {
432 wrap_in_context_of("doing stuff".to_string(), || {
433 Err(io::Error::new(io::ErrorKind::InvalidInput, "boom!"))?;
434 Ok(())
435 })?;
436 Ok(())
437 }
438
439 assert_matches!(foo().error_while("doing other stuff".to_string()), Err(FooError::IoError { context, .. }) => assert_eq!(context, vec!["doing stuff".to_string(), "doing other stuff".to_string()]));
440 }
441
442 #[test]
443 fn test_in_context_of_wrapped_context() {
444 use std::io::{Error, ErrorKind};
445
446 let err = in_context_of("opening file".to_string(), || {
447 let err: Result<(), Error> = Err(Error::new(ErrorKind::Other, "file is no good"));
448 err.map_error_context()
449 });
450
451 assert_eq!(
452 err.wrap_error_while("processing fish sticks".to_string())
453 .unwrap_err()
454 .to_string(),
455 "while processing fish sticks got error: while opening file got error: file is no good"
456 );
457 }
458}