anyerror/
any_error_impl.rs

1use std::error::Error;
2use std::fmt::Display;
3use std::fmt::Formatter;
4
5use serde::Deserialize;
6use serde::Serialize;
7
8/// AnyError is a serializable wrapper `Error`.
9///
10/// It is can be used to convert other `Error` into a serializable Error for transmission,
11/// with most necessary info kept.
12///
13/// ```
14/// # use std::fmt;
15/// # use anyerror::AnyError;
16/// let err = fmt::Error {};
17/// let e = AnyError::new(&err).add_context(|| "running test");
18/// assert_eq!(
19///     "core::fmt::Error: an error occurred when formatting an argument while: running test",
20///     e.to_string()
21/// );
22/// ```
23#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
24#[cfg_attr(
25    feature = "rkyv",
26    derive(rkyv::Archive, rkyv::Deserialize, rkyv::Serialize),
27    archive(
28        check_bytes,
29        bound(serialize = "__S: rkyv::ser::ScratchSpace + rkyv::ser::Serializer")
30    ),
31    archive_attr(check_bytes(
32        bound = "__C: rkyv::validation::ArchiveContext, <__C as rkyv::Fallible>::Error: std::error::Error"
33    ),)
34)]
35pub struct AnyError {
36    typ: Option<String>,
37
38    msg: String,
39
40    #[cfg_attr(feature = "rkyv", omit_bounds, archive_attr(omit_bounds))]
41    source: Option<Box<AnyError>>,
42
43    /// context provides additional info about the context when a error happened.
44    context: Vec<String>,
45
46    backtrace: Option<String>,
47}
48
49impl Display for AnyError {
50    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
51        if let Some(t) = &self.typ {
52            write!(f, "{}: ", t)?;
53        }
54
55        write!(f, "{}", self.msg)?;
56
57        for (i, ctx) in self.context.iter().enumerate() {
58            if f.alternate() {
59                writeln!(f)?;
60                write!(f, "    while: {}", ctx)?;
61            } else {
62                if i > 0 {
63                    write!(f, ",")?;
64                }
65                write!(f, " while: {}", ctx)?;
66            }
67        }
68
69        #[allow(clippy::collapsible_else_if)]
70        if f.alternate() {
71            if let Some(ref s) = self.source {
72                writeln!(f)?;
73                write!(f, "source: ")?;
74                write!(f, "{:#}", s)?;
75            }
76        } else {
77            if let Some(ref s) = self.source {
78                write!(f, "; source: {}", s)?;
79            }
80        }
81
82        Ok(())
83    }
84}
85
86impl std::fmt::Debug for AnyError {
87    fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result {
88        <Self as Display>::fmt(self, f)?;
89
90        if let Some(ref b) = self.backtrace {
91            write!(f, "\nbacktrace:\n{}", b)?;
92        }
93        Ok(())
94    }
95}
96
97impl std::error::Error for AnyError {
98    fn source(&self) -> Option<&(dyn Error + 'static)> {
99        match &self.source {
100            Some(x) => Some(x.as_ref()),
101            None => None,
102        }
103    }
104}
105
106#[cfg(feature = "anyhow")]
107impl From<anyhow::Error> for AnyError {
108    fn from(a: anyhow::Error) -> Self {
109        // NOTE: this does not work: anyhow::Error does not impl std::Error;
110        //       backtrace is lost after converting it to &dyn Error with stable rust.
111        // AnyError::from_dyn(a.as_ref(), None)
112
113        let source = a.source().map(|x| Box::new(AnyError::from_dyn(x, None)));
114        #[cfg(feature = "backtrace")]
115        let bt = Some({
116            let bt = a.backtrace();
117            format!("{:?}", bt)
118        });
119
120        #[cfg(not(feature = "backtrace"))]
121        let bt = None;
122
123        Self {
124            typ: None,
125            msg: a.to_string(),
126            source,
127            context: vec![],
128            backtrace: bt,
129        }
130    }
131}
132
133impl<E> From<&E> for AnyError
134where E: std::error::Error + 'static
135{
136    fn from(e: &E) -> Self {
137        AnyError::new(e)
138    }
139}
140
141impl AnyError {
142    /// Create an ad-hoc error with a string message
143    pub fn error(msg: impl ToString) -> Self {
144        Self {
145            typ: None,
146            msg: msg.to_string(),
147            source: None,
148            context: vec![],
149            backtrace: None,
150        }
151    }
152
153    /// Convert some `Error` to AnyError.
154    ///
155    /// - If there is a `source()` in the input error, it is also converted to AnyError, recursively.
156    /// - A new backtrace will be built if there is not.
157    pub fn new<E>(e: &E) -> Self
158    where E: Error + 'static {
159        let q: &(dyn Error + 'static) = e;
160
161        let x = q.downcast_ref::<AnyError>();
162        let typ = match x {
163            Some(ae) => ae.typ.clone(),
164            None => Some(std::any::type_name::<E>().to_string()),
165        };
166
167        Self::from_dyn(e, typ)
168    }
169
170    pub fn from_dyn(e: &(dyn Error + 'static), typ: Option<String>) -> Self {
171        let x = e.downcast_ref::<AnyError>();
172
173        match x {
174            Some(ae) => ae.clone(),
175            None => {
176                #[cfg(feature = "backtrace")]
177                let bt = crate::bt::error_backtrace_str(e);
178
179                #[cfg(not(feature = "backtrace"))]
180                let bt = None;
181
182                let source = e.source().map(|x| Box::new(AnyError::from_dyn(x, None)));
183
184                Self {
185                    typ,
186                    msg: e.to_string(),
187                    source,
188                    context: vec![],
189                    backtrace: bt,
190                }
191            }
192        }
193    }
194
195    #[cfg(feature = "backtrace")]
196    #[must_use]
197    pub fn with_backtrace(mut self) -> Self {
198        if self.backtrace.is_some() {
199            return self;
200        }
201
202        self.backtrace = Some(crate::bt::new_str());
203        self
204    }
205
206    #[must_use]
207    pub fn add_context<D: Display, F: FnOnce() -> D>(mut self, ctx: F) -> Self {
208        self.context.push(format!("{}", ctx()));
209        self
210    }
211
212    /// Update the type of the error.
213    pub fn with_type<T: ToString>(mut self, typ: Option<T>) -> Self {
214        self.typ = typ.map(|x| x.to_string());
215        self
216    }
217
218    /// Return the type of the error.
219    pub fn get_type(&self) -> Option<&str> {
220        self.typ.as_ref().map(|x| x as _)
221    }
222
223    pub fn backtrace(&self) -> Option<&str> {
224        self.backtrace.as_ref().map(|x| x as _)
225    }
226}
227
228/// Generate backtrace in string if feature `backtrace` is enabled.
229/// Otherwise it returns None.
230pub fn backtrace_str() -> Option<String> {
231    #[cfg(feature = "backtrace")]
232    return Some(crate::bt::new_str());
233
234    #[cfg(not(feature = "backtrace"))]
235    return None;
236}