use std::borrow::Cow; #[cfg(feature = "backtrace")] use std::backtrace::BacktraceStatus; use std::fmt; use std::panic::Location; use std::sync::Arc; /// Provides context furthering the explanation of *how* you got to an error. #[derive(Debug)] #[cfg_attr(feature = "clone-with-caveats", derive(Clone))] pub struct Context(pub(crate) ContextInner); impl fmt::Display for Context { #[inline(always)] fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } #[derive(Debug)] #[cfg_attr(feature = "clone-with-caveats", derive(Clone))] pub(crate) enum ContextInner { Elem(Detail), Compound(Vec), } #[derive(Debug)] #[cfg_attr(feature = "clone-with-caveats", derive(Clone))] pub(crate) enum Detail { Str(&'static str), String(String), Location(Location<'static>), #[cfg(feature = "backtrace")] Backtrace(Backtrace), Error(Arc), } // will be replaced with std::backtrace::Backtrace if and when it is Clone #[cfg(feature = "backtrace")] #[derive(Debug)] pub(crate) enum Backtrace { Disabled, Unsupported, Other(BacktraceStatus, String), } #[cfg(all(feature = "backtrace", feature = "clone-with-caveats"))] impl Clone for Backtrace { fn clone(&self) -> Self { Backtrace::Disabled } } impl fmt::Display for ContextInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { Self::Elem(elem) => fmt::Display::fmt(elem, f), Self::Compound(elems) => { let mut elems = elems.iter(); if let Some(elem) = elems.next() { fmt::Display::fmt(elem, f)?; for elem in elems { write!(f, "\n- {elem}")?; } } Ok(()) } } } } 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}"), #[cfg(feature = "backtrace")] Self::Backtrace(Backtrace::Unsupported) => f.write_str("\nI'd like to show you a backtrace,\n but it's not supported on your platform"), #[cfg(feature = "backtrace")] Self::Backtrace(Backtrace::Disabled) => f.write_str("\nIf you'd like a backtrace,\n try again with RUST_BACKTRACE=1"), #[cfg(feature = "backtrace")] Self::Backtrace(Backtrace::Other(status, bt)) => { f.write_str(if *status == BacktraceStatus::Captured { "\nHere is the backtrace:\n" } else { "\nI can't tell if backtraces are working,\n but I'll give it a go:\n" })?; write!(f, "{}", bt) }, Self::Error(e) => e.fmt(f), } } } pub trait IntoContext { fn into_context(self) -> Context; #[inline(always)] fn with(self, other: impl IntoContext) -> Context where Self: Sized, { self.into_context().with(other) } } impl IntoContext for Context { #[inline(always)] fn into_context(self) -> Context { self } /// Chains another piece of context that is a child from a hierarchical perspective. #[track_caller] #[inline] fn with(self, other: impl IntoContext) -> Self { let other = other.into_context().0; Context(ContextInner::Compound(match self.0 { ContextInner::Compound(mut elems) => { match other { ContextInner::Elem(elem) => elems.push(elem), ContextInner::Compound(mut elems1) => elems.append(&mut elems1), }; elems } ContextInner::Elem(elem) => match other { ContextInner::Elem(elem1) => vec![elem, elem1], ContextInner::Compound(mut elems) => { elems.insert(0, elem); elems } }, })) } } impl IntoContext for String { #[inline(always)] fn into_context(self) -> Context { Context(ContextInner::Elem(Detail::String(self))) } } impl IntoContext for &'static str { #[inline(always)] fn into_context(self) -> Context { Context(ContextInner::Elem(Detail::Str(self))) } } impl IntoContext for Cow<'static, str> { #[inline] fn into_context(self) -> Context { match self { Cow::Borrowed(s) => s.into_context(), Cow::Owned(s) => s.into_context(), } } } impl<'a> IntoContext for &'a Location<'static> { #[inline] fn into_context(self) -> Context { Location::into_context(*self) } } impl IntoContext for Location<'static> { #[inline] fn into_context(self) -> Context { Context(ContextInner::Elem(Detail::Location(self))) } } impl IntoContext for F where C: IntoContext, F: FnOnce() -> C, { #[inline(always)] fn into_context(self) -> Context { self().into_context() } }