use std::fmt;
pub trait AsReport: crate::error_sealed::Sealed {
fn as_report(&self) -> Report<'_>;
fn to_report_string(&self) -> String {
format!("{}", self.as_report())
}
fn to_report_string_with_backtrace(&self) -> String {
format!("{:?}", self.as_report())
}
fn to_report_string_pretty(&self) -> String {
format!("{:#}", self.as_report())
}
fn to_report_string_pretty_with_backtrace(&self) -> String {
format!("{:#?}", self.as_report())
}
}
impl<T: std::error::Error> AsReport for T {
fn as_report(&self) -> Report<'_> {
Report(self)
}
}
macro_rules! impl_as_report {
($({$ty:ty },)*) => {
$(
impl AsReport for $ty {
fn as_report(&self) -> Report<'_> {
Report(self)
}
}
)*
};
}
crate::for_dyn_error_types! { impl_as_report }
pub struct Report<'a>(pub &'a dyn std::error::Error);
impl fmt::Display for Report<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.cleaned_error_trace(f, f.alternate())
}
}
impl fmt::Debug for Report<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.cleaned_error_trace(f, f.alternate())?;
#[cfg(feature = "backtrace")]
{
use std::backtrace::{Backtrace, BacktraceStatus};
if let Some(bt) = std::error::request_ref::<Backtrace>(self.0) {
let force_show_backtrace = cfg!(debug_assertions)
&& std::env::var("THISERROR_EXT_TEST_SHOW_USELESS_BACKTRACE").is_ok();
if bt.status() == BacktraceStatus::Captured || force_show_backtrace {
if !f.alternate() {
writeln!(f)?;
}
writeln!(f, "\nBacktrace:\n{}", bt)?;
}
}
}
Ok(())
}
}
impl Report<'_> {
fn cleaned_error_trace(&self, f: &mut fmt::Formatter, pretty: bool) -> Result<(), fmt::Error> {
let cleaned_messages: Vec<_> = CleanedErrorText::new(self.0)
.flat_map(|(_error, msg, _cleaned)| Some(msg).filter(|msg| !msg.is_empty()))
.collect();
let mut visible_messages = cleaned_messages.iter();
let head = match visible_messages.next() {
Some(v) => v,
None => return Ok(()),
};
write!(f, "{}", head)?;
if pretty {
match cleaned_messages.len() {
0 | 1 => {}
2 => {
writeln!(f, "\n\nCaused by:")?;
writeln!(f, " {}", visible_messages.next().unwrap())?;
}
_ => {
writeln!(
f,
"\n\nCaused by these errors (recent errors listed first):"
)?;
for (i, msg) in visible_messages.enumerate() {
let i = i + 1;
writeln!(f, "{:3}: {}", i, msg)?;
}
}
}
} else {
for msg in visible_messages {
write!(f, ": {}", msg)?;
}
}
Ok(())
}
}
struct CleanedErrorText<'a>(Option<CleanedErrorTextStep<'a>>);
impl<'a> CleanedErrorText<'a> {
fn new(error: &'a dyn std::error::Error) -> Self {
Self(Some(CleanedErrorTextStep::new(error)))
}
}
impl<'a> Iterator for CleanedErrorText<'a> {
type Item = (&'a dyn std::error::Error, String, bool);
fn next(&mut self) -> Option<Self::Item> {
use std::mem;
let mut step = self.0.take()?;
let mut error_text = mem::take(&mut step.error_text);
match step.error.source() {
Some(next_error) => {
let next_error_text = next_error.to_string();
let cleaned_text = error_text
.trim_end_matches(&next_error_text)
.trim_end()
.trim_end_matches(':');
let cleaned = cleaned_text.len() != error_text.len();
let cleaned_len = cleaned_text.len();
error_text.truncate(cleaned_len);
self.0 = Some(CleanedErrorTextStep {
error: next_error,
error_text: next_error_text,
});
Some((step.error, error_text, cleaned))
}
None => Some((step.error, error_text, false)),
}
}
}
struct CleanedErrorTextStep<'a> {
error: &'a dyn std::error::Error,
error_text: String,
}
impl<'a> CleanedErrorTextStep<'a> {
fn new(error: &'a dyn std::error::Error) -> Self {
let error_text = error.to_string();
Self { error, error_text }
}
}