fedimint_core/util/
error.rs

1use std::fmt::Formatter;
2use std::{error, fmt};
3
4/// A wrapper with `fmt::Display` for any `E : Error` that will print chain
5/// of causes
6pub struct FmtErrorCompact<'e, E>(pub &'e E);
7
8impl<E> fmt::Display for FmtErrorCompact<'_, E>
9where
10    E: error::Error,
11{
12    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
13        let mut source_opt: Option<&(dyn std::error::Error)> = Some(self.0);
14
15        while source_opt.is_some() {
16            let source = source_opt.take().expect("Just checked");
17            f.write_fmt(format_args!("{source}"))?;
18
19            source_opt = source.source();
20            if source_opt.is_some() {
21                f.write_str(": ")?;
22            }
23        }
24        Ok(())
25    }
26}
27
28/// A wrapper with `fmt::Display` for [`anyhow::Error`] that will print
29/// chain of causes
30pub struct FmtCompactErrorAnyhow<'e>(pub &'e anyhow::Error);
31
32impl fmt::Display for FmtCompactErrorAnyhow<'_> {
33    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34        // <https://docs.rs/anyhow/latest/anyhow/struct.Error.html#display-representations>
35        f.write_fmt(format_args!("{:#}", self.0))
36    }
37}
38
39/// Simple utility trait to print error chains
40pub trait FmtCompact<'a> {
41    type Report: fmt::Display + 'a;
42    fn fmt_compact(self) -> Self::Report;
43}
44
45/// Simple utility trait to print error chains (for [`anyhow::Error`])
46pub trait FmtCompactAnyhow<'a> {
47    type Report: fmt::Display + 'a;
48    fn fmt_compact_anyhow(self) -> Self::Report;
49}
50
51impl<'e, E> FmtCompact<'e> for &'e E
52where
53    E: error::Error,
54{
55    type Report = FmtErrorCompact<'e, E>;
56
57    fn fmt_compact(self) -> Self::Report {
58        FmtErrorCompact(self)
59    }
60}
61
62impl<'e> FmtCompactAnyhow<'e> for &'e anyhow::Error {
63    type Report = FmtCompactErrorAnyhow<'e>;
64
65    fn fmt_compact_anyhow(self) -> Self::Report {
66        FmtCompactErrorAnyhow(self)
67    }
68}
69
70#[cfg(test)]
71mod test {
72    use std::io;
73
74    use anyhow::Context as _;
75
76    use super::*;
77
78    #[test]
79    pub(crate) fn fmt_compact_anyhow_sanity() {
80        fn foo() -> anyhow::Result<()> {
81            anyhow::bail!("Foo")
82        }
83
84        fn bar() -> anyhow::Result<()> {
85            foo().context("xyz")?;
86            unreachable!()
87        }
88
89        let Err(err) = bar() else {
90            panic!("abc");
91        };
92        assert_eq!(err.fmt_compact_anyhow().to_string(), "xyz: Foo");
93    }
94
95    #[test]
96    pub(crate) fn fmt_compact_sanity() {
97        fn foo() -> Result<(), io::Error> {
98            Err(io::Error::other("d"))
99        }
100
101        #[derive(Debug)]
102        struct BarError {
103            inner: io::Error,
104        }
105
106        impl std::error::Error for BarError {
107            fn source(&self) -> Option<&(dyn error::Error + 'static)> {
108                Some(&self.inner)
109            }
110        }
111
112        impl fmt::Display for BarError {
113            fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
114                f.write_str("BarError")
115            }
116        }
117
118        fn bar() -> Result<(), BarError> {
119            Err(BarError {
120                inner: foo().expect_err("wat"),
121            })?;
122            unreachable!()
123        }
124
125        let Err(err) = bar() else {
126            panic!("abc");
127        };
128        assert_eq!(err.fmt_compact().to_string(), "BarError: d");
129    }
130}