how.rs/src/context.rs

282 lines
7.0 KiB
Rust

#[cfg(feature = "backtrace")]
use std::backtrace::BacktraceStatus;
use std::borrow::Cow;
use std::fmt;
use std::panic::Location;
use std::sync::Arc;
use crate::report::report_write;
use crate::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 Context {
pub(crate) detail: Detail,
pub(crate) extra: Vec<Detail>,
}
impl Context {
pub(crate) fn new(detail: Detail) -> Self {
Self {
detail,
extra: Vec::new(),
}
}
pub fn detail(&self) -> &Detail {
&self.detail
}
pub fn extra(&self) -> &[Detail] {
&self.extra
}
pub fn detail_mut(&mut self) -> &mut Detail {
&mut self.detail
}
pub fn extra_mut(&mut self) -> &mut [Detail] {
&mut self.extra
}
pub fn push_extra(&mut self, detail: impl IntoContext) {
let detail = detail.into_context();
if detail.extra.is_empty() {
self.extra.push(detail.detail);
} else {
self.extra.push(Detail::Context(detail.into()));
}
}
pub fn pop_extra(&mut self) -> Option<Detail> {
self.extra.pop()
}
}
impl fmt::Display for Context {
#[inline(always)]
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.detail.fmt(f)?;
let opts = ReportOpts::default();
for detail in &self.extra {
f.write_str("\n")?;
report_write!(f, &opts.indent().next(), "- {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>),
#[cfg(feature = "backtrace")]
Backtrace(PrivateBacktrace),
Error(PrivateError),
Context(Box<Context>),
}
impl Detail {
#[cfg(feature = "backtrace")]
#[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>);
#[cfg(feature = "backtrace")]
#[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
#[cfg(feature = "backtrace")]
#[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 = "backtrace",
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}"),
#[cfg(feature = "backtrace")]
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",
),
#[cfg(feature = "backtrace")]
Self::Backtrace(PrivateBacktrace(Backtrace::Disabled)) => {
f.write_str("If you'd like a backtrace,\n try again with RUST_BACKTRACE=1")
}
#[cfg(feature = "backtrace")]
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::Context(c) => c.fmt(f),
}
}
}
pub trait IntoContext: Sized {
fn into_context(self) -> Context;
#[inline(always)]
fn with(self, other: impl IntoContext) -> Context {
self.into_context().with(other)
}
#[inline(always)]
#[track_caller]
fn with_caller(self) -> Context {
self.with(Location::caller())
}
#[inline(always)]
#[track_caller]
fn with_backtrace(self) -> Context {
#[cfg(feature = "backtrace")]
return self.with(Detail::backtrace());
#[cfg(not(feature = "backtrace"))]
self.into_context()
}
}
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(mut self, other: impl IntoContext) -> Self {
self.push_extra(other);
self
}
}
impl IntoContext for String {
#[inline(always)]
fn into_context(self) -> Context {
Detail::String(self).into_context()
}
}
impl IntoContext for &'static str {
#[inline(always)]
fn into_context(self) -> Context {
Detail::Str(self).into_context()
}
}
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 {
Detail::Location(self).into_context()
}
}
impl<E> IntoContext for Arc<E>
where
E: std::error::Error + Send + Sync + 'static,
{
#[inline]
fn into_context(self) -> Context {
Detail::Error(PrivateError(self)).into_context()
}
}
impl IntoContext for Arc<dyn std::error::Error + Send + Sync> {
#[inline]
fn into_context(self) -> Context {
Detail::Error(PrivateError(self)).into_context()
}
}
impl IntoContext for Detail {
#[inline(always)]
fn into_context(self) -> Context {
Context::new(self)
}
}
impl<C, F> IntoContext for F
where
C: IntoContext,
F: FnOnce() -> C,
{
#[inline(always)]
fn into_context(self) -> Context {
self().into_context()
}
}