1
//! The Report type which reports errors nicely
2

            
3
use std::fmt::{self, Debug, Display};
4

            
5
/// Wraps any Error, providing a nicely-reporting Display impl
6
#[derive(Debug, Copy, Clone)]
7
#[allow(clippy::exhaustive_structs)] // this is a transparent wrapper
8
pub struct Report<E>(pub E)
9
where
10
    E: AsRef<dyn std::error::Error>;
11

            
12
impl<E> Display for Report<E>
13
where
14
    E: AsRef<dyn std::error::Error>,
15
{
16
6
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
17
6
        /// Non-generic inner function avoids code bloat
18
6
        fn inner(mut e: &dyn std::error::Error, f: &mut fmt::Formatter) -> fmt::Result {
19
6
            write!(f, "error")?;
20
6
            let mut last = String::new();
21
12
            loop {
22
12
                let this = e.to_string();
23
12
                if !last.contains(&this) {
24
9
                    write!(f, ": {}", &this)?;
25
6
                }
26
12
                last = this;
27
6

            
28
12
                if let Some(ne) = e.source() {
29
6
                    e = ne;
30
6
                } else {
31
6
                    break;
32
6
                }
33
6
            }
34
6
            Ok(())
35
6
        }
36
6

            
37
6
        inner(self.0.as_ref(), f)
38
6
    }
39
}
40

            
41
/// Report the error E to stderr, and exit the program
42
///
43
/// Does not return.  Return type is any type R, for convenience with eg `unwrap_or_else`.
44
#[allow(clippy::print_stderr)] // this is the point of this function
45
pub fn report_and_exit<E, R>(e: E) -> R
46
where
47
    E: AsRef<dyn std::error::Error>,
48
{
49
    /// Non-generic inner function avoids code bloat
50
    fn eprint_progname() {
51
        if let Some(progname) = std::env::args().next() {
52
            eprint!("{}: ", progname);
53
        }
54
    }
55

            
56
    eprint_progname();
57
    eprintln!("{}", Report(e));
58
    std::process::exit(127)
59
}
60

            
61
#[cfg(test)]
62
mod test {
63
    use super::*;
64
    use std::error::Error as StdError;
65
    use std::io;
66
    use thiserror::Error;
67

            
68
    #[derive(Error, Debug)]
69
    #[error("terse")]
70
    struct TerseError {
71
        #[from]
72
        source: Box<dyn StdError>,
73
    }
74

            
75
    #[derive(Error, Debug)]
76
    #[error("verbose - {source}")]
77
    struct VerboseError {
78
        #[from]
79
        source: Box<dyn StdError>,
80
    }
81

            
82
    #[derive(Error, Debug)]
83
    #[error("shallow")]
84
    struct ShallowError;
85

            
86
    fn chk<E: StdError + 'static>(e: E, expected: &str) {
87
        let e: Box<dyn StdError> = Box::new(e);
88
        let got = Report(&e).to_string();
89
        assert_eq!(got, expected, "\nmismatch: {:?}", &e);
90
    }
91

            
92
    #[test]
93
    #[rustfmt::skip] // preserve layout of chk calls
94
    fn test() {
95
        chk(ShallowError,
96
            "error: shallow");
97

            
98
        let terse_1 = || TerseError { source: ShallowError.into() };
99
        chk(terse_1(),
100
            "error: terse: shallow");
101

            
102
        let verbose_1 = || VerboseError { source: ShallowError.into() };
103
        chk(verbose_1(),
104
            "error: verbose - shallow");
105

            
106
        chk(VerboseError { source: terse_1().into() },
107
            "error: verbose - terse: shallow");
108

            
109
        chk(TerseError { source: verbose_1().into() },
110
            "error: terse: verbose - shallow");
111

            
112
        chk(io::Error::new(io::ErrorKind::Other, ShallowError),
113
            "error: shallow");
114
    }
115
}