Coverage Report

Created: 2025-02-21 07:11

/rust/registry/src/index.crates.io-6f17d22bba15001f/snafu-0.7.5/src/report.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::ChainCompat;
2
use core::fmt;
3
4
#[cfg(all(feature = "std", feature = "rust_1_61"))]
5
use 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
67
pub struct Report<E>(Result<(), E>);
68
69
impl<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
0
    pub fn from_error(error: E) -> Self {
90
0
        Self(Err(error))
91
0
    }
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
0
    pub fn capture_into_result<T>(body: impl FnOnce() -> Result<T, E>) -> Result<T, Self> {
117
0
        body().map_err(Self::from_error)
118
0
    }
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
0
    pub fn capture(body: impl FnOnce() -> Result<(), E>) -> Self {
146
0
        Self(body())
147
0
    }
148
149
    /// A [`Report`][] that indicates no error occurred.
150
0
    pub const fn ok() -> Self {
151
0
        Self(Ok(()))
152
0
    }
153
}
154
155
impl<E> From<Result<(), E>> for Report<E> {
156
0
    fn from(other: Result<(), E>) -> Self {
157
0
        Self(other)
158
0
    }
159
}
160
161
impl<E> fmt::Debug for Report<E>
162
where
163
    E: crate::Error,
164
{
165
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166
0
        fmt::Display::fmt(self, f)
167
0
    }
168
}
169
170
impl<E> fmt::Display for Report<E>
171
where
172
    E: crate::Error,
173
{
174
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175
0
        match &self.0 {
176
0
            Err(e) => fmt::Display::fmt(&ReportFormatter(e), f),
177
0
            _ => Ok(()),
178
        }
179
0
    }
180
}
181
182
#[cfg(all(feature = "std", feature = "rust_1_61"))]
183
impl<E> Termination for Report<E>
184
where
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")]
212
impl<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
218
struct ReportFormatter<'a>(&'a dyn crate::Error);
219
220
impl<'a> fmt::Display for ReportFormatter<'a> {
221
0
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222
0
        #[cfg(feature = "std")]
223
0
        {
224
0
            if trace_cleaning_enabled() {
225
0
                self.cleaned_error_trace(f)?;
226
            } else {
227
0
                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
0
        Ok(())
246
0
    }
247
}
248
249
impl<'a> ReportFormatter<'a> {
250
0
    fn error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
251
0
        writeln!(f, "{}", self.0)?;
252
253
0
        let sources = ChainCompat::new(self.0).skip(1);
254
0
        let plurality = sources.clone().take(2).count();
255
0
256
0
        match plurality {
257
0
            0 => {}
258
0
            1 => writeln!(f, "\nCaused by this error:")?,
259
0
            _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?,
260
        }
261
262
0
        for (i, source) in sources.enumerate() {
263
            // Let's use 1-based indexing for presentation
264
0
            let i = i + 1;
265
0
            writeln!(f, "{:3}: {}", i, source)?;
266
        }
267
268
0
        Ok(())
269
0
    }
270
271
    #[cfg(feature = "std")]
272
0
    fn cleaned_error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
273
        const NOTE: char = '*';
274
275
0
        let mut any_cleaned = false;
276
0
        let mut any_removed = false;
277
0
        let cleaned_messages: Vec<_> = CleanedErrorText::new(self.0)
278
0
            .flat_map(|(_, mut msg, cleaned)| {
279
0
                if msg.is_empty() {
280
0
                    any_removed = true;
281
0
                    None
282
                } else {
283
0
                    if cleaned {
284
0
                        any_cleaned = true;
285
0
                        msg.push(' ');
286
0
                        msg.push(NOTE);
287
0
                    }
288
0
                    Some(msg)
289
                }
290
0
            })
291
0
            .collect();
292
0
293
0
        let mut visible_messages = cleaned_messages.iter();
294
295
0
        let head = match visible_messages.next() {
296
0
            Some(v) => v,
297
0
            None => return Ok(()),
298
        };
299
300
0
        writeln!(f, "{}", head)?;
301
302
0
        match cleaned_messages.len() {
303
0
            0 | 1 => {}
304
0
            2 => writeln!(f, "\nCaused by this error:")?,
305
0
            _ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?,
306
        }
307
308
0
        for (i, msg) in visible_messages.enumerate() {
309
            // Let's use 1-based indexing for presentation
310
0
            let i = i + 1;
311
0
            writeln!(f, "{:3}: {}", i, msg)?;
312
        }
313
314
0
        if any_cleaned || any_removed {
315
0
            write!(f, "\nNOTE: ")?;
316
317
0
            if any_cleaned {
318
0
                write!(
319
0
                    f,
320
0
                    "Some redundant information has been removed from the lines marked with {}. ",
321
0
                    NOTE,
322
0
                )?;
323
            } else {
324
0
                write!(f, "Some redundant information has been removed. ")?;
325
            }
326
327
0
            writeln!(
328
0
                f,
329
0
                "Set {}=1 to disable this behavior.",
330
0
                SNAFU_RAW_ERROR_MESSAGES,
331
0
            )?;
332
0
        }
333
334
0
        Ok(())
335
0
    }
