error_report/
lib.rs

1mod clean;
2
3pub use clean::{CleanedErrorText, CleanedErrors};
4
5use core::fmt;
6use std::error::Error;
7
8/// Provides the `report` method for `std::error::Error`,
9/// converting the error to a `Report`.
10pub trait Reportable {
11    fn report(self) -> Report<Self>
12    where
13        Self: std::error::Error,
14        Self: std::marker::Sized;
15}
16
17impl<E: Error> Reportable for E {
18    fn report(self) -> Report<Self>
19    where
20        Self: std::error::Error,
21        Self: std::marker::Sized,
22    {
23        Report::new(self)
24    }
25}
26
27/// AsRefError is needed because `anyhow::Error` only implements `AsRef<dyn Error>`, not `Error`,
28/// but `&dyn Error` does not implement `AsRef<dyn Error>` because `AsRef` doesn't have a blanket
29/// implementation (https://doc.rust-lang.org/std/convert/trait.AsRef.html#reflexivity).
30pub trait AsRefError {
31    fn as_ref_error(&self) -> &dyn Error;
32}
33
34impl<E: Error> AsRefError for E {
35    fn as_ref_error(&self) -> &dyn Error {
36        self
37    }
38}
39
40// This implementation is unfortunately not possible.
41/*
42impl<E: AsRef<dyn Error>> AsRefError for E {
43    fn as_ref_error(&self) -> &dyn Error {
44        self.as_ref()
45    }
46}
47*/
48
49/// Report prints an error and all its sources.
50///
51/// Source messages will be cleaned using `CleanedErrors` to remove duplication
52/// from errors that include their source's message in their own message.
53///
54/// The debug implementation prints each error on a separate line, while the display
55/// implementation prints all errors on one line separated by a colon.
56/// Using alternate formatting (`{:#}`) is identical to the debug implementation.
57///
58/// The debug implementation is intended for cases where errors are debug printed,
59/// for example returning an error from main or using `expect` on `Result`:
60///
61/// ```should_panic
62/// use error_report::Report;
63///
64/// fn func1() -> Result<(), std::io::Error> {
65///     Err(std::io::Error::other("oh no!"))
66/// }
67///
68/// fn main() -> Result<(), Report<impl std::error::Error>> {
69///     func1()?;
70///     Ok(())
71/// }
72/// ```
73///
74/// ```should_panic
75/// # use error_report::Report;
76/// let i: i8 = 256.try_into().map_err(Report::from).expect("conversion error");
77/// ```
78pub struct Report<E: AsRefError>(E);
79
80impl<E: AsRefError> From<E> for Report<E> {
81    fn from(value: E) -> Self {
82        Self(value)
83    }
84}
85
86impl<E: AsRefError> Report<E> {
87    /// Construct a new `Report` from an error.
88    pub fn new(err: E) -> Self {
89        Self::from(err)
90    }
91
92    fn format(&self, f: &mut fmt::Formatter<'_>, multiline: bool) -> fmt::Result {
93        let cleaned_texts = CleanedErrorText::new(self.0.as_ref_error())
94            .filter(|(_, t, _)| !t.is_empty())
95            .enumerate();
96
97        if !multiline {
98            for (i, (_, text, _)) in cleaned_texts {
99                if i > 0 {
100                    write!(f, ": ")?;
101                }
102                write!(f, "{text}")?;
103            }
104        } else {
105            for (i, (_, text, _)) in cleaned_texts {
106                if i == 0 {
107                    write!(f, "{text}")?;
108                } else {
109                    if i == 1 {
110                        write!(f, "\n\nCaused by:\n")?;
111                    }
112                    writeln!(f, "    {i}. {text}")?;
113                }
114            }
115        }
116
117        Ok(())
118    }
119}
120
121impl<E: AsRefError> fmt::Debug for Report<E> {
122    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123        self.format(f, true)
124    }
125}
126
127impl<E: AsRefError> fmt::Display for Report<E> {
128    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129        self.format(f, f.alternate())
130    }
131}
132
133/// Ref holds an error type that is `AsRef<dyn Error>`, allowing it
134/// to be used with `Report` without having to implement `AsRefError` on it.
135pub struct Ref<E>(E);
136
137impl<E: AsRef<dyn Error>> AsRefError for Ref<E> {
138    fn as_ref_error(&self) -> &dyn Error {
139        self.0.as_ref()
140    }
141}
142
143impl<E: AsRef<dyn Error>> Report<Ref<E>> {
144    /// Construct a new `Report` from a type that implements `AsRef<dyn Error>`.
145    pub fn from_ref(value: E) -> Self {
146        Self::new(Ref(value))
147    }
148}
149
150impl<E: AsRef<dyn Error>> From<E> for Report<Ref<E>> {
151    fn from(value: E) -> Self {
152        Self::from_ref(value)
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159    use anyhow::Context;
160
161    #[test]
162    fn test_conversion() {
163        fn anyhow_fn() -> anyhow::Result<()> {
164            Err(anyhow::anyhow!("oh no!"))
165        }
166
167        fn anyhow_caller() -> Result<(), super::Report<impl AsRefError>> {
168            anyhow_fn().context("fn failed")?;
169
170            Ok(())
171        }
172
173        #[derive(Debug)]
174        struct CustomError();
175
176        impl std::fmt::Display for CustomError {
177            fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178                write!(f, "custom error")
179            }
180        }
181
182        impl std::error::Error for CustomError {}
183
184        fn custom_fn() -> Result<(), CustomError> {
185            Err(CustomError())
186        }
187
188        fn custom_caller() -> Result<(), super::Report<CustomError>> {
189            custom_fn()?;
190
191            Ok(())
192        }
193
194        let err = anyhow_caller().expect_err("function did not return error");
195
196        let normal_string = format!("{}", err);
197        let alt_string = format!("{:#}", err);
198        let debug_string = format!("{:?}", err);
199
200        assert_eq!(normal_string, "fn failed: oh no!");
201        assert_eq!(alt_string, "fn failed\n\nCaused by:\n    1. oh no!\n");
202        assert_eq!(debug_string, "fn failed\n\nCaused by:\n    1. oh no!\n");
203
204        let err = custom_caller().expect_err("function did not return error");
205
206        let normal_string = format!("{}", err);
207        let alt_string = format!("{:#}", err);
208        let debug_string = format!("{:?}", err);
209
210        assert_eq!(normal_string, "custom error");
211        assert_eq!(alt_string, "custom error");
212        assert_eq!(debug_string, "custom error");
213
214        let err = custom_fn().expect_err("function did not return error");
215        let report = Report::from(&err);
216        let normal_string = format!("{}", report);
217        let alt_string = format!("{:#}", report);
218        let debug_string = format!("{:?}", report);
219
220        assert_eq!(normal_string, "custom error");
221        assert_eq!(alt_string, "custom error");
222        assert_eq!(debug_string, "custom error");
223        _ = err
224    }
225}