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