336
}
337
338
#[cfg(feature = "std")]
339
const SNAFU_RAW_ERROR_MESSAGES: &str = "SNAFU_RAW_ERROR_MESSAGES";
340
341
#[cfg(feature = "std")]
342
0
fn trace_cleaning_enabled() -> bool {
343
    use crate::once_bool::OnceBool;
344
    use std::env;
345
346
    static DISABLED: OnceBool = OnceBool::new();
347
0
    !DISABLED.get(|| env::var_os(SNAFU_RAW_ERROR_MESSAGES).map_or(false, |v| v == "1"))
348
0
}
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")]
379
pub struct CleanedErrorText<'a>(Option<CleanedErrorTextStep<'a>>);
380
381
#[cfg(feature = "std")]
382
impl<'a> CleanedErrorText<'a> {
383
    /// Constructs the iterator.
384
0
    pub fn new(error: &'a dyn crate::Error) -> Self {
385
0
        Self(Some(CleanedErrorTextStep::new(error)))
386
0
    }
387
}
388
389
#[cfg(feature = "std")]
390
impl<'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
0
    fn next(&mut self) -> Option<Self::Item> {
395
        use std::mem;
396
397
0
        let mut step = self.0.take()?;
398
0
        let mut error_text = mem::replace(&mut step.error_text, Default::default());
399
0
400
0
        match step.error.source() {
401
0
            Some(next_error) => {
402
0
                let next_error_text = next_error.to_string();
403
0
404
0
                let cleaned_text = error_text
405
0
                    .trim_end_matches(&next_error_text)
406
0
                    .trim_end()
407
0
                    .trim_end_matches(':');
408
0
                let cleaned = cleaned_text.len() != error_text.len();
409
0
                let cleaned_len = cleaned_text.len();
410
0
                error_text.truncate(cleaned_len);
411
0
412
0
                self.0 = Some(CleanedErrorTextStep {
413
0
                    error: next_error,
414
0
                    error_text: next_error_text,
415
0
                });
416
0
417
0
                Some((step.error, error_text, cleaned))
418
            }
419
0
            None => Some((step.error, error_text, false)),
420
        }
421
0
    }
422
}
423
424
#[cfg(feature = "std")]
425
struct CleanedErrorTextStep<'a> {
426
    error: &'a dyn crate::Error,
427
    error_text: String,
428
}
429
430
#[cfg(feature = "std")]
431
impl<'a> CleanedErrorTextStep<'a> {
432
0
    fn new(error: &'a dyn crate::Error) -> Self {
433
0
        let error_text = error.to_string();
434
0
        Self { error, error_text }
435
0
    }
436
}
437
438
#[doc(hidden)]
439
pub trait __InternalExtractErrorType {
440
    type Err;
441
}
442
443
impl<T, E> __InternalExtractErrorType for core::result::Result<T, E> {
444
    type Err = E;
445
}