tor_error/report.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177
//! The Report type which reports errors nicely
use std::error::Error as StdError;
use std::fmt::{self, Debug, Display};
use crate::sealed::Sealed;
/// 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<E>(pub E)
where
E: AsRef<dyn StdError>;
impl<E> Display for Report<E>
where
E: AsRef<dyn StdError>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
/// Non-generic inner function avoids code bloat
fn inner(e: &dyn StdError, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "error: ")?;
retry_error::fmt_error_with_sources(e, f)?;
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, R>(e: E) -> R
where
E: AsRef<dyn StdError>,
{
/// 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<dyn Error> 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<dyn StdError + 'static> 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 Deref<Target = dyn Error...>`,
/// you can use `tor_error::Report(err)` directly,
/// but you can also call `.report()` via the impl of this trait for `dyn Error`.
pub trait ErrorReport: Sealed + StdError + 'static {
/// Return an object that displays the error and its causes
//
// We would ideally have returned `Report<impl AsRef<...>>` but that's TAIT.
fn report(&self) -> Report<ReportHelper>;
}
impl<E: StdError + Sized + 'static> Sealed for E {}
impl<E: StdError + Sized + 'static> ErrorReport for E {
fn report(&self) -> Report<ReportHelper> {
Report(ReportHelper(self as _))
}
}
impl Sealed for dyn StdError + Send + Sync {}
/// Implementation for `anyhow::Error`, which derefs to `dyn StdError`.
impl ErrorReport for dyn StdError + Send + Sync {
fn report(&self) -> Report<ReportHelper> {
Report(ReportHelper(self))
}
}
/// Defines `AsRef<dyn StdError + 'static>` 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<dyn Trait> 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-deftly)
impl AsRef<dyn std::error::Error + 'static> 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::mixed_attributes_style)]
#![allow(clippy::print_stderr)]
#![allow(clippy::print_stdout)]
#![allow(clippy::single_char_pattern)]
#![allow(clippy::unwrap_used)]
#![allow(clippy::unchecked_duration_subtraction)]
#![allow(clippy::useless_vec)]
#![allow(clippy::needless_pass_by_value)]
//! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
use super::*;
use std::io;
use thiserror::Error;
#[derive(Error, Debug)]
#[error("terse")]
struct TerseError {
#[from]
source: Box<dyn StdError>,
}
#[derive(Error, Debug)]
#[error("verbose - {source}")]
struct VerboseError {
#[from]
source: Box<dyn StdError>,
}
#[derive(Error, Debug)]
#[error("shallow")]
struct ShallowError;
fn chk<E: StdError + 'static>(e: E, expected: &str) {
let e: Box<dyn StdError> = 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");
}
}