use core::fmt;
use std::backtrace::Backtrace;
use super::BoxError;
#[derive(Debug)]
pub(super) struct ErrorWithBacktrace {
source: BoxError,
backtrace: Backtrace,
}
impl ErrorWithBacktrace {
pub(super) fn new(source: BoxError) -> Self {
Self {
source,
backtrace: Backtrace::capture(),
}
}
}
impl fmt::Display for ErrorWithBacktrace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if !f.alternate() {
return write!(f, "{}", self.source);
}
writeln!(f, "{}", self.source)?;
writeln!(f, "Backtrace:")?;
writeln!(f, "{}", self.backtrace)?;
Ok(())
}
}
impl core::error::Error for ErrorWithBacktrace {
fn source(&self) -> Option<&(dyn core::error::Error + 'static)> {
Some(self.source.as_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::error::Error as _;
use std::io;
#[test]
fn display_non_alternate_prints_only_source() {
let src = io::Error::other("boom");
let err = ErrorWithBacktrace::new(Box::new(src));
let s = format!("{err}");
assert_eq!(s, "boom");
}
#[test]
fn display_alternate_includes_source_and_backtrace_label() {
let src = io::Error::other("boom");
let err = ErrorWithBacktrace::new(Box::new(src));
let s = format!("{err:#}");
assert!(s.starts_with("boom\n"), "got: {s:?}");
assert!(
s.contains("\nBacktrace:\n") || s.contains("\nBacktrace:\r\n"),
"got: {s:?}"
);
let parts: crate::std::Vec<&str> = s.split("Backtrace:").collect();
assert_eq!(parts.len(), 2, "got: {s:?}");
assert!(
!parts[1].trim().is_empty(),
"expected something after Backtrace:, got: {s:?}"
);
}
#[test]
fn source_returns_inner_error() {
let src = io::Error::other("boom");
let err = ErrorWithBacktrace::new(Box::new(src));
let src_ref = err.source().expect("source should exist");
assert_eq!(src_ref.to_string(), "boom");
}
#[test]
fn debug_includes_type_name() {
let src = io::Error::other("boom");
let err = ErrorWithBacktrace::new(Box::new(src));
let s = format!("{err:?}");
assert!(s.contains("ErrorWithBacktrace"), "got: {s:?}");
}
}