Skip to main content

agglayer_errors/
lib.rs

1#[cfg(feature = "tracing")]
2use std::fmt::Display;
3
4#[doc(hidden)] // Used by the macro
5pub use eyre;
6
7pub mod prelude {
8    #[cfg(feature = "tracing")]
9    pub use crate::ResultExt;
10}
11
12/// Extension trait for `Result` to log errors and warnings using the `tracing`
13/// crate.
14#[cfg(feature = "tracing")]
15pub trait ResultExt {
16    /// Log errors at error level with a static message.
17    ///
18    /// If you don't care about the error message, call eg. `log_err("")`.
19    fn log_err(self, msg: impl Display) -> Self;
20
21    /// Log errors at error level with a message generated by a closure.
22    fn log_err_with<F>(self, f: F) -> Self
23    where
24        F: FnOnce() -> String;
25
26    /// Log errors at warning level with a static message.
27    ///
28    /// If you don't care about the error message, call eg. `log_warn("")`.
29    fn log_warn(self, msg: impl Display) -> Self;
30
31    /// Log errors at warning level with a message generated by a closure.
32    fn log_warn_with<F>(self, f: F) -> Self
33    where
34        F: FnOnce() -> String;
35
36    /// Log errors at informational level with a static message.
37    ///
38    /// If you don't care about the error message, call eg. `log_warn("")`.
39    fn log_info(self, msg: impl Display) -> Self;
40
41    /// Log errors at informational level with a message generated by a closure.
42    fn log_info_with<F>(self, f: F) -> Self
43    where
44        F: FnOnce() -> String;
45
46    /// Log errors at debug level with a static message.
47    ///
48    /// If you don't care about the error message, call eg. `log_warn("")`.
49    fn log_debug(self, msg: impl Display) -> Self;
50
51    /// Log errors at debug level with a message generated by a closure.
52    fn log_debug_with<F>(self, f: F) -> Self
53    where
54        F: FnOnce() -> String;
55
56    /// Log errors at trace level with a static message.
57    ///
58    /// If you don't care about the error message, call eg. `log_warn("")`.
59    fn log_trace(self, msg: impl Display) -> Self;
60
61    /// Log errors at trace level with a message generated by a closure.
62    fn log_trace_with<F>(self, f: F) -> Self
63    where
64        F: FnOnce() -> String;
65}
66
67#[cfg(feature = "tracing")]
68impl<T, E> ResultExt for Result<T, E>
69where
70    E: std::fmt::Debug,
71{
72    fn log_err(self, msg: impl Display) -> Self {
73        self.inspect_err(|error| tracing::error!(?error, "{msg}"))
74    }
75
76    fn log_err_with<F>(self, msg: F) -> Self
77    where
78        F: FnOnce() -> String,
79    {
80        self.inspect_err(|error| tracing::error!(?error, "{}", msg()))
81    }
82
83    fn log_warn(self, msg: impl Display) -> Self {
84        self.inspect_err(|error| tracing::warn!(?error, "{msg}"))
85    }
86
87    fn log_warn_with<F>(self, msg: F) -> Self
88    where
89        F: FnOnce() -> String,
90    {
91        self.inspect_err(|error| tracing::warn!(?error, "{}", msg()))
92    }
93
94    fn log_info(self, msg: impl Display) -> Self {
95        self.inspect_err(|error| tracing::info!(?error, "{msg}"))
96    }
97
98    fn log_info_with<F>(self, msg: F) -> Self
99    where
100        F: FnOnce() -> String,
101    {
102        self.inspect_err(|error| tracing::info!(?error, "{}", msg()))
103    }
104
105    fn log_debug(self, msg: impl Display) -> Self {
106        self.inspect_err(|error| tracing::debug!(?error, "{msg}"))
107    }
108
109    fn log_debug_with<F>(self, msg: F) -> Self
110    where
111        F: FnOnce() -> String,
112    {
113        self.inspect_err(|error| tracing::debug!(?error, "{}", msg()))
114    }
115
116    fn log_trace(self, msg: impl Display) -> Self {
117        self.inspect_err(|error| tracing::trace!(?error, "{msg}"))
118    }
119
120    fn log_trace_with<F>(self, msg: F) -> Self
121    where
122        F: FnOnce() -> String,
123    {
124        self.inspect_err(|error| tracing::trace!(?error, "{}", msg()))
125    }
126}
127
128/// Match an `eyre::Report` against multiple error types and patterns.
129///
130/// # Example
131/// ```rust
132/// use agglayer_errors::match_err;
133/// use eyre::Context as _;
134///
135/// #[derive(Debug, thiserror::Error)]
136/// #[error("Foo")]
137/// struct Foo;
138///
139/// #[derive(Debug, thiserror::Error)]
140/// #[error("Bar")]
141/// struct Bar(&'static str);
142///
143/// let err: eyre::Report = Foo.into();
144/// let s = match_err!(err,
145///     type<Foo>(_) => "foo".to_string(),
146///     type<Bar>(Bar("foo")) => "bar/foo".to_string(),
147///     type<Bar>(Bar(s)) => format!("bar({s})"),
148///     else(_) => "other".to_string(),
149/// );
150/// ```
151#[macro_export]
152macro_rules! match_err {
153    (
154        $expr:expr
155        $(, type<$ty:ty>($pat:pat) => $handler:expr)*
156        $(, else($default_pat:pat) => $default_handler:expr)?
157        $(,)?
158    ) => {
159        'result: {
160            let expr: $crate::eyre::Report = $expr;
161            $(
162                #[allow(unused_variables)] // Pattern variables are unused from this downcast_ref
163                if let Some($pat) = expr.downcast_ref::<$ty>() {
164                    let Ok($pat) = expr.downcast::<$ty>() else {
165                        // This should never happen because we just checked downcast_ref
166                        unreachable!("downcast_ref succeeded but downcast failed");
167                    };
168                    let res = $handler;
169                    break 'result res;
170                }
171            )*
172            $(
173                let $default_pat = expr;
174                $default_handler
175            )?
176        }
177    };
178}
179
180/// Match an `eyre::Result` against multiple `Ok` patterns and multiple error
181/// types and patterns.
182///
183/// If no error pattern matches, propagate the error up.
184///
185/// # Example
186/// ```rust
187/// use agglayer_errors::match_res;
188/// use eyre::Context as _;
189///
190/// #[derive(Debug, thiserror::Error)]
191/// #[error("Foo")]
192/// struct Foo;
193///
194/// #[derive(Debug, thiserror::Error)]
195/// #[error("Bar")]
196/// struct Bar(&'static str);
197///
198/// fn main() -> eyre::Result<()> {
199///     let res: eyre::Result<u32> = Err(Bar("foo").into());
200///     let s = match_res!(res,
201///         Ok(42) => "the answer".to_string(),
202///         Ok(n) => format!("ok({n})"),
203///         Err(type<Bar>(Bar("foo"))) => "bar/foo".to_string(),
204///         Err(type<Bar>(b)) => format!("bar({})", b.0),
205///         // By default, propagate the error up
206///     );
207///
208///     let res2: eyre::Result<u32> = Err(Foo.into());
209///     let s2 = match_res!(res2,
210///         Ok(42) => "the answer".to_string(),
211///         Ok(n) => format!("ok({n})"),
212///         Err(type<Bar>(Bar("foo"))) => "bar/foo".to_string(),
213///         Err(type<Bar>(b)) => format!("bar({})", b.0),
214///         Err(else(err)) => format!("other({err})"), // Catch-all, do not propagate
215///     );
216///
217///     Ok(())
218/// }
219/// ```
220#[macro_export]
221macro_rules! match_res {
222    (
223        $expr:expr
224        $(, Ok($ok:pat) => $ok_handler:expr)*
225        $(, Err(type<$err_ty:ty>($err:pat)) => $err_handler:expr)*
226        $(,)?
227    ) => {
228        $crate::match_res!($expr
229            $(, Ok($ok) => $ok_handler)*
230            $(, Err(type<$err_ty>($err)) => $err_handler)*
231            , Err(else(err)) => { return Err(err) }
232        )
233    };
234
235    (
236        $expr:expr
237        $(, Ok($ok:pat) => $ok_handler:expr)*
238        $(, Err(type<$err_ty:ty>($err:pat)) => $err_handler:expr)*
239        , Err(else($default_err:pat)) => $default_handler:expr
240        $(,)?
241    ) => {
242        match $expr {
243            $(
244                Ok($ok) => $ok_handler,
245            )*
246            Err(err) => $crate::match_err!(err,
247                $(
248                    type<$err_ty>($err) => $err_handler,
249                )*
250                else($default_err) => $default_handler,
251            ),
252        }
253    };
254}
255
256#[cfg(test)]
257mod tests {
258    use eyre::Context as _;
259
260    #[derive(Debug, thiserror::Error)]
261    #[error("Foo")]
262    struct Foo;
263
264    #[derive(Debug, thiserror::Error)]
265    #[error("Bar")]
266    struct Bar(&'static str);
267
268    #[derive(Debug, thiserror::Error)]
269    #[error("Quux")]
270    struct Quux;
271
272    fn use_match_err_to_make_a_value<E: Into<eyre::Report>>(err: E) -> String {
273        let err: eyre::Report = err.into();
274        match_err!(err,
275            type<Foo>(_) => "foo".to_string(),
276            type<Bar>(Bar("foo")) => "bar/foo".to_string(),
277            type<Bar>(Bar(s)) => format!("bar({s})"),
278            else(_) => "other".to_string(),
279        )
280    }
281
282    #[test]
283    fn match_err_to_make_a_value() {
284        // Basic tests
285        assert_eq!(use_match_err_to_make_a_value(Foo), "foo");
286        assert_eq!(use_match_err_to_make_a_value(Bar("baz")), "bar(baz)");
287        assert_eq!(use_match_err_to_make_a_value(Bar("foo")), "bar/foo");
288
289        // Default works
290        assert_eq!(use_match_err_to_make_a_value(Quux), "other");
291        assert_eq!(use_match_err_to_make_a_value(eyre::eyre!("baz")), "other");
292
293        // Wrapping with context has no impact
294        assert_eq!(
295            use_match_err_to_make_a_value(eyre::Report::from(Foo).wrap_err("with some context")),
296            "foo"
297        );
298
299        // Wrapping with another match-able error shows the first match among the
300        // match_err branches.
301        // Not necessarily the best choice, but it should be good enough for now.
302        assert_eq!(
303            use_match_err_to_make_a_value(eyre::Report::from(Foo).wrap_err(Bar("baz"))),
304            "foo"
305        );
306        assert_eq!(
307            use_match_err_to_make_a_value(eyre::Report::from(Bar("baz")).wrap_err(Foo)),
308            "foo"
309        );
310
311        // Wrapping an unknown error with a match-able error does show the match-able
312        // error
313        assert_eq!(
314            use_match_err_to_make_a_value(eyre::Report::from(Quux).wrap_err(Foo)),
315            "foo"
316        );
317        assert_eq!(
318            use_match_err_to_make_a_value(eyre::Report::from(Quux).wrap_err(Foo).wrap_err(Quux)),
319            "foo"
320        );
321    }
322
323    fn use_match_err_to_run_code<E: Into<eyre::Report>>(err: E) -> String {
324        let err: eyre::Report = err.into();
325        let mut res = "other".to_string();
326        match_err!(err,
327            type<Foo>(_) => {
328                res = "foo".to_string();
329            },
330            type<Bar>(Bar("foo")) => {
331                res = "bar/foo".to_string();
332            },
333            type<Bar>(Bar(s)) => {
334                res = format!("bar({s})");
335            },
336            // No @default branch, the result is () everywhere
337        );
338        res
339    }
340
341    #[test]
342    fn match_err_to_run_code() {
343        // Basic tests
344        assert_eq!(use_match_err_to_run_code(Foo), "foo");
345        assert_eq!(use_match_err_to_run_code(Bar("baz")), "bar(baz)");
346        assert_eq!(use_match_err_to_run_code(Bar("foo")), "bar/foo");
347
348        // Default works
349        assert_eq!(use_match_err_to_run_code(Quux), "other");
350        assert_eq!(use_match_err_to_run_code(eyre::eyre!("baz")), "other");
351
352        // Wrapping with context has no impact
353        assert_eq!(
354            use_match_err_to_run_code(eyre::Report::from(Foo).wrap_err("with some context")),
355            "foo"
356        );
357
358        // Wrapping with another match-able error shows the first match among the
359        // match_err branches.
360        // Not necessarily the best choice, but it should be good enough for now.
361        assert_eq!(
362            use_match_err_to_run_code(eyre::Report::from(Foo).wrap_err(Bar("baz"))),
363            "foo"
364        );
365        assert_eq!(
366            use_match_err_to_run_code(eyre::Report::from(Bar("baz")).wrap_err(Foo)),
367            "foo"
368        );
369
370        // Wrapping an unknown error with a match-able error does show the match-able
371        // error
372        assert_eq!(
373            use_match_err_to_run_code(eyre::Report::from(Quux).wrap_err(Foo)),
374            "foo"
375        );
376        assert_eq!(
377            use_match_err_to_run_code(eyre::Report::from(Quux).wrap_err(Foo).wrap_err(Quux)),
378            "foo"
379        );
380    }
381
382    fn use_match_res_with_default(res: eyre::Result<u32>) -> String {
383        match_res!(res,
384            Ok(42) => "the answer".to_string(),
385            Ok(n) => format!("ok({n})"),
386            Err(type<Bar>(Bar("foo"))) => "bar/foo".to_string(),
387            Err(type<Bar>(b)) => format!("bar({})", b.0),
388            Err(else(err)) => format!("other({err})"),
389        )
390    }
391
392    #[test]
393    fn match_err_with_default() {
394        assert_eq!(use_match_res_with_default(Ok(42)), "the answer");
395        assert_eq!(use_match_res_with_default(Ok(7)), "ok(7)");
396        assert_eq!(
397            use_match_res_with_default(Err(Bar("foo").into())),
398            "bar/foo"
399        );
400        assert_eq!(
401            use_match_res_with_default(Err(Bar("baz").into())),
402            "bar(baz)"
403        );
404        assert_eq!(use_match_res_with_default(Err(Foo.into())), "other(Foo)");
405
406        assert_eq!(
407            use_match_res_with_default(Err(eyre::Report::from(Bar("baz"))).wrap_err(Quux)),
408            "bar(baz)"
409        );
410    }
411
412    fn use_match_res_without_default(res: eyre::Result<u32>) -> eyre::Result<String> {
413        match_res!(res,
414            Ok(42) => Ok("the answer".to_string()),
415            Ok(n) => Ok(format!("ok({n})")),
416            Err(type<Bar>(Bar("foo"))) => Ok("bar/foo".to_string()),
417            Err(type<Bar>(b)) => Ok(format!("bar({})", b.0)),
418            // No @default branch, will propagate other errors up
419        )
420    }
421
422    #[test]
423    fn match_err_without_default() {
424        assert_eq!(use_match_res_without_default(Ok(42)).unwrap(), "the answer");
425        assert_eq!(use_match_res_without_default(Ok(7)).unwrap(), "ok(7)");
426        assert_eq!(
427            use_match_res_without_default(Err(Bar("foo").into())).unwrap(),
428            "bar/foo"
429        );
430        assert_eq!(
431            use_match_res_without_default(Err(Bar("baz").into())).unwrap(),
432            "bar(baz)"
433        );
434        assert!(use_match_res_without_default(Err(Foo.into())).is_err());
435
436        assert_eq!(
437            use_match_res_without_default(Err(eyre::Report::from(Bar("baz"))).wrap_err(Quux))
438                .unwrap(),
439            "bar(baz)"
440        );
441    }
442}