Skip to main content

tor_error/
report.rs

1//! The Report type which reports errors nicely
2
3use std::error::Error as StdError;
4use std::fmt::{self, Debug, Display};
5
6use 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
11pub struct Report<E>(pub E)
12where
13    E: AsRef<dyn StdError>;
14
15impl<E> Display for Report<E>
16where
17    E: AsRef<dyn StdError>,
18{
19    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
20        /// Non-generic inner function avoids code bloat
21        fn inner(e: &dyn StdError, f: &mut fmt::Formatter) -> fmt::Result {
22            write!(f, "error: ")?;
23            retry_error::fmt_error_with_sources(e, f)?;
24            Ok(())
25        }
26
27        inner(self.0.as_ref(), f)
28    }
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
35pub fn report_and_exit<E, R>(e: E) -> R
36where
37    E: AsRef<dyn StdError>,
38{
39    /// Non-generic inner function avoids code bloat
40    fn eprint_progname() {
41        if let Some(progname) = std::env::args().next() {
42            eprint!("{}: ", progname);
43        }
44    }
45
46    eprint_progname();
47    eprintln!("{}", Report(e));
48    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.
61pub struct ReportHelper<'e>(&'e (dyn StdError + 'static));
62impl<'e> AsRef<dyn StdError + 'static> for ReportHelper<'e> {
63    fn as_ref(&self) -> &(dyn StdError + 'static) {
64        self.0
65    }
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`.
75pub 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}
81impl<E: StdError + Sized + 'static> Sealed for E {}
82impl<E: StdError + Sized + 'static> ErrorReport for E {
83    fn report(&self) -> Report<ReportHelper> {
84        Report(ReportHelper(self as _))
85    }
86}
87impl Sealed for dyn StdError + Send + Sync {}
88/// Implementation for `anyhow::Error`, which derefs to `dyn StdError`.
89impl ErrorReport for dyn StdError + Send + Sync {
90    fn report(&self) -> Report<ReportHelper> {
91        Report(ReportHelper(self))
92    }
93}
94impl Sealed for dyn StdError + 'static {}
95impl 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]
107macro_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)]
118mod 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}