thiserror_context/
composition.rs

1/// Allows for converting from a child error type to a parent
2/// error type while preserving any context on the child error.
3///
4/// This is intended to be used when:
5///   1. Both Source and Target are context enriched thiserror enums
6///   2. Source is a variant of Target's inner error
7///
8/// ** Example **
9/// ```
10/// use thiserror::Error;
11/// use thiserror_context::{Context, impl_context, impl_from_carry_context};
12///
13/// // Some inner error type
14/// #[derive(Debug, Error)]
15/// pub enum InnerError {
16///     #[error("dummy")]
17///     Dummy,
18/// }
19/// impl_context!(Inner(InnerError));
20///
21/// // And some outer error type, which contains
22/// // a variant of the inner error type
23/// #[derive(Debug, Error)]
24/// pub enum OuterError {
25///     #[error("inner error")]
26///     // we explicitly do _not_ use #[from] here, instead
27///     // opting to use the macro to create the conversion
28///     // and handle the context propagation.
29///     Inner(Inner),
30/// }
31/// impl_context!(Outer(OuterError));
32///
33/// // Then we use the macro to implement the conversion
34/// // from Inner to Outer
35/// impl_from_carry_context!(Inner, Outer, OuterError::Inner);
36///
37/// fn inner_call() -> Result<(), Inner> {
38///     Err(InnerError::Dummy)
39///         .context("context on inner")
40/// }
41///
42/// fn outer_call() -> Result<(), Outer> {
43///     inner_call().context("context on outer")
44/// }
45///
46/// let r = outer_call();
47/// assert!(r.is_err());
48/// let e = r.unwrap_err();
49/// assert_eq!(format!("{:?}",e), r#"Inner(Dummy)
50///
51/// Caused by:
52///     0: context on outer
53///     1: context on inner
54/// "#);
55/// ```
56#[macro_export]
57macro_rules! impl_from_carry_context {
58    ($source: ident, $target: ident, $variant: path) => {
59        impl From<$source> for $target {
60            fn from(mut value: $source) -> Self {
61                let mut contexts = vec![];
62
63                let inner = loop {
64                    match value {
65                        $source::Base(x) => break x,
66                        $source::Context { context, error } => {
67                            contexts.push(context);
68                            value = *error;
69                        }
70                    }
71                };
72                let inner = $source::Base(inner);
73
74                let mut x = $target::Base($variant(inner));
75
76                for ctx in contexts.into_iter().rev() {
77                    x = $target::Context {
78                        context: ctx,
79                        error: Box::new(x),
80                    };
81                }
82
83                x
84            }
85        }
86    };
87}
88
89pub use impl_from_carry_context;