Skip to main content

err_report/
lib.rs

1//! Clone of the unstable [`std::error::Report`] type.
2//!
3//! Backtrace support is omitted due to nightly requirement.
4//!
5//! Copied on 2025-09-09.
6//!
7//! # Examples
8//!
9//! ```
10//! use std::ffi::CString;
11//!
12//! use err_report::Report;
13//!
14//! let invalid_utf8 = [b'f', 0xff, b'o', b'o'];
15//! let c_string = CString::new(invalid_utf8).unwrap();
16//! let err = c_string.into_string().unwrap_err();
17//!
18//! // without Report, the source/root error is not printed
19//! assert_eq!("C string contained non-utf8 bytes", err.to_string());
20//!
21//! // with Report, all details in error chain are printed
22//! assert_eq!(
23//!     "C string contained non-utf8 bytes: invalid utf-8 sequence of 1 bytes from index 1",
24//!     Report::new(err).to_string(),
25//! );
26//! ```
27
28pub use core::error::Error;
29// pub use core::error::{Request, request_ref, request_value};
30
31// use std::backtrace::Backtrace;
32use std::fmt::{self, Write as _};
33
34/// An error reporter that prints an error and its sources.
35///
36/// Report also exposes configuration options for formatting the error sources, either entirely on a
37/// single line, or in multi-line format with each source on a new line.
38///
39/// `Report` only requires that the wrapped error implement `Error`. It doesn't require that the
40/// wrapped error be `Send`, `Sync`, or `'static`.
41///
42/// # Examples
43///
44/// ```rust
45/// use std::{error::Error, fmt};
46///
47/// use err_report::Report;
48///
49/// #[derive(Debug)]
50/// struct SuperError {
51///     source: SuperErrorSideKick,
52/// }
53///
54/// impl fmt::Display for SuperError {
55///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56///         write!(f, "SuperError is here!")
57///     }
58/// }
59///
60/// impl Error for SuperError {
61///     fn source(&self) -> Option<&(dyn Error + 'static)> {
62///         Some(&self.source)
63///     }
64/// }
65///
66/// #[derive(Debug)]
67/// struct SuperErrorSideKick;
68///
69/// impl fmt::Display for SuperErrorSideKick {
70///     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71///         write!(f, "SuperErrorSideKick is here!")
72///     }
73/// }
74///
75/// impl Error for SuperErrorSideKick {}
76///
77/// fn get_super_error() -> Result<(), SuperError> {
78///     Err(SuperError {
79///         source: SuperErrorSideKick,
80///     })
81/// }
82///
83/// fn main() {
84///     match get_super_error() {
85///         Err(e) => println!("Error: {}", Report::new(e)),
86///         _ => println!("No error"),
87///     }
88/// }
89/// ```
90///
91/// This example produces the following output:
92///
93/// ```console
94/// Error: SuperError is here!: SuperErrorSideKick is here!
95/// ```
96///
97/// ## Output consistency
98///
99/// Report prints the same output via `Display` and `Debug`, so it works well with
100/// [`Result::unwrap`]/[`Result::expect`] which print their `Err` variant via `Debug`:
101///
102/// ```should_panic
103/// use err_report::Report;
104/// # use std::error::Error;
105/// # use std::fmt;
106/// # #[derive(Debug)]
107/// # struct SuperError {
108/// #     source: SuperErrorSideKick,
109/// # }
110/// # impl fmt::Display for SuperError {
111/// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112/// #         write!(f, "SuperError is here!")
113/// #     }
114/// # }
115/// # impl Error for SuperError {
116/// #     fn source(&self) -> Option<&(dyn Error + 'static)> {
117/// #         Some(&self.source)
118/// #     }
119/// # }
120/// # #[derive(Debug)]
121/// # struct SuperErrorSideKick;
122/// # impl fmt::Display for SuperErrorSideKick {
123/// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124/// #         write!(f, "SuperErrorSideKick is here!")
125/// #     }
126/// # }
127/// # impl Error for SuperErrorSideKick {}
128/// # fn get_super_error() -> Result<(), SuperError> {
129/// #     Err(SuperError { source: SuperErrorSideKick })
130/// # }
131///
132/// get_super_error().map_err(Report::new).unwrap();
133/// ```
134///
135/// This example produces the following output:
136///
137/// ```console
138/// thread 'main' panicked at src/error.rs:34:40:
139/// called `Result::unwrap()` on an `Err` value: SuperError is here!: SuperErrorSideKick is here!
140/// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
141/// ```
142///
143/// ## Return from `main`
144///
145/// `Report` also implements `From` for all types that implement [`Error`]; this when combined with
146/// the `Debug` output means `Report` is an ideal starting place for formatting errors returned
147/// from `main`.
148///
149/// ```should_panic
150/// use err_report::Report;
151/// # use std::error::Error;
152/// # use std::fmt;
153/// # #[derive(Debug)]
154/// # struct SuperError {
155/// #     source: SuperErrorSideKick,
156/// # }
157/// # impl fmt::Display for SuperError {
158/// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159/// #         write!(f, "SuperError is here!")
160/// #     }
161/// # }
162/// # impl Error for SuperError {
163/// #     fn source(&self) -> Option<&(dyn Error + 'static)> {
164/// #         Some(&self.source)
165/// #     }
166/// # }
167/// # #[derive(Debug)]
168/// # struct SuperErrorSideKick;
169/// # impl fmt::Display for SuperErrorSideKick {
170/// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
171/// #         write!(f, "SuperErrorSideKick is here!")
172/// #     }
173/// # }
174/// # impl Error for SuperErrorSideKick {}
175/// # fn get_super_error() -> Result<(), SuperError> {
176/// #     Err(SuperError { source: SuperErrorSideKick })
177/// # }
178///
179/// fn main() -> Result<(), Report<SuperError>> {
180///     get_super_error()?;
181///     Ok(())
182/// }
183/// ```
184///
185/// This example produces the following output:
186///
187/// ```console
188/// Error: SuperError is here!: SuperErrorSideKick is here!
189/// ```
190///
191/// **Note**: `Report`s constructed via `?` and `From` will be configured to use the single line
192/// output format. If you want to make sure your `Report`s are pretty printed and include backtrace
193/// you will need to manually convert and enable those flags.
194///
195/// ```should_panic
196/// use err_report::Report;
197/// # use std::error::Error;
198/// # use std::fmt;
199/// # #[derive(Debug)]
200/// # struct SuperError {
201/// #     source: SuperErrorSideKick,
202/// # }
203/// # impl fmt::Display for SuperError {
204/// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205/// #         write!(f, "SuperError is here!")
206/// #     }
207/// # }
208/// # impl Error for SuperError {
209/// #     fn source(&self) -> Option<&(dyn Error + 'static)> {
210/// #         Some(&self.source)
211/// #     }
212/// # }
213/// # #[derive(Debug)]
214/// # struct SuperErrorSideKick;
215/// # impl fmt::Display for SuperErrorSideKick {
216/// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
217/// #         write!(f, "SuperErrorSideKick is here!")
218/// #     }
219/// # }
220/// # impl Error for SuperErrorSideKick {}
221/// # fn get_super_error() -> Result<(), SuperError> {
222/// #     Err(SuperError { source: SuperErrorSideKick })
223/// # }
224///
225/// fn main() -> Result<(), Report<SuperError>> {
226///     get_super_error()
227///         .map_err(Report::from)
228///         .map_err(|r| r.pretty(true))?;
229///     Ok(())
230/// }
231/// ```
232///
233/// This example produces the following output:
234///
235/// ```console
236/// Error: SuperError is here!
237///
238/// Caused by:
239///       SuperErrorSideKick is here!
240/// ```
241pub struct Report<E = Box<dyn Error>> {
242    /// The error being reported.
243    error: E,
244    // /// Whether a backtrace should be included as part of the report.
245    // show_backtrace: bool,
246    /// Whether the report should be pretty-printed.
247    pretty: bool,
248}
249
250impl<E> Report<E>
251where
252    Report<E>: From<E>,
253{
254    /// Creates a new `Report` from an input error.
255    pub fn new(error: E) -> Report<E> {
256        Self::from(error)
257    }
258}
259
260impl<E> Report<E> {
261    /// Enable pretty-printing the report across multiple lines.
262    ///
263    /// # Examples
264    ///
265    /// ```rust
266    /// use err_report::Report;
267    /// # use std::error::Error;
268    /// # use std::fmt;
269    /// # #[derive(Debug)]
270    /// # struct SuperError {
271    /// #     source: SuperErrorSideKick,
272    /// # }
273    /// # impl fmt::Display for SuperError {
274    /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275    /// #         write!(f, "SuperError is here!")
276    /// #     }
277    /// # }
278    /// # impl Error for SuperError {
279    /// #     fn source(&self) -> Option<&(dyn Error + 'static)> {
280    /// #         Some(&self.source)
281    /// #     }
282    /// # }
283    /// # #[derive(Debug)]
284    /// # struct SuperErrorSideKick;
285    /// # impl fmt::Display for SuperErrorSideKick {
286    /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
287    /// #         write!(f, "SuperErrorSideKick is here!")
288    /// #     }
289    /// # }
290    /// # impl Error for SuperErrorSideKick {}
291    ///
292    /// let error = SuperError {
293    ///     source: SuperErrorSideKick,
294    /// };
295    /// let report = Report::new(error).pretty(true);
296    /// eprintln!("Error: {report:?}");
297    /// ```
298    ///
299    /// This example produces the following output:
300    ///
301    /// ```console
302    /// Error: SuperError is here!
303    ///
304    /// Caused by:
305    ///       SuperErrorSideKick is here!
306    /// ```
307    ///
308    /// When there are multiple source errors the causes will be numbered in order of iteration
309    /// starting from the outermost error.
310    ///
311    /// ```rust
312    /// use err_report::Report;
313    /// # use std::error::Error;
314    /// # use std::fmt;
315    /// # #[derive(Debug)]
316    /// # struct SuperError {
317    /// #     source: SuperErrorSideKick,
318    /// # }
319    /// # impl fmt::Display for SuperError {
320    /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321    /// #         write!(f, "SuperError is here!")
322    /// #     }
323    /// # }
324    /// # impl Error for SuperError {
325    /// #     fn source(&self) -> Option<&(dyn Error + 'static)> {
326    /// #         Some(&self.source)
327    /// #     }
328    /// # }
329    /// # #[derive(Debug)]
330    /// # struct SuperErrorSideKick {
331    /// #     source: SuperErrorSideKickSideKick,
332    /// # }
333    /// # impl fmt::Display for SuperErrorSideKick {
334    /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
335    /// #         write!(f, "SuperErrorSideKick is here!")
336    /// #     }
337    /// # }
338    /// # impl Error for SuperErrorSideKick {
339    /// #     fn source(&self) -> Option<&(dyn Error + 'static)> {
340    /// #         Some(&self.source)
341    /// #     }
342    /// # }
343    /// # #[derive(Debug)]
344    /// # struct SuperErrorSideKickSideKick;
345    /// # impl fmt::Display for SuperErrorSideKickSideKick {
346    /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
347    /// #         write!(f, "SuperErrorSideKickSideKick is here!")
348    /// #     }
349    /// # }
350    /// # impl Error for SuperErrorSideKickSideKick { }
351    ///
352    /// let source = SuperErrorSideKickSideKick;
353    /// let source = SuperErrorSideKick { source };
354    /// let error = SuperError { source };
355    /// let report = Report::new(error).pretty(true);
356    /// eprintln!("Error: {report:?}");
357    /// ```
358    ///
359    /// This example produces the following output:
360    ///
361    /// ```console
362    /// Error: SuperError is here!
363    ///
364    /// Caused by:
365    ///    0: SuperErrorSideKick is here!
366    ///    1: SuperErrorSideKickSideKick is here!
367    /// ```
368    pub fn pretty(mut self, pretty: bool) -> Self {
369        self.pretty = pretty;
370        self
371    }
372
373    // /// Display backtrace if available when using pretty output format.
374    // ///
375    // /// # Examples
376    // ///
377    // /// **Note**: Report will search for the first `Backtrace` it can find starting from the
378    // /// outermost error. In this example it will display the backtrace from the second error in the
379    // /// sources, `SuperErrorSideKick`.
380    // ///
381    // /// ```rust
382    // /// #![feature(error_generic_member_access)]
383    // /// # use std::error::Error;
384    // /// # use std::fmt;
385    // /// use std::error::Request;
386    // /// use err_report::Report;
387    // /// use std::backtrace::Backtrace;
388    // ///
389    // /// # #[derive(Debug)]
390    // /// # struct SuperError {
391    // /// #     source: SuperErrorSideKick,
392    // /// # }
393    // /// # impl fmt::Display for SuperError {
394    // /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395    // /// #         write!(f, "SuperError is here!")
396    // /// #     }
397    // /// # }
398    // /// # impl Error for SuperError {
399    // /// #     fn source(&self) -> Option<&(dyn Error + 'static)> {
400    // /// #         Some(&self.source)
401    // /// #     }
402    // /// # }
403    // /// #[derive(Debug)]
404    // /// struct SuperErrorSideKick {
405    // ///     backtrace: Backtrace,
406    // /// }
407    // ///
408    // /// impl SuperErrorSideKick {
409    // ///     fn new() -> SuperErrorSideKick {
410    // ///         SuperErrorSideKick { backtrace: Backtrace::force_capture() }
411    // ///     }
412    // /// }
413    // ///
414    // /// impl Error for SuperErrorSideKick {
415    // ///     fn provide<'a>(&'a self, request: &mut Request<'a>) {
416    // ///         request.provide_ref::<Backtrace>(&self.backtrace);
417    // ///     }
418    // /// }
419    // ///
420    // /// // The rest of the example is unchanged ...
421    // /// # impl fmt::Display for SuperErrorSideKick {
422    // /// #     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
423    // /// #         write!(f, "SuperErrorSideKick is here!")
424    // /// #     }
425    // /// # }
426    // ///
427    // /// let source = SuperErrorSideKick::new();
428    // /// let error = SuperError { source };
429    // /// let report = Report::new(error).pretty(true).show_backtrace(true);
430    // /// eprintln!("Error: {report:?}");
431    // /// ```
432    // ///
433    // /// This example produces something similar to the following output:
434    // ///
435    // /// ```console
436    // /// Error: SuperError is here!
437    // ///
438    // /// Caused by:
439    // ///       SuperErrorSideKick is here!
440    // ///
441    // /// Stack backtrace:
442    // ///    0: rust_out::main::_doctest_main_src_error_rs_1158_0::SuperErrorSideKick::new
443    // ///    1: rust_out::main::_doctest_main_src_error_rs_1158_0
444    // ///    2: rust_out::main
445    // ///    3: core::ops::function::FnOnce::call_once
446    // ///    4: std::sys::backtrace::__rust_begin_short_backtrace
447    // ///    5: std::rt::lang_start::{{closure}}
448    // ///    6: std::panicking::try
449    // ///    7: std::rt::lang_start_internal
450    // ///    8: std::rt::lang_start
451    // ///    9: main
452    // ///   10: __libc_start_main
453    // ///   11: _start
454    // /// ```
455    // pub fn show_backtrace(mut self, show_backtrace: bool) -> Self {
456    //     self.show_backtrace = show_backtrace;
457    //     self
458    // }
459}
460
461impl<E> Report<E>
462where
463    E: Error,
464{
465    // fn backtrace(&self) -> Option<&Backtrace> {
466    //     // have to grab the backtrace on the first error directly since that error may not be
467    //     // 'static
468    //     let backtrace = request_ref(&self.error);
469    //     let backtrace = backtrace.or_else(|| {
470    //         self.error
471    //             .source()
472    //             .map(|source| Source::new(source).find_map(|source| request_ref(source)))
473    //             .flatten()
474    //     });
475    //     backtrace
476    // }
477
478    /// Format the report as a single line.
479    fn fmt_singleline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
480        write!(f, "{}", self.error)?;
481
482        let sources = self.error.source().into_iter().flat_map(Source::new);
483
484        for cause in sources {
485            write!(f, ": {cause}")?;
486        }
487
488        Ok(())
489    }
490
491    /// Format the report as multiple lines, with each error cause on its own line.
492    fn fmt_multiline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
493        let error = &self.error;
494
495        write!(f, "{error}")?;
496
497        if let Some(cause) = error.source() {
498            write!(f, "\n\nCaused by:")?;
499
500            let multiple = cause.source().is_some();
501
502            for (ind, error) in Source::new(cause).enumerate() {
503                writeln!(f)?;
504                let mut indented = Indented { inner: f };
505                if multiple {
506                    write!(indented, "{ind: >4}: {error}")?;
507                } else {
508                    write!(indented, "      {error}")?;
509                }
510            }
511        }
512
513        // if self.show_backtrace {
514        //     if let Some(backtrace) = self.backtrace() {
515        //         write!(
516        //             f,
517        //             "\n\nStack backtrace:\n{}",
518        //             backtrace.to_string().trim_end()
519        //         )?;
520        //     }
521        // }
522
523        Ok(())
524    }
525}
526
527impl<E> From<E> for Report<E>
528where
529    E: Error,
530{
531    fn from(error: E) -> Self {
532        Report {
533            error,
534            // show_backtrace: false,
535            pretty: false,
536        }
537    }
538}
539
540impl<E> fmt::Display for Report<E>
541where
542    E: Error,
543{
544    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
545        if self.pretty {
546            self.fmt_multiline(f)
547        } else {
548            self.fmt_singleline(f)
549        }
550    }
551}
552
553// This type intentionally outputs the same format for `Display` and `Debug`for
554// situations where you unwrap a `Report` or return it from main.
555impl<E> fmt::Debug for Report<E>
556where
557    Report<E>: fmt::Display,
558{
559    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
560        fmt::Display::fmt(self, f)
561    }
562}
563
564/// Wrapper type for indenting the inner source.
565struct Indented<'a, D> {
566    inner: &'a mut D,
567}
568
569impl<T> fmt::Write for Indented<'_, T>
570where
571    T: fmt::Write,
572{
573    fn write_str(&mut self, s: &str) -> fmt::Result {
574        for (i, line) in s.split('\n').enumerate() {
575            if i > 0 {
576                self.inner.write_char('\n')?;
577                self.inner.write_str("      ")?;
578            }
579
580            self.inner.write_str(line)?;
581        }
582
583        Ok(())
584    }
585}
586
587/// An iterator over an [`Error`] and its sources.
588///
589/// If you want to omit the initial error and only process
590/// its sources, use `skip(1)`.
591#[derive(Clone, Debug)]
592struct Source<'a> {
593    current: Option<&'a (dyn Error + 'static)>,
594}
595
596impl<'a> Source<'a> {
597    fn new(error: &'a (dyn Error + 'static)) -> Self {
598        Self {
599            current: Some(error),
600        }
601    }
602}
603
604impl<'a> Iterator for Source<'a> {
605    type Item = &'a (dyn Error + 'static);
606
607    fn next(&mut self) -> Option<Self::Item> {
608        let current = self.current;
609        self.current = self.current.and_then(Error::source);
610        current
611    }
612
613    fn size_hint(&self) -> (usize, Option<usize>) {
614        if self.current.is_some() {
615            (1, None)
616        } else {
617            (0, Some(0))
618        }
619    }
620}