snafu/report.rs
1use crate::ChainCompat;
2use core::fmt;
3
4#[cfg(all(feature = "std", feature = "rust_1_61"))]
5use std::process::{ExitCode, Termination};
6
7/// Opinionated solution to format an error in a user-friendly
8/// way. Useful as the return type from `main` and test functions.
9///
10/// Most users will use the [`snafu::report`][] procedural macro
11/// instead of directly using this type, but you can if you do not
12/// wish to use the macro.
13///
14/// [`snafu::report`]: macro@crate::report
15///
16/// ## Rust 1.61 and up
17///
18/// Change the return type of the function to [`Report`][] and wrap
19/// the body of your function with [`Report::capture`][].
20///
21/// ## Rust before 1.61
22///
23/// Use [`Report`][] as the error type inside of [`Result`][] and then
24/// call either [`Report::capture_into_result`][] or
25/// [`Report::from_error`][].
26///
27/// ## Nightly Rust
28///
29/// Enabling the [`unstable-try-trait` feature flag][try-ff] will
30/// allow you to use the `?` operator directly:
31///
32/// ```rust
33/// use snafu::{prelude::*, Report};
34///
35/// # #[cfg(all(feature = "unstable-try-trait", feature = "rust_1_61"))]
36/// fn main() -> Report<PlaceholderError> {
37/// let _v = may_fail_with_placeholder_error()?;
38///
39/// Report::ok()
40/// }
41/// # #[cfg(not(all(feature = "unstable-try-trait", feature = "rust_1_61")))] fn main() {}
42/// # #[derive(Debug, Snafu)]
43/// # struct PlaceholderError;
44/// # fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> { Ok(42) }
45/// ```
46///
47/// [try-ff]: crate::guide::feature_flags#unstable-try-trait
48///
49/// ## Interaction with the Provider API
50///
51/// If you return a [`Report`][] from your function and enable the
52/// [`unstable-provider-api` feature flag][provider-ff], additional
53/// capabilities will be added:
54///
55/// 1. If provided, a [`Backtrace`][] will be included in the output.
56/// 1. If provided, a [`ExitCode`][] will be used as the return value.
57///
58/// [provider-ff]: crate::guide::feature_flags#unstable-provider-api
59/// [`Backtrace`]: crate::Backtrace
60/// [`ExitCode`]: std::process::ExitCode
61///
62/// ## Stability of the output
63///
64/// The exact content and format of a displayed `Report` are not
65/// stable, but this type strives to print the error and as much
66/// user-relevant information in an easily-consumable manner
67pub struct Report<E>(Result<(), E>);
68
69impl<E> Report<E> {
70 /// Convert an error into a [`Report`][].
71 ///
72 /// Recommended if you support versions of Rust before 1.61.
73 ///
74 /// ```rust
75 /// use snafu::{prelude::*, Report};
76 ///
77 /// #[derive(Debug, Snafu)]
78 /// struct PlaceholderError;
79 ///
80 /// fn main() -> Result<(), Report<PlaceholderError>> {
81 /// let _v = may_fail_with_placeholder_error().map_err(Report::from_error)?;
82 /// Ok(())
83 /// }
84 ///
85 /// fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> {
86 /// Ok(42)
87 /// }
88 /// ```
89 pub fn from_error(error: E) -> Self {
90 Self(Err(error))
91 }
92
93 /// Executes a closure that returns a [`Result`][], converting the
94 /// error variant into a [`Report`][].
95 ///
96 /// Recommended if you support versions of Rust before 1.61.
97 ///
98 /// ```rust
99 /// use snafu::{prelude::*, Report};
100 ///
101 /// #[derive(Debug, Snafu)]
102 /// struct PlaceholderError;
103 ///
104 /// fn main() -> Result<(), Report<PlaceholderError>> {
105 /// Report::capture_into_result(|| {
106 /// let _v = may_fail_with_placeholder_error()?;
107 ///
108 /// Ok(())
109 /// })
110 /// }
111 ///
112 /// fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> {
113 /// Ok(42)
114 /// }
115 /// ```
116 pub fn capture_into_result<T>(body: impl FnOnce() -> Result<T, E>) -> Result<T, Self> {
117 body().map_err(Self::from_error)
118 }
119
120 /// Executes a closure that returns a [`Result`][], converting any
121 /// error to a [`Report`][].
122 ///
123 /// Recommended if you only support Rust version 1.61 or above.
124 ///
125 /// ```rust
126 /// use snafu::{prelude::*, Report};
127 ///
128 /// #[derive(Debug, Snafu)]
129 /// struct PlaceholderError;
130 ///
131 /// # #[cfg(feature = "rust_1_61")]
132 /// fn main() -> Report<PlaceholderError> {
133 /// Report::capture(|| {
134 /// let _v = may_fail_with_placeholder_error()?;
135 ///
136 /// Ok(())
137 /// })
138 /// }
139 /// # #[cfg(not(feature = "rust_1_61"))] fn main() {}
140 ///
141 /// fn may_fail_with_placeholder_error() -> Result<u8, PlaceholderError> {
142 /// Ok(42)
143 /// }
144 /// ```
145 pub fn capture(body: impl FnOnce() -> Result<(), E>) -> Self {
146 Self(body())
147 }
148
149 /// A [`Report`][] that indicates no error occurred.
150 pub const fn ok() -> Self {
151 Self(Ok(()))
152 }
153}
154
155impl<E> From<Result<(), E>> for Report<E> {
156 fn from(other: Result<(), E>) -> Self {
157 Self(other)
158 }
159}
160
161impl<E> fmt::Debug for Report<E>
162where
163 E: crate::Error,
164{
165 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166 fmt::Display::fmt(self, f)
167 }
168}
169
170impl<E> fmt::Display for Report<E>
171where
172 E: crate::Error,
173{
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 match &self.0 {
176 Err(e) => fmt::Display::fmt(&ReportFormatter(e), f),
177 _ => Ok(()),
178 }
179 }
180}
181
182#[cfg(all(feature = "std", feature = "rust_1_61"))]
183impl<E> Termination for Report<E>
184where
185 E: crate::Error,
186{
187 fn report(self) -> ExitCode {
188 match self.0 {
189 Ok(()) => ExitCode::SUCCESS,
190 Err(e) => {
191 eprintln!("{}", ReportFormatter(&e));
192
193 #[cfg(feature = "unstable-provider-api")]
194 {
195 use core::any;
196
197 any::request_value::<ExitCode>(&e)
198 .or_else(|| any::request_ref::<ExitCode>(&e).copied())
199 .unwrap_or(ExitCode::FAILURE)
200 }
201
202 #[cfg(not(feature = "unstable-provider-api"))]
203 {
204 ExitCode::FAILURE
205 }
206 }
207 }
208 }
209}
210
211#[cfg(feature = "unstable-try-trait")]
212impl<T, E> core::ops::FromResidual<Result<T, E>> for Report<E> {
213 fn from_residual(residual: Result<T, E>) -> Self {
214 Self(residual.map(drop))
215 }
216}
217
218struct ReportFormatter<'a>(&'a dyn crate::Error);
219
220impl<'a> fmt::Display for ReportFormatter<'a> {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 #[cfg(feature = "std")]
223 {
224 if trace_cleaning_enabled() {
225 self.cleaned_error_trace(f)?;
226 } else {
227 self.error_trace(f)?;
228 }
229 }
230
231 #[cfg(not(feature = "std"))]
232 {
233 self.error_trace(f)?;
234 }
235
236 #[cfg(feature = "unstable-provider-api")]
237 {
238 use core::any;
239
240 if let Some(bt) = any::request_ref::<crate::Backtrace>(self.0) {
241 writeln!(f, "\nBacktrace:\n{}", bt)?;
242 }
243 }
244
245 Ok(())
246 }
247}
248
249impl<'a> ReportFormatter<'a> {
250 fn error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
251 writeln!(f, "{}", self.0)?;
252
253 let sources = ChainCompat::new(self.0).skip(1);
254 let plurality = sources.clone().take(2).count();
255
256 match plurality {
257 0 => {}
258 1 => writeln!(f, "\nCaused by this error:")?,
259 _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?,
260 }
261
262 for (i, source) in sources.enumerate() {
263 // Let's use 1-based indexing for presentation
264 let i = i + 1;
265 writeln!(f, "{:3}: {}", i, source)?;
266 }
267
268 Ok(())
269 }
270
271 #[cfg(feature = "std")]
272 fn cleaned_error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
273 const NOTE: char = '*';
274
275 let mut any_cleaned = false;
276 let mut any_removed = false;
277 let cleaned_messages: Vec<_> = CleanedErrorText::new(self.0)
278 .flat_map(|(_, mut msg, cleaned)| {
279 if msg.is_empty() {
280 any_removed = true;
281 None
282 } else {
283 if cleaned {
284 any_cleaned = true;
285 msg.push(' ');
286 msg.push(NOTE);
287 }
288 Some(msg)
289 }
290 })
291 .collect();
292
293 let mut visible_messages = cleaned_messages.iter();
294
295 let head = match visible_messages.next() {
296 Some(v) => v,
297 None => return Ok(()),
298 };
299
300 writeln!(f, "{}", head)?;
301
302 match cleaned_messages.len() {
303 0 | 1 => {}
304 2 => writeln!(f, "\nCaused by this error:")?,
305 _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?,
306 }
307
308 for (i, msg) in visible_messages.enumerate() {
309 // Let's use 1-based indexing for presentation
310 let i = i + 1;
311 writeln!(f, "{:3}: {}", i, msg)?;
312 }
313
314 if any_cleaned || any_removed {
315 write!(f, "\nNOTE: ")?;
316
317 if any_cleaned {
318 write!(
319 f,
320 "Some redundant information has been removed from the lines marked with {}. ",
321 NOTE,
322 )?;
323 } else {
324 write!(f, "Some redundant information has been removed. ")?;
325 }
326
327 writeln!(
328 f,
329 "Set {}=1 to disable this behavior.",
330 SNAFU_RAW_ERROR_MESSAGES,
331 )?;
332 }
333
334 Ok(())
335 }
336}
337
338#[cfg(feature = "std")]
339const SNAFU_RAW_ERROR_MESSAGES: &str = "SNAFU_RAW_ERROR_MESSAGES";
340
341#[cfg(feature = "std")]
342fn trace_cleaning_enabled() -> bool {
343 use crate::once_bool::OnceBool;
344 use std::env;
345
346 static DISABLED: OnceBool = OnceBool::new();
347 !DISABLED.get(|| env::var_os(SNAFU_RAW_ERROR_MESSAGES).map_or(false, |v| v == "1"))
348}
349
350/// An iterator over an Error and its sources that removes duplicated
351/// text from the error display strings.
352///
353/// It's common for errors with a `source` to have a `Display`
354/// implementation that includes their source text as well:
355///
356/// ```text
357/// Outer error text: Middle error text: Inner error text
358/// ```
359///
360/// This works for smaller errors without much detail, but can be
361/// annoying when trying to format the error in a more structured way,
362/// such as line-by-line:
363///
364/// ```text
365/// 1. Outer error text: Middle error text: Inner error text
366/// 2. Middle error text: Inner error text
367/// 3. Inner error text
368/// ```
369///
370/// This iterator compares each pair of errors in the source chain,
371/// removing the source error's text from the containing error's text:
372///
373/// ```text
374/// 1. Outer error text
375/// 2. Middle error text
376/// 3. Inner error text
377/// ```
378#[cfg(feature = "std")]
379pub struct CleanedErrorText<'a>(Option<CleanedErrorTextStep<'a>>);
380
381#[cfg(feature = "std")]
382impl<'a> CleanedErrorText<'a> {
383 /// Constructs the iterator.
384 pub fn new(error: &'a dyn crate::Error) -> Self {
385 Self(Some(CleanedErrorTextStep::new(error)))
386 }
387}
388
389#[cfg(feature = "std")]
390impl<'a> Iterator for CleanedErrorText<'a> {
391 /// The original error, the display string and if it has been cleaned
392 type Item = (&'a dyn crate::Error, String, bool);
393
394 fn next(&mut self) -> Option<Self::Item> {
395 use std::mem;
396
397 let mut step = self.0.take()?;
398 let mut error_text = mem::replace(&mut step.error_text, Default::default());
399
400 match step.error.source() {
401 Some(next_error) => {
402 let next_error_text = next_error.to_string();
403
404 let cleaned_text = error_text
405 .trim_end_matches(&next_error_text)
406 .trim_end()
407 .trim_end_matches(':');
408 let cleaned = cleaned_text.len() != error_text.len();
409 let cleaned_len = cleaned_text.len();
410 error_text.truncate(cleaned_len);
411
412 self.0 = Some(CleanedErrorTextStep {
413 error: next_error,
414 error_text: next_error_text,
415 });
416
417 Some((step.error, error_text, cleaned))
418 }
419 None => Some((step.error, error_text, false)),
420 }
421 }
422}
423
424#[cfg(feature = "std")]
425struct CleanedErrorTextStep<'a> {
426 error: &'a dyn crate::Error,
427 error_text: String,
428}
429
430#[cfg(feature = "std")]
431impl<'a> CleanedErrorTextStep<'a> {
432 fn new(error: &'a dyn crate::Error) -> Self {
433 let error_text = error.to_string();
434 Self { error, error_text }
435 }
436}
437
438#[doc(hidden)]
439pub trait __InternalExtractErrorType {
440 type Err;
441}
442
443impl<T, E> __InternalExtractErrorType for core::result::Result<T, E> {
444 type Err = E;
445}