//! The Report type which reports errors nicely use std::error::Error as StdError; use std::fmt::{self, Debug, Display}; /// Wraps any Error, providing a nicely-reporting Display impl #[derive(Debug, Copy, Clone)] #[allow(clippy::exhaustive_structs)] // this is a transparent wrapper pub struct Report(pub E) where E: AsRef; impl Display for Report where E: AsRef, { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { /// Non-generic inner function avoids code bloat fn inner(mut e: &dyn StdError, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "error")?; let mut last = String::new(); loop { let this = e.to_string(); if !last.contains(&this) { write!(f, ": {}", &this)?; } last = this; if let Some(ne) = e.source() { e = ne; } else { break; } } Ok(()) } inner(self.0.as_ref(), f) } } /// Report the error E to stderr, and exit the program /// /// Does not return. Return type is any type R, for convenience with eg `unwrap_or_else`. #[allow(clippy::print_stderr)] // this is the point of this function pub fn report_and_exit(e: E) -> R where E: AsRef, { /// Non-generic inner function avoids code bloat fn eprint_progname() { if let Some(progname) = std::env::args().next() { eprint!("{}: ", progname); } } eprint_progname(); eprintln!("{}", Report(e)); std::process::exit(127) } /// Helper type for reporting errors that are concrete implementors of `StdError` /// /// This is an opaque type, only constructable via the `ErrorExt` helper trait /// and only usable via its `AsRef` implementation. // // We need this because Rust's trait object handling rules, and provided AsRef impls, // are rather anaemic. We cannot simply put a &dyn Error into Report, because // &dyn Error doesn't impl AsRef even though the implementation is trivial. // We can't provide that AsRef impl ourselves due to trait coherency rules. // So instead, we wrap up the &dyn Error in a newtype, for which we *can* provide the AsRef. pub struct ReportHelper<'e>(&'e (dyn StdError + 'static)); impl<'e> AsRef for ReportHelper<'e> { fn as_ref(&self) -> &(dyn StdError + 'static) { self.0 } } /// Extension trait providing `.report()` method on concrete errors /// /// This is implemented for types that directly implement [`std::error::Error`]` + 'static`. /// For types like `anyhow::Error` that `impl AsRef`, /// use `tor_error::Report(err)` directly. pub trait ErrorReport: StdError + Sized + 'static { /// Return an object that displays the error and its causes // // We would ideally have returned `Report>` but that's TAIT. fn report(&self) -> Report { Report(ReportHelper(self as _)) } } impl ErrorReport for E {} /// Defines `AsRef` for a type implementing [`StdError`] /// /// This trivial `AsRef` impl enables use of `tor_error::Report`. // Rust don't do this automatically, sadly, even though // it's basically `impl AsRef for T where T: Trait`. #[macro_export] macro_rules! define_asref_dyn_std_error { { $ty:ty } => { // TODO: It would nice if this could be generated more automatically; // TODO wouldn't it be nice if this was a `derive` (eg using derive-adhoc) impl AsRef for $ty { fn as_ref(&self) -> &(dyn std::error::Error + 'static) { self as _ } } } } #[cfg(test)] mod test { // @@ begin test lint list maintained by maint/add_warning @@ #![allow(clippy::bool_assert_comparison)] #![allow(clippy::clone_on_copy)] #![allow(clippy::dbg_macro)] #![allow(clippy::print_stderr)] #![allow(clippy::print_stdout)] #![allow(clippy::single_char_pattern)] #![allow(clippy::unwrap_used)] #![allow(clippy::unchecked_duration_subtraction)] //! use super::*; use std::io; use thiserror::Error; #[derive(Error, Debug)] #[error("terse")] struct TerseError { #[from] source: Box, } #[derive(Error, Debug)] #[error("verbose - {source}")] struct VerboseError { #[from] source: Box, } #[derive(Error, Debug)] #[error("shallow")] struct ShallowError; fn chk(e: E, expected: &str) { let e: Box = Box::new(e); let got = Report(&e).to_string(); assert_eq!(got, expected, "\nmismatch: {:?}", &e); } #[test] #[rustfmt::skip] // preserve layout of chk calls fn test() { chk(ShallowError, "error: shallow"); let terse_1 = || TerseError { source: ShallowError.into() }; chk(terse_1(), "error: terse: shallow"); let verbose_1 = || VerboseError { source: ShallowError.into() }; chk(verbose_1(), "error: verbose - shallow"); chk(VerboseError { source: terse_1().into() }, "error: verbose - terse: shallow"); chk(TerseError { source: verbose_1().into() }, "error: terse: verbose - shallow"); chk(io::Error::new(io::ErrorKind::Other, ShallowError), "error: shallow"); } }