1use std::error::Error as StdError;
4use std::fmt::{self, Debug, Display};
5
6use crate::sealed::Sealed;
7
8#[derive(Debug, Copy, Clone)]
10#[allow(clippy::exhaustive_structs)] pub 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 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#[allow(clippy::print_stderr)] pub fn report_and_exit<E, R>(e: E) -> R
36where
37 E: AsRef<dyn StdError>,
38{
39 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
51pub 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
68pub trait ErrorReport: Sealed + StdError + 'static {
76 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 {}
88impl 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#[macro_export]
107macro_rules! define_asref_dyn_std_error { { $ty:ty } => {
108impl 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 #![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)] 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] 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}