305 lines
7.9 KiB
Rust
305 lines
7.9 KiB
Rust
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<DetailTree>,
|
|
}
|
|
|
|
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<DetailTree> {
|
|
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::<false>,
|
|
&format_args!("└╼ {detail}"),
|
|
)?;
|
|
} else if detail.is_multi_line() {
|
|
fmt_report(
|
|
f,
|
|
&opts.indent().next(),
|
|
DetailIndent::<true>,
|
|
&format_args!("├╼ {detail}"),
|
|
)?;
|
|
} else {
|
|
fmt_report(
|
|
f,
|
|
&opts.indent().next(),
|
|
DetailIndent::<false>,
|
|
&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<dyn std::error::Error + Send + Sync>);
|
|
|
|
#[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<std::backtrace::Backtrace>),
|
|
#[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<E> IntoDetails for Arc<E>
|
|
where
|
|
E: std::error::Error + Send + Sync + 'static,
|
|
{
|
|
#[inline]
|
|
fn into_details(self) -> DetailTree {
|
|
Detail::Error(PrivateError(self)).into_details()
|
|
}
|
|
}
|
|
|
|
impl IntoDetails for Arc<dyn std::error::Error + Send + Sync> {
|
|
#[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<C, F> IntoDetails for F
|
|
where
|
|
C: IntoDetails,
|
|
F: FnOnce() -> C,
|
|
{
|
|
#[inline(always)]
|
|
fn into_details(self) -> DetailTree {
|
|
self().into_details()
|
|
}
|
|
}
|