Rollup merge of #91938 - yaahc:error-reporter, r=m-ou-se
Add `std::error::Report` type This is a continuation of https://github.com/rust-lang/rust/pull/90174, split into a separate PR since I cannot push to ```````@seanchen1991``````` 's fork
This commit is contained in:
commit
e045c79c2d
@ -25,7 +25,7 @@ use crate::backtrace::Backtrace;
|
||||
use crate::borrow::Cow;
|
||||
use crate::cell;
|
||||
use crate::char;
|
||||
use crate::fmt::{self, Debug, Display};
|
||||
use crate::fmt::{self, Debug, Display, Write};
|
||||
use crate::mem::transmute;
|
||||
use crate::num;
|
||||
use crate::str;
|
||||
@ -63,7 +63,7 @@ pub trait Error: Debug + Display {
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct SuperError {
|
||||
/// side: SuperErrorSideKick,
|
||||
/// source: SuperErrorSideKick,
|
||||
/// }
|
||||
///
|
||||
/// impl fmt::Display for SuperError {
|
||||
@ -74,7 +74,7 @@ pub trait Error: Debug + Display {
|
||||
///
|
||||
/// impl Error for SuperError {
|
||||
/// fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// Some(&self.side)
|
||||
/// Some(&self.source)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
@ -90,7 +90,7 @@ pub trait Error: Debug + Display {
|
||||
/// impl Error for SuperErrorSideKick {}
|
||||
///
|
||||
/// fn get_super_error() -> Result<(), SuperError> {
|
||||
/// Err(SuperError { side: SuperErrorSideKick })
|
||||
/// Err(SuperError { source: SuperErrorSideKick })
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
@ -810,3 +810,642 @@ impl dyn Error + Send + Sync {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// An error reporter that print's an error and its sources.
|
||||
///
|
||||
/// Report also exposes configuration options for formatting the error chain, either entirely on a
|
||||
/// single line, or in multi-line format with each cause in the error chain on a new line.
|
||||
///
|
||||
/// `Report` only requires that the wrapped error implements `Error`. It doesn't require that the
|
||||
/// wrapped error be `Send`, `Sync`, or `'static`.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(error_reporter)]
|
||||
/// use std::error::{Error, Report};
|
||||
/// use std::fmt;
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct SuperError {
|
||||
/// source: SuperErrorSideKick,
|
||||
/// }
|
||||
///
|
||||
/// impl fmt::Display for SuperError {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// write!(f, "SuperError is here!")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl Error for SuperError {
|
||||
/// fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// Some(&self.source)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Debug)]
|
||||
/// struct SuperErrorSideKick;
|
||||
///
|
||||
/// impl fmt::Display for SuperErrorSideKick {
|
||||
/// fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// write!(f, "SuperErrorSideKick is here!")
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl Error for SuperErrorSideKick {}
|
||||
///
|
||||
/// fn get_super_error() -> Result<(), SuperError> {
|
||||
/// Err(SuperError { source: SuperErrorSideKick })
|
||||
/// }
|
||||
///
|
||||
/// fn main() {
|
||||
/// match get_super_error() {
|
||||
/// Err(e) => println!("Error: {}", Report::new(e)),
|
||||
/// _ => println!("No error"),
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This example produces the following output:
|
||||
///
|
||||
/// ```console
|
||||
/// Error: SuperError is here!: SuperErrorSideKick is here!
|
||||
/// ```
|
||||
///
|
||||
/// ## Output consistency
|
||||
///
|
||||
/// Report prints the same output via `Display` and `Debug`, so it works well with
|
||||
/// [`Result::unwrap`]/[`Result::expect`] which print their `Err` variant via `Debug`:
|
||||
///
|
||||
/// ```should_panic
|
||||
/// #![feature(error_reporter)]
|
||||
/// use std::error::Report;
|
||||
/// # use std::error::Error;
|
||||
/// # use std::fmt;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperError {
|
||||
/// # source: SuperErrorSideKick,
|
||||
/// # }
|
||||
/// # impl fmt::Display for SuperError {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperError is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperError {
|
||||
/// # fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// # Some(&self.source)
|
||||
/// # }
|
||||
/// # }
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperErrorSideKick;
|
||||
/// # impl fmt::Display for SuperErrorSideKick {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperErrorSideKick is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperErrorSideKick {}
|
||||
/// # fn get_super_error() -> Result<(), SuperError> {
|
||||
/// # Err(SuperError { source: SuperErrorSideKick })
|
||||
/// # }
|
||||
///
|
||||
/// get_super_error().map_err(Report::new).unwrap();
|
||||
/// ```
|
||||
///
|
||||
/// This example produces the following output:
|
||||
///
|
||||
/// ```console
|
||||
/// thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: SuperError is here!: SuperErrorSideKick is here!', src/error.rs:34:40
|
||||
/// note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
|
||||
/// ```
|
||||
///
|
||||
/// ## Return from `main`
|
||||
///
|
||||
/// `Report` also implements `From` for all types that implement [`Error`], this when combined with
|
||||
/// the `Debug` output means `Report` is an ideal starting place for formatting errors returned
|
||||
/// from `main`.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// #![feature(error_reporter)]
|
||||
/// use std::error::Report;
|
||||
/// # use std::error::Error;
|
||||
/// # use std::fmt;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperError {
|
||||
/// # source: SuperErrorSideKick,
|
||||
/// # }
|
||||
/// # impl fmt::Display for SuperError {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperError is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperError {
|
||||
/// # fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// # Some(&self.source)
|
||||
/// # }
|
||||
/// # }
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperErrorSideKick;
|
||||
/// # impl fmt::Display for SuperErrorSideKick {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperErrorSideKick is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperErrorSideKick {}
|
||||
/// # fn get_super_error() -> Result<(), SuperError> {
|
||||
/// # Err(SuperError { source: SuperErrorSideKick })
|
||||
/// # }
|
||||
///
|
||||
/// fn main() -> Result<(), Report> {
|
||||
/// get_super_error()?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This example produces the following output:
|
||||
///
|
||||
/// ```console
|
||||
/// Error: SuperError is here!: SuperErrorSideKick is here!
|
||||
/// ```
|
||||
///
|
||||
/// **Note**: `Report`s constructed via `?` and `From` will be configured to use the single line
|
||||
/// output format, if you want to make sure your `Report`s are pretty printed and include backtrace
|
||||
/// you will need to manually convert and enable those flags.
|
||||
///
|
||||
/// ```should_panic
|
||||
/// #![feature(error_reporter)]
|
||||
/// use std::error::Report;
|
||||
/// # use std::error::Error;
|
||||
/// # use std::fmt;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperError {
|
||||
/// # source: SuperErrorSideKick,
|
||||
/// # }
|
||||
/// # impl fmt::Display for SuperError {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperError is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperError {
|
||||
/// # fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// # Some(&self.source)
|
||||
/// # }
|
||||
/// # }
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperErrorSideKick;
|
||||
/// # impl fmt::Display for SuperErrorSideKick {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperErrorSideKick is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperErrorSideKick {}
|
||||
/// # fn get_super_error() -> Result<(), SuperError> {
|
||||
/// # Err(SuperError { source: SuperErrorSideKick })
|
||||
/// # }
|
||||
///
|
||||
/// fn main() -> Result<(), Report> {
|
||||
/// get_super_error()
|
||||
/// .map_err(Report::from)
|
||||
/// .map_err(|r| r.pretty(true).show_backtrace(true))?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// This example produces the following output:
|
||||
///
|
||||
/// ```console
|
||||
/// Error: SuperError is here!
|
||||
///
|
||||
/// Caused by:
|
||||
/// SuperErrorSideKick is here!
|
||||
/// ```
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
pub struct Report<E = Box<dyn Error>> {
|
||||
/// The error being reported.
|
||||
error: E,
|
||||
/// Whether a backtrace should be included as part of the report.
|
||||
show_backtrace: bool,
|
||||
/// Whether the report should be pretty-printed.
|
||||
pretty: bool,
|
||||
}
|
||||
|
||||
impl<E> Report<E>
|
||||
where
|
||||
Report<E>: From<E>,
|
||||
{
|
||||
/// Create a new `Report` from an input error.
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
pub fn new(error: E) -> Report<E> {
|
||||
Self::from(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Report<E> {
|
||||
/// Enable pretty-printing the report across multiple lines.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(error_reporter)]
|
||||
/// use std::error::Report;
|
||||
/// # use std::error::Error;
|
||||
/// # use std::fmt;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperError {
|
||||
/// # source: SuperErrorSideKick,
|
||||
/// # }
|
||||
/// # impl fmt::Display for SuperError {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperError is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperError {
|
||||
/// # fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// # Some(&self.source)
|
||||
/// # }
|
||||
/// # }
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperErrorSideKick;
|
||||
/// # impl fmt::Display for SuperErrorSideKick {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperErrorSideKick is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperErrorSideKick {}
|
||||
///
|
||||
/// let error = SuperError { source: SuperErrorSideKick };
|
||||
/// let report = Report::new(error).pretty(true);
|
||||
/// eprintln!("Error: {:?}", report);
|
||||
/// ```
|
||||
///
|
||||
/// This example produces the following output:
|
||||
///
|
||||
/// ```console
|
||||
/// Error: SuperError is here!
|
||||
///
|
||||
/// Caused by:
|
||||
/// SuperErrorSideKick is here!
|
||||
/// ```
|
||||
///
|
||||
/// When there are multiple source errors the causes will be numbered in order of iteration
|
||||
/// starting from the outermost error.
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(error_reporter)]
|
||||
/// use std::error::Report;
|
||||
/// # use std::error::Error;
|
||||
/// # use std::fmt;
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperError {
|
||||
/// # source: SuperErrorSideKick,
|
||||
/// # }
|
||||
/// # impl fmt::Display for SuperError {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperError is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperError {
|
||||
/// # fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// # Some(&self.source)
|
||||
/// # }
|
||||
/// # }
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperErrorSideKick {
|
||||
/// # source: SuperErrorSideKickSideKick,
|
||||
/// # }
|
||||
/// # impl fmt::Display for SuperErrorSideKick {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperErrorSideKick is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperErrorSideKick {
|
||||
/// # fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// # Some(&self.source)
|
||||
/// # }
|
||||
/// # }
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperErrorSideKickSideKick;
|
||||
/// # impl fmt::Display for SuperErrorSideKickSideKick {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperErrorSideKickSideKick is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperErrorSideKickSideKick { }
|
||||
///
|
||||
/// let source = SuperErrorSideKickSideKick;
|
||||
/// let source = SuperErrorSideKick { source };
|
||||
/// let error = SuperError { source };
|
||||
/// let report = Report::new(error).pretty(true);
|
||||
/// eprintln!("Error: {:?}", report);
|
||||
/// ```
|
||||
///
|
||||
/// This example produces the following output:
|
||||
///
|
||||
/// ```console
|
||||
/// Error: SuperError is here!
|
||||
///
|
||||
/// Caused by:
|
||||
/// 0: SuperErrorSideKick is here!
|
||||
/// 1: SuperErrorSideKickSideKick is here!
|
||||
/// ```
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
pub fn pretty(mut self, pretty: bool) -> Self {
|
||||
self.pretty = pretty;
|
||||
self
|
||||
}
|
||||
|
||||
/// Display backtrace if available when using pretty output format.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// **Note**: Report will search for the first `Backtrace` it can find starting from the
|
||||
/// outermost error. In this example it will display the backtrace from the second error in the
|
||||
/// chain, `SuperErrorSideKick`.
|
||||
///
|
||||
/// ```rust
|
||||
/// #![feature(error_reporter)]
|
||||
/// #![feature(backtrace)]
|
||||
/// # use std::error::Error;
|
||||
/// # use std::fmt;
|
||||
/// use std::error::Report;
|
||||
/// use std::backtrace::Backtrace;
|
||||
///
|
||||
/// # #[derive(Debug)]
|
||||
/// # struct SuperError {
|
||||
/// # source: SuperErrorSideKick,
|
||||
/// # }
|
||||
/// # impl fmt::Display for SuperError {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperError is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
/// # impl Error for SuperError {
|
||||
/// # fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
/// # Some(&self.source)
|
||||
/// # }
|
||||
/// # }
|
||||
/// #[derive(Debug)]
|
||||
/// struct SuperErrorSideKick {
|
||||
/// backtrace: Backtrace,
|
||||
/// }
|
||||
///
|
||||
/// impl SuperErrorSideKick {
|
||||
/// fn new() -> SuperErrorSideKick {
|
||||
/// SuperErrorSideKick { backtrace: Backtrace::force_capture() }
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// impl Error for SuperErrorSideKick {
|
||||
/// fn backtrace(&self) -> Option<&Backtrace> {
|
||||
/// Some(&self.backtrace)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// // The rest of the example is unchanged ...
|
||||
/// # impl fmt::Display for SuperErrorSideKick {
|
||||
/// # fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
/// # write!(f, "SuperErrorSideKick is here!")
|
||||
/// # }
|
||||
/// # }
|
||||
///
|
||||
/// let source = SuperErrorSideKick::new();
|
||||
/// let error = SuperError { source };
|
||||
/// let report = Report::new(error).pretty(true).show_backtrace(true);
|
||||
/// eprintln!("Error: {:?}", report);
|
||||
/// ```
|
||||
///
|
||||
/// This example produces something similar to the following output:
|
||||
///
|
||||
/// ```console
|
||||
/// Error: SuperError is here!
|
||||
///
|
||||
/// Caused by:
|
||||
/// SuperErrorSideKick is here!
|
||||
///
|
||||
/// Stack backtrace:
|
||||
/// 0: rust_out::main::_doctest_main_src_error_rs_1158_0::SuperErrorSideKick::new
|
||||
/// 1: rust_out::main::_doctest_main_src_error_rs_1158_0
|
||||
/// 2: rust_out::main
|
||||
/// 3: core::ops::function::FnOnce::call_once
|
||||
/// 4: std::sys_common::backtrace::__rust_begin_short_backtrace
|
||||
/// 5: std::rt::lang_start::{{closure}}
|
||||
/// 6: std::panicking::try
|
||||
/// 7: std::rt::lang_start_internal
|
||||
/// 8: std::rt::lang_start
|
||||
/// 9: main
|
||||
/// 10: __libc_start_main
|
||||
/// 11: _start
|
||||
/// ```
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
pub fn show_backtrace(mut self, show_backtrace: bool) -> Self {
|
||||
self.show_backtrace = show_backtrace;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> Report<E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
fn backtrace(&self) -> Option<&Backtrace> {
|
||||
// have to grab the backtrace on the first error directly since that error may not be
|
||||
// 'static
|
||||
let backtrace = self.error.backtrace();
|
||||
let backtrace = backtrace.or_else(|| {
|
||||
self.error
|
||||
.source()
|
||||
.map(|source| source.chain().find_map(|source| source.backtrace()))
|
||||
.flatten()
|
||||
});
|
||||
backtrace
|
||||
}
|
||||
|
||||
/// Format the report as a single line.
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
fn fmt_singleline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.error)?;
|
||||
|
||||
let sources = self.error.source().into_iter().flat_map(<dyn Error>::chain);
|
||||
|
||||
for cause in sources {
|
||||
write!(f, ": {}", cause)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Format the report as multiple lines, with each error cause on its own line.
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
fn fmt_multiline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let error = &self.error;
|
||||
|
||||
write!(f, "{}", error)?;
|
||||
|
||||
if let Some(cause) = error.source() {
|
||||
write!(f, "\n\nCaused by:")?;
|
||||
|
||||
let multiple = cause.source().is_some();
|
||||
|
||||
for (ind, error) in cause.chain().enumerate() {
|
||||
writeln!(f)?;
|
||||
let mut indented = Indented { inner: f };
|
||||
if multiple {
|
||||
write!(indented, "{: >4}: {}", ind, error)?;
|
||||
} else {
|
||||
write!(indented, " {}", error)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_backtrace {
|
||||
let backtrace = self.backtrace();
|
||||
|
||||
if let Some(backtrace) = backtrace {
|
||||
let backtrace = backtrace.to_string();
|
||||
|
||||
f.write_str("\n\nStack backtrace:\n")?;
|
||||
f.write_str(backtrace.trim_end())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Report<Box<dyn Error>> {
|
||||
fn backtrace(&self) -> Option<&Backtrace> {
|
||||
// have to grab the backtrace on the first error directly since that error may not be
|
||||
// 'static
|
||||
let backtrace = self.error.backtrace();
|
||||
let backtrace = backtrace.or_else(|| {
|
||||
self.error
|
||||
.source()
|
||||
.map(|source| source.chain().find_map(|source| source.backtrace()))
|
||||
.flatten()
|
||||
});
|
||||
backtrace
|
||||
}
|
||||
|
||||
/// Format the report as a single line.
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
fn fmt_singleline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}", self.error)?;
|
||||
|
||||
let sources = self.error.source().into_iter().flat_map(<dyn Error>::chain);
|
||||
|
||||
for cause in sources {
|
||||
write!(f, ": {}", cause)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Format the report as multiple lines, with each error cause on its own line.
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
fn fmt_multiline(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let error = &self.error;
|
||||
|
||||
write!(f, "{}", error)?;
|
||||
|
||||
if let Some(cause) = error.source() {
|
||||
write!(f, "\n\nCaused by:")?;
|
||||
|
||||
let multiple = cause.source().is_some();
|
||||
|
||||
for (ind, error) in cause.chain().enumerate() {
|
||||
writeln!(f)?;
|
||||
let mut indented = Indented { inner: f };
|
||||
if multiple {
|
||||
write!(indented, "{: >4}: {}", ind, error)?;
|
||||
} else {
|
||||
write!(indented, " {}", error)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if self.show_backtrace {
|
||||
let backtrace = self.backtrace();
|
||||
|
||||
if let Some(backtrace) = backtrace {
|
||||
let backtrace = backtrace.to_string();
|
||||
|
||||
f.write_str("\n\nStack backtrace:\n")?;
|
||||
f.write_str(backtrace.trim_end())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
impl<E> From<E> for Report<E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
Report { error, show_backtrace: false, pretty: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
impl<'a, E> From<E> for Report<Box<dyn Error + 'a>>
|
||||
where
|
||||
E: Error + 'a,
|
||||
{
|
||||
fn from(error: E) -> Self {
|
||||
let error = box error;
|
||||
Report { error, show_backtrace: false, pretty: false }
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
impl<E> fmt::Display for Report<E>
|
||||
where
|
||||
E: Error,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.pretty { self.fmt_multiline(f) } else { self.fmt_singleline(f) }
|
||||
}
|
||||
}
|
||||
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
impl fmt::Display for Report<Box<dyn Error>> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if self.pretty { self.fmt_multiline(f) } else { self.fmt_singleline(f) }
|
||||
}
|
||||
}
|
||||
|
||||
// This type intentionally outputs the same format for `Display` and `Debug`for
|
||||
// situations where you unwrap a `Report` or return it from main.
|
||||
#[unstable(feature = "error_reporter", issue = "90172")]
|
||||
impl<E> fmt::Debug for Report<E>
|
||||
where
|
||||
Report<E>: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(self, f)
|
||||
}
|
||||
}
|
||||
|
||||
/// Wrapper type for indenting the inner source.
|
||||
struct Indented<'a, D> {
|
||||
inner: &'a mut D,
|
||||
}
|
||||
|
||||
impl<T> Write for Indented<'_, T>
|
||||
where
|
||||
T: Write,
|
||||
{
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
for (i, line) in s.split('\n').enumerate() {
|
||||
if i > 0 {
|
||||
self.inner.write_char('\n')?;
|
||||
self.inner.write_str(" ")?;
|
||||
}
|
||||
|
||||
self.inner.write_str(line)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -35,3 +35,408 @@ fn downcasting() {
|
||||
Err(e) => assert_eq!(*e.downcast::<A>().unwrap(), A),
|
||||
}
|
||||
}
|
||||
|
||||
use crate::backtrace::Backtrace;
|
||||
use crate::error::Report;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SuperError {
|
||||
source: SuperErrorSideKick,
|
||||
}
|
||||
|
||||
impl fmt::Display for SuperError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "SuperError is here!")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SuperError {
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
Some(&self.source)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct SuperErrorSideKick;
|
||||
|
||||
impl fmt::Display for SuperErrorSideKick {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "SuperErrorSideKick is here!")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for SuperErrorSideKick {}
|
||||
|
||||
#[test]
|
||||
fn single_line_formatting() {
|
||||
let error = SuperError { source: SuperErrorSideKick };
|
||||
let report = Report::new(&error);
|
||||
let actual = report.to_string();
|
||||
let expected = String::from("SuperError is here!: SuperErrorSideKick is here!");
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multi_line_formatting() {
|
||||
let error = SuperError { source: SuperErrorSideKick };
|
||||
let report = Report::new(&error).pretty(true);
|
||||
let actual = report.to_string();
|
||||
let expected = String::from(
|
||||
"\
|
||||
SuperError is here!
|
||||
|
||||
Caused by:
|
||||
SuperErrorSideKick is here!",
|
||||
);
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_with_no_sources_formats_single_line_correctly() {
|
||||
let report = Report::new(SuperErrorSideKick);
|
||||
let actual = report.to_string();
|
||||
let expected = String::from("SuperErrorSideKick is here!");
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_with_no_sources_formats_multi_line_correctly() {
|
||||
let report = Report::new(SuperErrorSideKick).pretty(true);
|
||||
let actual = report.to_string();
|
||||
let expected = String::from("SuperErrorSideKick is here!");
|
||||
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_with_backtrace_outputs_correctly_with_one_source() {
|
||||
let trace = Backtrace::force_capture();
|
||||
let expected = format!(
|
||||
"\
|
||||
The source of the error
|
||||
|
||||
Caused by:
|
||||
Error with backtrace
|
||||
|
||||
Stack backtrace:
|
||||
{}",
|
||||
trace
|
||||
);
|
||||
let error = GenericError::new("Error with backtrace");
|
||||
let mut error = GenericError::new_with_source("The source of the error", error);
|
||||
error.backtrace = Some(trace);
|
||||
let report = Report::new(error).pretty(true).show_backtrace(true);
|
||||
|
||||
println!("Error: {}", report);
|
||||
assert_eq!(expected.trim_end(), report.to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_with_backtrace_outputs_correctly_with_two_sources() {
|
||||
let trace = Backtrace::force_capture();
|
||||
let expected = format!(
|
||||
"\
|
||||
Error with two sources
|
||||
|
||||
Caused by:
|
||||
0: The source of the error
|
||||
1: Error with backtrace
|
||||
|
||||
Stack backtrace:
|
||||
{}",
|
||||
trace
|
||||
);
|
||||
let mut error = GenericError::new("Error with backtrace");
|
||||
error.backtrace = Some(trace);
|
||||
let error = GenericError::new_with_source("The source of the error", error);
|
||||
let error = GenericError::new_with_source("Error with two sources", error);
|
||||
let report = Report::new(error).pretty(true).show_backtrace(true);
|
||||
|
||||
println!("Error: {}", report);
|
||||
assert_eq!(expected.trim_end(), report.to_string());
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct GenericError<D> {
|
||||
message: D,
|
||||
backtrace: Option<Backtrace>,
|
||||
source: Option<Box<dyn Error + 'static>>,
|
||||
}
|
||||
|
||||
impl<D> GenericError<D> {
|
||||
fn new(message: D) -> GenericError<D> {
|
||||
Self { message, backtrace: None, source: None }
|
||||
}
|
||||
|
||||
fn new_with_source<E>(message: D, source: E) -> GenericError<D>
|
||||
where
|
||||
E: Error + 'static,
|
||||
{
|
||||
let source: Box<dyn Error + 'static> = Box::new(source);
|
||||
let source = Some(source);
|
||||
GenericError { message, backtrace: None, source }
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> fmt::Display for GenericError<D>
|
||||
where
|
||||
D: fmt::Display,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.message, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl<D> Error for GenericError<D>
|
||||
where
|
||||
D: fmt::Debug + fmt::Display,
|
||||
{
|
||||
fn source(&self) -> Option<&(dyn Error + 'static)> {
|
||||
self.source.as_deref()
|
||||
}
|
||||
|
||||
fn backtrace(&self) -> Option<&Backtrace> {
|
||||
self.backtrace.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_formats_single_line_with_rude_display_impl() {
|
||||
#[derive(Debug)]
|
||||
struct MyMessage;
|
||||
|
||||
impl fmt::Display for MyMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("line 1\nline 2")?;
|
||||
f.write_str("\nline 3\nline 4\n")?;
|
||||
f.write_str("line 5\nline 6")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let error = GenericError::new(MyMessage);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let report = Report::new(error);
|
||||
let expected = "\
|
||||
line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6: line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6: line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6: line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6";
|
||||
|
||||
let actual = report.to_string();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_formats_multi_line_with_rude_display_impl() {
|
||||
#[derive(Debug)]
|
||||
struct MyMessage;
|
||||
|
||||
impl fmt::Display for MyMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("line 1\nline 2")?;
|
||||
f.write_str("\nline 3\nline 4\n")?;
|
||||
f.write_str("line 5\nline 6")?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
let error = GenericError::new(MyMessage);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let report = Report::new(error).pretty(true);
|
||||
let expected = "line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6
|
||||
|
||||
Caused by:
|
||||
0: line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6
|
||||
1: line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6
|
||||
2: line 1
|
||||
line 2
|
||||
line 3
|
||||
line 4
|
||||
line 5
|
||||
line 6";
|
||||
|
||||
let actual = report.to_string();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_that_start_with_newline_formats_correctly() {
|
||||
#[derive(Debug)]
|
||||
struct MyMessage;
|
||||
|
||||
impl fmt::Display for MyMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("\nThe message\n")
|
||||
}
|
||||
}
|
||||
|
||||
let error = GenericError::new(MyMessage);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let report = Report::new(error).pretty(true);
|
||||
let expected = "
|
||||
The message
|
||||
|
||||
|
||||
Caused by:
|
||||
0: \
|
||||
\n The message
|
||||
\
|
||||
\n 1: \
|
||||
\n The message
|
||||
";
|
||||
|
||||
let actual = report.to_string();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_with_multiple_writes_on_same_line_dont_insert_erroneous_newlines() {
|
||||
#[derive(Debug)]
|
||||
struct MyMessage;
|
||||
|
||||
impl fmt::Display for MyMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("The message")?;
|
||||
f.write_str(" goes on")?;
|
||||
f.write_str(" and on.")
|
||||
}
|
||||
}
|
||||
|
||||
let error = GenericError::new(MyMessage);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let report = Report::new(error).pretty(true);
|
||||
let expected = "\
|
||||
The message goes on and on.
|
||||
|
||||
Caused by:
|
||||
0: The message goes on and on.
|
||||
1: The message goes on and on.";
|
||||
|
||||
let actual = report.to_string();
|
||||
println!("{}", actual);
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn errors_with_string_interpolation_formats_correctly() {
|
||||
#[derive(Debug)]
|
||||
struct MyMessage(usize);
|
||||
|
||||
impl fmt::Display for MyMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Got an error code: ({}). ", self.0)?;
|
||||
write!(f, "What would you like to do in response?")
|
||||
}
|
||||
}
|
||||
|
||||
let error = GenericError::new(MyMessage(10));
|
||||
let error = GenericError::new_with_source(MyMessage(20), error);
|
||||
let report = Report::new(error).pretty(true);
|
||||
let expected = "\
|
||||
Got an error code: (20). What would you like to do in response?
|
||||
|
||||
Caused by:
|
||||
Got an error code: (10). What would you like to do in response?";
|
||||
let actual = report.to_string();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_lines_mid_message() {
|
||||
#[derive(Debug)]
|
||||
struct MyMessage;
|
||||
|
||||
impl fmt::Display for MyMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("line 1\n\nline 2")
|
||||
}
|
||||
}
|
||||
|
||||
let error = GenericError::new(MyMessage);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let report = Report::new(error).pretty(true);
|
||||
let expected = "\
|
||||
line 1
|
||||
|
||||
line 2
|
||||
|
||||
Caused by:
|
||||
0: line 1
|
||||
\
|
||||
\n line 2
|
||||
1: line 1
|
||||
\
|
||||
\n line 2";
|
||||
|
||||
let actual = report.to_string();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn only_one_source() {
|
||||
#[derive(Debug)]
|
||||
struct MyMessage;
|
||||
|
||||
impl fmt::Display for MyMessage {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("line 1\nline 2")
|
||||
}
|
||||
}
|
||||
|
||||
let error = GenericError::new(MyMessage);
|
||||
let error = GenericError::new_with_source(MyMessage, error);
|
||||
let report = Report::new(error).pretty(true);
|
||||
let expected = "\
|
||||
line 1
|
||||
line 2
|
||||
|
||||
Caused by:
|
||||
line 1
|
||||
line 2";
|
||||
|
||||
let actual = report.to_string();
|
||||
assert_eq!(expected, actual);
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user