anyhow_tracing/
error.rs

1use std::error::Error as StdError;
2use std::fmt;
3
4/// A type alias for `Result<T, Error>`.
5pub type Result<T> = std::result::Result<T, Error>;
6
7/// An error type that extends `anyhow::Error` with named fields.
8pub struct Error {
9    /// The underlying anyhow error
10    inner: anyhow::Error,
11    /// Named fields stored as key-value pairs
12    fields: Vec<(&'static str, Box<str>)>,
13}
14
15impl Error {
16    /// Create a new error from an anyhow error.
17    pub const fn new(error: anyhow::Error) -> Self {
18        Self {
19            inner: error,
20            fields: Vec::new(),
21        }
22    }
23
24    /// Create a new error with a message and optional fields.
25    pub fn msg<T: fmt::Display + fmt::Debug + Send + Sync + 'static>(msg: T) -> Self {
26        Self::new(anyhow::Error::msg(msg))
27    }
28
29    /// Add a named field to this error.
30    pub fn with_field<V: fmt::Display>(mut self, key: &'static str, value: V) -> Self {
31        self.fields.push((key, value.to_string().into_boxed_str()));
32        self
33    }
34
35    /// Add a named field with debug formatting to this error.
36    pub fn with_field_debug<V: fmt::Debug>(mut self, key: &'static str, value: V) -> Self {
37        self.fields
38            .push((key, format!("{:?}", value).into_boxed_str()));
39        self
40    }
41
42    /// Get the named fields.
43    pub fn fields(&self) -> &[(&'static str, Box<str>)] {
44        &self.fields
45    }
46
47    /// Get a specific field value by key, this is an O(n) operation.
48    pub fn get_field(&self, key: &str) -> Option<&str> {
49        self.fields
50            .iter()
51            .find(|(k, _)| *k == key)
52            .map(|(_, v)| v.as_ref())
53    }
54
55    /// Add context to this error, see [`anyhow::Context`] for more details.
56    pub fn context<C: fmt::Display + Send + Sync + 'static>(self, context: C) -> Self {
57        Self {
58            inner: self.inner.context(context),
59            fields: self.fields,
60        }
61    }
62
63    /// Add context to this error with a closure, see [`anyhow::Context`] for more details.
64    pub fn with_context<C, F>(self, f: F) -> Self
65    where
66        C: fmt::Display + Send + Sync + 'static,
67        F: FnOnce() -> C,
68    {
69        Self {
70            inner: self.inner.context(f()),
71            fields: self.fields,
72        }
73    }
74
75    /// Get the root cause of this error.
76    pub fn root_cause(&self) -> &dyn StdError {
77        self.inner.root_cause()
78    }
79
80    /// Get the chain of errors.
81    pub fn chain(&self) -> anyhow::Chain {
82        self.inner.chain()
83    }
84
85    /// Downcast the error to a concrete type.
86    pub fn downcast<E>(self) -> std::result::Result<E, Self>
87    where
88        E: fmt::Display + fmt::Debug + Send + Sync + 'static,
89    {
90        match self.inner.downcast::<E>() {
91            Ok(e) => Ok(e),
92            Err(inner) => Err(Self {
93                inner,
94                fields: self.fields,
95            }),
96        }
97    }
98
99    /// Downcast the error to a reference to a concrete type.
100    pub fn downcast_ref<E>(&self) -> Option<&E>
101    where
102        E: fmt::Display + fmt::Debug + Send + Sync + 'static,
103    {
104        self.inner.downcast_ref::<E>()
105    }
106
107    /// Downcast the error to a mutable reference to a concrete type.
108    pub fn downcast_mut<E>(&mut self) -> Option<&mut E>
109    where
110        E: fmt::Display + fmt::Debug + Send + Sync + 'static,
111    {
112        self.inner.downcast_mut::<E>()
113    }
114
115    /// Check if the error is of a particular type.
116    pub fn is<E>(&self) -> bool
117    where
118        E: fmt::Display + fmt::Debug + Send + Sync + 'static,
119    {
120        self.inner.is::<E>()
121    }
122}
123
124impl fmt::Display for Error {
125    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
126        // Display the main error
127        write!(f, "{}", self.inner)?;
128
129        // Add fields if any
130        if !self.fields.is_empty() {
131            write!(f, " [")?;
132            for (i, (key, value)) in self.fields.iter().enumerate() {
133                if i > 0 {
134                    write!(f, ", ")?;
135                }
136                write!(f, "{}={}", key, value)?;
137            }
138            write!(f, "]")?;
139        }
140
141        Ok(())
142    }
143}
144
145impl fmt::Debug for Error {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        // Display the main error
148        write!(f, "{:?}", self.inner)?;
149
150        // Add fields if any
151        if !self.fields.is_empty() {
152            write!(f, "\n\nFields:\n")?;
153            for (i, (key, value)) in self.fields.iter().enumerate() {
154                write!(f, "\t{}: {:?}", key, value)?;
155                if i < self.fields.len().saturating_sub(1) {
156                    write!(f, ",")?;
157                }
158                writeln!(f)?;
159            }
160        }
161
162        Ok(())
163    }
164}
165
166impl StdError for Error {
167    fn source(&self) -> Option<&(dyn StdError + 'static)> {
168        self.inner.source()
169    }
170}
171
172impl From<anyhow::Error> for Error {
173    fn from(error: anyhow::Error) -> Self {
174        Self::new(error)
175    }
176}
177
178impl From<String> for Error {
179    fn from(msg: String) -> Self {
180        Self::msg(msg)
181    }
182}
183
184impl From<&str> for Error {
185    fn from(msg: &str) -> Self {
186        Self::msg(msg.to_string())
187    }
188}
189
190/// Extension trait for adding context and fields to errors.
191pub trait Context<T> {
192    /// Wrap the error value with additional context.
193    fn context<C>(self, context: C) -> Result<T>
194    where
195        C: fmt::Display + fmt::Debug + Send + Sync + 'static;
196
197    /// Wrap the error value with additional context that is evaluated lazily.
198    fn with_context<C, F>(self, f: F) -> Result<T>
199    where
200        C: fmt::Display + fmt::Debug + Send + Sync + 'static,
201        F: FnOnce() -> C;
202
203    /// Add a named field to the error.
204    fn with_field<V>(self, key: &'static str, value: V) -> Result<T>
205    where
206        V: fmt::Display;
207
208    /// Add a named field with debug formatting to the error.
209    fn with_field_debug<V>(self, key: &'static str, value: V) -> Result<T>
210    where
211        V: fmt::Debug;
212}
213
214impl<T, E> Context<T> for std::result::Result<T, E>
215where
216    E: std::error::Error + Send + Sync + 'static,
217{
218    fn context<C>(self, context: C) -> Result<T>
219    where
220        C: fmt::Display + fmt::Debug + Send + Sync + 'static,
221    {
222        self.map_err(|e| Error::from(anyhow::Error::from(e)).context(context))
223    }
224
225    fn with_context<C, F>(self, f: F) -> Result<T>
226    where
227        C: fmt::Display + fmt::Debug + Send + Sync + 'static,
228        F: FnOnce() -> C,
229    {
230        self.map_err(|e| Error::from(anyhow::Error::from(e)).with_context(f))
231    }
232
233    fn with_field<V>(self, key: &'static str, value: V) -> Result<T>
234    where
235        V: fmt::Display,
236    {
237        self.map_err(|e| Error::from(anyhow::Error::from(e)).with_field(key, value))
238    }
239
240    fn with_field_debug<V>(self, key: &'static str, value: V) -> Result<T>
241    where
242        V: fmt::Debug,
243    {
244        self.map_err(|e| Error::from(anyhow::Error::from(e)).with_field_debug(key, value))
245    }
246}
247
248impl<T> Context<T> for Option<T> {
249    fn context<C>(self, context: C) -> Result<T>
250    where
251        C: fmt::Display + fmt::Debug + Send + Sync + 'static,
252    {
253        self.ok_or_else(|| Error::msg(context))
254    }
255
256    fn with_context<C, F>(self, f: F) -> Result<T>
257    where
258        C: fmt::Display + fmt::Debug + Send + Sync + 'static,
259        F: FnOnce() -> C,
260    {
261        self.ok_or_else(|| Error::msg(f()))
262    }
263
264    fn with_field<V>(self, key: &'static str, value: V) -> Result<T>
265    where
266        V: fmt::Display,
267    {
268        self.ok_or_else(|| Error::msg("None value").with_field(key, value))
269    }
270
271    fn with_field_debug<V>(self, key: &'static str, value: V) -> Result<T>
272    where
273        V: fmt::Debug,
274    {
275        self.ok_or_else(|| Error::msg("None value").with_field_debug(key, value))
276    }
277}