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 }