pub fn format_error(err: &anyhow::Error) -> String {
let mut lines: Vec<String> = Vec::new();
for cause in err.chain() {
let text = cause.to_string();
if lines.iter().any(|line| line.contains(&text)) {
continue;
}
lines.push(text);
}
lines.join("\n")
}
#[cfg(test)]
mod tests {
use super::format_error;
use anyhow::anyhow;
use std::fmt;
#[derive(Debug)]
struct Wrapping {
message: &'static str,
cause: Inner,
}
impl fmt::Display for Wrapping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "outer: {}", self.message)
}
}
impl std::error::Error for Wrapping {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.cause)
}
}
#[derive(Debug)]
struct Inner(&'static str);
impl fmt::Display for Inner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for Inner {}
#[test]
fn drops_a_cause_whose_text_is_already_embedded_in_the_outer_message() {
let err = anyhow::Error::new(Wrapping {
message: "something went wrong",
cause: Inner("something went wrong"),
});
assert_eq!(format_error(&err), "outer: something went wrong");
}
#[test]
fn keeps_a_genuinely_separate_context_cause_on_its_own_line() {
let io_err = std::io::Error::other("disk full");
let err = anyhow!(io_err).context("Failed to write the output file");
assert_eq!(format_error(&err), "Failed to write the output file\ndisk full");
}
}