1use std::{
2 error::Error,
3 fmt::{self, Display, Write},
4};
5
6pub trait ReportExt {
8 fn report(&self) -> impl Display;
10}
11
12impl<E> ReportExt for E
13where
14 E: Error,
15{
16 fn report(&self) -> impl Display {
17 ReportImpl(self)
18 }
19}
20
21struct ReportImpl<E>(E);
24
25impl<E: Error> Display for ReportImpl<E> {
26 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
27 let error = &self.0;
28
29 write!(f, "{error}")?;
30
31 if let Some(cause) = error.source() {
32 write!(f, "\n\nCaused by:")?;
33 let multiple = cause.source().is_some();
34 for (n, error) in anyhow::Chain::new(cause).enumerate() {
35 writeln!(f)?;
36 let mut indented = Indented {
37 inner: f,
38 number: multiple.then_some(n),
39 started: false,
40 };
41 write!(indented, "{error}")?;
42 }
43 }
44
45 Ok(())
46 }
47}
48
49struct Indented<'a, D> {
50 inner: &'a mut D,
51 number: Option<usize>,
52 started: bool,
53}
54
55impl<T> Write for Indented<'_, T>
56where
57 T: Write,
58{
59 fn write_str(&mut self, s: &str) -> fmt::Result {
60 for (i, line) in s.split('\n').enumerate() {
61 if !self.started {
62 self.started = true;
63 match self.number {
64 Some(number) => write!(self.inner, "{number: >5}: ")?,
65 None => self.inner.write_str(" ")?,
66 }
67 } else if i > 0 {
68 self.inner.write_char('\n')?;
69 if self.number.is_some() {
70 self.inner.write_str(" ")?;
71 } else {
72 self.inner.write_str(" ")?;
73 }
74 }
75
76 self.inner.write_str(line)?;
77 }
78
79 Ok(())
80 }
81}