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

            
3
use std::error::Error as StdError;
4
use std::fmt::{self, Debug, Display};
5

            
6
use crate::sealed::Sealed;
7

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

            
15
impl<E> Display for Report<E>
16
where
17
    E: AsRef<dyn StdError>,
18
{
19
2272
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20
        /// Non-generic inner function avoids code bloat
21
4700
        fn inner(e: &dyn StdError, f: &mut fmt::Formatter) -> fmt::Result {
22
4700
            write!(f, "error: ")?;
23
4700
            retry_error::fmt_error_with_sources(e, f)?;
24
4700
            Ok(())
25
4700
        }
26

            
27
2272
        inner(self.0.as_ref(), f)
28
2272
    }
29
}
30

            
31
/// Report the error E to stderr, and exit the program
32
///
33
/// Does not return.  Return type is any type R, for convenience with eg `unwrap_or_else`.
34
#[allow(clippy::print_stderr)] // this is the point of this function
35
96
pub fn report_and_exit<E, R>(e: E) -> R
36
96
where
37
96
    E: AsRef<dyn StdError>,
38
{
39
    /// Non-generic inner function avoids code bloat
40
1056
    fn eprint_progname() {
41
1056
        if let Some(progname) = std::env::args().next() {
42
1056
            eprint!("{}: ", progname);
43
1056
        }
44
1056
    }
45

            
46
96
    eprint_progname();
47
96
    eprintln!("{}", Report(e));
48
96
    std::process::exit(127)
49
}
50

            
51
/// Helper type for reporting errors that are concrete implementors of `StdError`
52
///
53
/// This is an opaque type, only constructable via the [`ErrorReport`] helper trait
54
/// and only usable via its `AsRef` implementation.
55
//
56
// We need this because Rust's trait object handling rules, and provided AsRef impls,
57
// are rather anaemic.  We cannot simply put a &dyn Error into Report, because
58
// &dyn Error doesn't impl AsRef<dyn Error> even though the implementation is trivial.
59
// We can't provide that AsRef impl ourselves due to trait coherency rules.
60
// So instead, we wrap up the &dyn Error in a newtype, for which we *can* provide the AsRef.
61
pub struct ReportHelper<'e>(&'e (dyn StdError + 'static));
62
impl<'e> AsRef<dyn StdError + 'static> for ReportHelper<'e> {
63
3566
    fn as_ref(&self) -> &(dyn StdError + 'static) {
64
3566
        self.0
65
3566
    }
66
}
67

            
68
/// Extension trait providing `.report()` method on concrete errors
69
///
70
/// This is implemented for types that directly implement [`std::error::Error`]` + 'static`.
71
///
72
/// For types like `anyhow::Error` that `impl Deref<Target = dyn Error...>`,
73
/// you can use `tor_error::Report(err)` directly,
74
/// but you can also call `.report()` via the impl of this trait for `dyn Error`.
75
pub trait ErrorReport: Sealed + StdError + 'static {
76
    /// Return an object that displays the error and its causes
77
    //
78
    // We would ideally have returned `Report<impl AsRef<...>>` but that's TAIT.
79
    fn report(&self) -> Report<ReportHelper>;
80
}
81
impl<E: StdError + Sized + 'static> Sealed for E {}
82
impl<E: StdError + Sized + 'static> ErrorReport for E {
83
114
    fn report(&self) -> Report<ReportHelper> {
84
114
        Report(ReportHelper(self as _))
85
114
    }
86
}
87
impl Sealed for dyn StdError + Send + Sync {}
88
/// Implementation for `anyhow::Error`, which derefs to `dyn StdError`.
89
impl ErrorReport for dyn StdError + Send + Sync {
90
2
    fn report(&self) -> Report<ReportHelper> {
91
2
        Report(ReportHelper(self))
92
2
    }
93
}
94
impl Sealed for dyn StdError + 'static {}
95
impl ErrorReport for dyn StdError + 'static {
96
    fn report(&self) -> Report<ReportHelper> {
97
        Report(ReportHelper(self))
98
    }
99
}
100

            
101
/// Defines `AsRef<dyn StdError + 'static>` for a type implementing [`StdError`]
102
///
103
/// This trivial `AsRef` impl enables use of `tor_error::Report`.
104
// Rust don't do this automatically, sadly, even though
105
// it's basically `impl AsRef<dyn Trait> for T where T: Trait`.
106
#[macro_export]
107
macro_rules! define_asref_dyn_std_error { { $ty:ty } => {
108
// TODO: It would nice if this could be generated more automatically;
109
// TODO wouldn't it be nice if this was a `derive` (eg using derive-deftly)
110
    impl AsRef<dyn std::error::Error + 'static> for $ty {
111
        fn as_ref(&self) -> &(dyn std::error::Error + 'static) {
112
            self as _
113
        }
114
    }
115
} }
116

            
117
#[cfg(test)]
118
mod test {
119
    // @@ begin test lint list maintained by maint/add_warning @@
120
    #![allow(clippy::bool_assert_comparison)]
121
    #![allow(clippy::clone_on_copy)]
122
    #![allow(clippy::dbg_macro)]
123
    #![allow(clippy::mixed_attributes_style)]
124
    #![allow(clippy::print_stderr)]
125
    #![allow(clippy::print_stdout)]
126
    #![allow(clippy::single_char_pattern)]
127
    #![allow(clippy::unwrap_used)]
128
    #![allow(clippy::unchecked_time_subtraction)]
129
    #![allow(clippy::useless_vec)]
130
    #![allow(clippy::needless_pass_by_value)]
131
    #![allow(clippy::string_slice)] // See arti#2571
132
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
133
    use super::*;
134
    use std::io;
135
    use thiserror::Error;
136

            
137
    #[derive(Error, Debug)]
138
    #[error("terse")]
139
    struct TerseError {
140
        #[from]
141
        source: Box<dyn StdError>,
142
    }
143

            
144
    #[derive(Error, Debug)]
145
    #[error("verbose - {source}")]
146
    struct VerboseError {
147
        #[from]
148
        source: Box<dyn StdError>,
149
    }
150

            
151
    #[derive(Error, Debug)]
152
    #[error("shallow")]
153
    struct ShallowError;
154

            
155
    fn chk<E: StdError + 'static>(e: E, expected: &str) {
156
        let e: Box<dyn StdError> = Box::new(e);
157
        let got = Report(&e).to_string();
158
        assert_eq!(got, expected, "\nmismatch: {:?}", &e);
159
    }
160

            
161
    #[test]
162
    #[rustfmt::skip] // preserve layout of chk calls
163
    fn test() {
164
        chk(ShallowError,
165
            "error: shallow");
166

            
167
        let terse_1 = || TerseError { source: ShallowError.into() };
168
        chk(terse_1(),
169
            "error: terse: shallow");
170

            
171
        let verbose_1 = || VerboseError { source: ShallowError.into() };
172
        chk(verbose_1(),
173
            "error: verbose - shallow");
174

            
175
        chk(VerboseError { source: terse_1().into() },
176
            "error: verbose - terse: shallow");
177

            
178
        chk(TerseError { source: verbose_1().into() },
179
            "error: terse: verbose - shallow");
180

            
181
        chk(io::Error::other(ShallowError),
182
            "error: shallow");
183
    }
184
}