Skip to content

Commit

Permalink
Automatically detect and remove nested error messages in Report output
Browse files Browse the repository at this point in the history
Closes #363
  • Loading branch information
shepmaster committed Oct 21, 2022
1 parent e491da4 commit 214da81
Show file tree
Hide file tree
Showing 2 changed files with 127 additions and 6 deletions.
90 changes: 84 additions & 6 deletions src/report.rs
Expand Up @@ -219,6 +219,35 @@ struct ReportFormatter<'a>(&'a dyn crate::Error);

impl<'a> fmt::Display for ReportFormatter<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
#[cfg(feature = "std")]
{
if trace_cleaning_enabled() {
self.cleaned_error_trace(f)?;
} else {
self.error_trace(f)?;
}
}

#[cfg(not(feature = "std"))]
{
self.error_trace(f)?;
}

#[cfg(feature = "unstable-provider-api")]
{
use core::any;

if let Some(bt) = any::request_ref::<crate::Backtrace>(self.0) {
writeln!(f, "\nBacktrace:\n{}", bt)?;
}
}

Ok(())
}
}

impl<'a> ReportFormatter<'a> {
fn error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
writeln!(f, "{}", self.0)?;

let sources = ChainCompat::new(self.0).skip(1);
Expand All @@ -236,19 +265,68 @@ impl<'a> fmt::Display for ReportFormatter<'a> {
writeln!(f, "{:3}: {}", i, source)?;
}

#[cfg(feature = "unstable-provider-api")]
{
use core::any;
Ok(())
}

if let Some(bt) = any::request_ref::<crate::Backtrace>(self.0) {
writeln!(f, "\nBacktrace:\n{}", bt)?;
}
#[cfg(feature = "std")]
fn cleaned_error_trace(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
let original_messages: Vec<_> = ChainCompat::new(self.0).map(ToString::to_string).collect();

let message_pairs = original_messages
.iter()
.zip(original_messages.iter().skip(1));
let cleaned_messages = message_pairs.map(|(a, b)| {
let cleaned = a.trim_end_matches(b).trim_end().trim_end_matches(":");
(cleaned, a != cleaned)
});
let mut cleaned_messages =
cleaned_messages.chain(original_messages.last().map(|msg| (&**msg, false)));

let (head, cleaned) = match cleaned_messages.next() {
Some(v) => v,
None => return Ok(()),
};

let mut any_cleaned = cleaned;
let marker = |b| if b { " *" } else { "" };

writeln!(f, "{}{}", head, marker(cleaned))?;

let plurality = original_messages.len();

match plurality {
0 => {}
1 => writeln!(f, "\nCaused by this error:")?,
_ => writeln!(f, "\nCaused by these errors (recent errors listed first):")?,
}

for (i, (source, cleaned)) in cleaned_messages.enumerate() {
// Let's use 1-based indexing for presentation
let i = i + 1;
writeln!(f, "{:3}: {}{}", i, source, marker(cleaned))?;
any_cleaned = any_cleaned || cleaned;
}

if any_cleaned {
writeln!(f, "\nNOTE: Some redundant information has been removed from the lines marked with *. Set {}=1 to disable this behavior.", SNAFU_RAW_ERROR_MESSAGES)?;
}

Ok(())
}
}

#[cfg(feature = "std")]
const SNAFU_RAW_ERROR_MESSAGES: &str = "SNAFU_RAW_ERROR_MESSAGES";

#[cfg(feature = "std")]
fn trace_cleaning_enabled() -> bool {
use crate::once_bool::OnceBool;
use std::env;

static DISABLED: OnceBool = OnceBool::new();
!DISABLED.get(|| env::var_os(SNAFU_RAW_ERROR_MESSAGES).map_or(false, |v| v == "1"))
}

#[doc(hidden)]
pub trait __InternalExtractErrorType {
type Err;
Expand Down
43 changes: 43 additions & 0 deletions tests/report.rs
Expand Up @@ -43,6 +43,49 @@ fn includes_the_source_display_text() {
);
}

#[test]
fn reduces_duplication_of_the_source_display_text() {
// Including the source in the Display message is discouraged but
// quite common.

#[derive(Debug, Snafu)]
#[snafu(display("Level 0"))]
struct Level0Error;

#[derive(Debug, Snafu)]
#[snafu(display("Level 1: {source}"))]
struct Level1Error {
source: Level0Error,
}

#[derive(Debug, Snafu)]
#[snafu(display("Level 2: {source}"))]
struct Level2Error {
source: Level1Error,
}

let e = Level2Snafu.into_error(Level1Snafu.into_error(Level0Error));
let raw_msg = e.to_string();

let expected = "Level 2: Level 1";
assert!(
raw_msg.contains(expected),
"Expected {:?} to include {:?}",
raw_msg,
expected,
);

let r = Report::from_error(e);
let msg = r.to_string();

assert!(
!msg.contains(expected),
"Expected {:?} to not include {:?}",
msg,
expected,
);
}

#[test]
fn debug_and_display_are_the_same() {
#[derive(Debug, Snafu)]
Expand Down

0 comments on commit 214da81

Please sign in to comment.