use std::backtrace::BacktraceStatus; use std::borrow::Cow; use std::fmt; use std::panic::Location; use std::sync::Arc; use crate::{ report::{fmt_report, DetailIndent}, How, ReportOpts, }; /// Provides context furthering the explanation of *how* you got to an error. #[derive(Debug)] #[cfg_attr( any(feature = "arc-backtrace", feature = "clone-with-caveats"), derive(Clone) )] pub struct DetailTree { pub(crate) detail: Detail, pub(crate) extra: Vec, } impl DetailTree { pub(crate) fn new(detail: Detail) -> Self { Self { detail, extra: Vec::new(), } } pub fn detail(&self) -> &Detail { &self.detail } pub fn extra(&self) -> &[DetailTree] { &self.extra } pub fn detail_mut(&mut self) -> &mut Detail { &mut self.detail } pub fn extra_mut(&mut self) -> &mut [DetailTree] { &mut self.extra } pub fn push_extra(&mut self, detail: impl IntoDetails) { let detail = detail.into_details(); self.extra.push(detail); } pub fn pop_extra(&mut self) -> Option { self.extra.pop() } fn is_multi_line(&self) -> bool { if self.extra().is_empty() { match self.detail { Detail::Str(s) => s.contains('\n'), Detail::String(ref s) => s.contains('\n'), Detail::Location(_) => false, Detail::Backtrace(_) => true, Detail::Error(_) => false, Detail::HowError(ref e) => e.0.context.len() > 1, } } else { true } } } impl fmt::Display for DetailTree { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.detail.fmt(f)?; let opts = ReportOpts::default(); let mut iter = self.extra.iter().peekable(); while let Some(detail) = iter.next() { f.write_str("\n")?; if iter.peek().is_none() { fmt_report( f, &opts.indent().next(), DetailIndent::, &format_args!("└╼ {detail}"), )?; } else if detail.is_multi_line() { fmt_report( f, &opts.indent().next(), DetailIndent::, &format_args!("├╼ {detail}"), )?; } else { fmt_report( f, &opts.indent().next(), DetailIndent::, &format_args!("├╼ {detail}"), )?; } } Ok(()) } } #[derive(Debug)] #[cfg_attr( any(feature = "arc-backtrace", feature = "clone-with-caveats"), derive(Clone) )] #[non_exhaustive] pub enum Detail { Str(&'static str), String(String), Location(Location<'static>), Backtrace(PrivateBacktrace), Error(PrivateError), HowError(How), } impl Detail { #[track_caller] pub fn backtrace() -> Self { use std::backtrace::BacktraceStatus::*; let bt = std::backtrace::Backtrace::capture(); let bt = match bt.status() { Disabled => Backtrace::Disabled, Unsupported => Backtrace::Unsupported, _ => Backtrace::Other(bt.into()), }; Self::Backtrace(PrivateBacktrace(bt)) } } #[derive(Debug, Clone)] pub struct PrivateError(pub(crate) Arc); #[derive(Debug)] #[cfg_attr( any(feature = "arc-backtrace", feature = "clone-with-caveats"), derive(Clone) )] pub struct PrivateBacktrace(pub(crate) Backtrace); // will be replaced with std::backtrace::Backtrace if and when it is Clone #[derive(Debug)] #[cfg_attr(feature = "arc-backtrace", derive(Clone))] pub(crate) enum Backtrace { Disabled, Unsupported, #[cfg(feature = "arc-backtrace")] Other(Arc), #[cfg(not(feature = "arc-backtrace"))] Other(std::backtrace::Backtrace), } #[cfg(all(feature = "clone-with-caveats", not(feature = "arc-backtrace")))] impl Clone for Backtrace { fn clone(&self) -> Self { match self { Self::Unsupported => Self::Unsupported, _ => Self::Disabled, } } } impl fmt::Display for Detail { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Str(s) => f.write_str(s), Self::String(s) => f.write_str(s), Self::Location(l) => write!(f, "at {l}"), Self::Backtrace(PrivateBacktrace(Backtrace::Unsupported)) => f.write_str( "I'd like to show you a backtrace,\n but it's not supported on your platform", ), Self::Backtrace(PrivateBacktrace(Backtrace::Disabled)) => { f.write_str("If you'd like a backtrace,\n try again with RUST_BACKTRACE=1") } Self::Backtrace(PrivateBacktrace(Backtrace::Other(bt))) => { f.write_str(if bt.status() == BacktraceStatus::Captured { "Here is the backtrace:\n" } else { "I can't tell if backtraces are working,\n but I'll give it a go:\n" })?; write!(f, "{}", bt) } Self::Error(PrivateError(e)) => e.fmt(f), Self::HowError(e) => e.fmt(f), } } } pub trait IntoDetails: Sized { fn into_details(self) -> DetailTree; /// Annotates the context with the given detail. #[inline(always)] fn with(self, detail: impl IntoDetails) -> DetailTree { self.into_details().with(detail) } /// Annotates the context with the caller location. #[inline(always)] #[track_caller] fn with_caller(self) -> DetailTree { self.with(Location::caller()) } /// Annotates the context with the current backtrace. #[inline(always)] #[track_caller] fn with_backtrace(self) -> DetailTree { self.with(Detail::backtrace()) } } impl IntoDetails for DetailTree { #[inline(always)] fn into_details(self) -> DetailTree { self } /// Chains another piece of context that is a child from a hierarchical perspective. #[track_caller] #[inline] fn with(mut self, other: impl IntoDetails) -> Self { self.push_extra(other); self } } impl IntoDetails for String { #[inline(always)] fn into_details(self) -> DetailTree { Detail::String(self).into_details() } } impl IntoDetails for &'static str { #[inline(always)] fn into_details(self) -> DetailTree { Detail::Str(self).into_details() } } impl IntoDetails for Cow<'static, str> { #[inline] fn into_details(self) -> DetailTree { match self { Cow::Borrowed(s) => s.into_details(), Cow::Owned(s) => s.into_details(), } } } impl<'a> IntoDetails for &'a Location<'static> { #[inline] fn into_details(self) -> DetailTree { Location::into_details(*self) } } impl IntoDetails for Location<'static> { #[inline] fn into_details(self) -> DetailTree { Detail::Location(self).into_details() } } impl IntoDetails for Arc where E: std::error::Error + Send + Sync + 'static, { #[inline] fn into_details(self) -> DetailTree { Detail::Error(PrivateError(self)).into_details() } } impl IntoDetails for Arc { #[inline] fn into_details(self) -> DetailTree { Detail::Error(PrivateError(self)).into_details() } } impl IntoDetails for Detail { #[inline(always)] fn into_details(self) -> DetailTree { DetailTree::new(self) } } impl IntoDetails for F where C: IntoDetails, F: FnOnce() -> C, { #[inline(always)] fn into_details(self) -> DetailTree { self().into_details() } }