/ crates / tor-error / src / report.rs
report.rs
  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      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
 35  pub fn report_and_exit<E, R>(e: E) -> R
 36  where
 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 `ErrorExt` 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      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`.
 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      fn report(&self) -> Report<ReportHelper> {
 84          Report(ReportHelper(self as _))
 85      }
 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      fn report(&self) -> Report<ReportHelper> {
 91          Report(ReportHelper(self))
 92      }
 93  }
 94  
 95  /// Defines `AsRef<dyn StdError + 'static>` for a type implementing [`StdError`]
 96  ///
 97  /// This trivial `AsRef` impl enables use of `tor_error::Report`.
 98  // Rust don't do this automatically, sadly, even though
 99  // it's basically `impl AsRef<dyn Trait> for T where T: Trait`.
100  #[macro_export]
101  macro_rules! define_asref_dyn_std_error { { $ty:ty } => {
102  // TODO: It would nice if this could be generated more automatically;
103  // TODO wouldn't it be nice if this was a `derive` (eg using derive-deftly)
104      impl AsRef<dyn std::error::Error + 'static> for $ty {
105          fn as_ref(&self) -> &(dyn std::error::Error + 'static) {
106              self as _
107          }
108      }
109  } }
110  
111  #[cfg(test)]
112  mod test {
113      // @@ begin test lint list maintained by maint/add_warning @@
114      #![allow(clippy::bool_assert_comparison)]
115      #![allow(clippy::clone_on_copy)]
116      #![allow(clippy::dbg_macro)]
117      #![allow(clippy::mixed_attributes_style)]
118      #![allow(clippy::print_stderr)]
119      #![allow(clippy::print_stdout)]
120      #![allow(clippy::single_char_pattern)]
121      #![allow(clippy::unwrap_used)]
122      #![allow(clippy::unchecked_duration_subtraction)]
123      #![allow(clippy::useless_vec)]
124      #![allow(clippy::needless_pass_by_value)]
125      //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
126      use super::*;
127      use std::io;
128      use thiserror::Error;
129  
130      #[derive(Error, Debug)]
131      #[error("terse")]
132      struct TerseError {
133          #[from]
134          source: Box<dyn StdError>,
135      }
136  
137      #[derive(Error, Debug)]
138      #[error("verbose - {source}")]
139      struct VerboseError {
140          #[from]
141          source: Box<dyn StdError>,
142      }
143  
144      #[derive(Error, Debug)]
145      #[error("shallow")]
146      struct ShallowError;
147  
148      fn chk<E: StdError + 'static>(e: E, expected: &str) {
149          let e: Box<dyn StdError> = Box::new(e);
150          let got = Report(&e).to_string();
151          assert_eq!(got, expected, "\nmismatch: {:?}", &e);
152      }
153  
154      #[test]
155      #[rustfmt::skip] // preserve layout of chk calls
156      fn test() {
157          chk(ShallowError,
158              "error: shallow");
159  
160          let terse_1 = || TerseError { source: ShallowError.into() };
161          chk(terse_1(),
162              "error: terse: shallow");
163  
164          let verbose_1 = || VerboseError { source: ShallowError.into() };
165          chk(verbose_1(),
166              "error: verbose - shallow");
167  
168          chk(VerboseError { source: terse_1().into() },
169              "error: verbose - terse: shallow");
170  
171          chk(TerseError { source: verbose_1().into() },
172              "error: terse: verbose - shallow");
173  
174          chk(io::Error::new(io::ErrorKind::Other, ShallowError),
175              "error: shallow");
176      }
177  }