Refactor and redesign API
This commit is contained in:
parent
cab16130f7
commit
db01cce71c
|
@ -5,8 +5,6 @@ edition = "2021"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
backtrace = []
|
|
||||||
extra-backtrace = ["backtrace"]
|
|
||||||
clone-with-caveats = []
|
clone-with-caveats = []
|
||||||
arc-backtrace = []
|
arc-backtrace = []
|
||||||
termination = ["dep:ansee"]
|
termination = ["dep:ansee"]
|
||||||
|
|
173
src/context.rs
173
src/context.rs
|
@ -1,12 +1,13 @@
|
||||||
#[cfg(feature = "backtrace")]
|
|
||||||
use std::backtrace::BacktraceStatus;
|
use std::backtrace::BacktraceStatus;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::panic::Location;
|
use std::panic::Location;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::report::report_write;
|
use crate::{
|
||||||
use crate::ReportOpts;
|
report::{fmt_report, DetailIndent},
|
||||||
|
How, ReportOpts,
|
||||||
|
};
|
||||||
|
|
||||||
/// Provides context furthering the explanation of *how* you got to an error.
|
/// Provides context furthering the explanation of *how* you got to an error.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -14,12 +15,12 @@ use crate::ReportOpts;
|
||||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
||||||
derive(Clone)
|
derive(Clone)
|
||||||
)]
|
)]
|
||||||
pub struct Context {
|
pub struct DetailTree {
|
||||||
pub(crate) detail: Detail,
|
pub(crate) detail: Detail,
|
||||||
pub(crate) extra: Vec<Detail>,
|
pub(crate) extra: Vec<DetailTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Context {
|
impl DetailTree {
|
||||||
pub(crate) fn new(detail: Detail) -> Self {
|
pub(crate) fn new(detail: Detail) -> Self {
|
||||||
Self {
|
Self {
|
||||||
detail,
|
detail,
|
||||||
|
@ -31,7 +32,7 @@ impl Context {
|
||||||
&self.detail
|
&self.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extra(&self) -> &[Detail] {
|
pub fn extra(&self) -> &[DetailTree] {
|
||||||
&self.extra
|
&self.extra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,32 +40,65 @@ impl Context {
|
||||||
&mut self.detail
|
&mut self.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extra_mut(&mut self) -> &mut [Detail] {
|
pub fn extra_mut(&mut self) -> &mut [DetailTree] {
|
||||||
&mut self.extra
|
&mut self.extra
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_extra(&mut self, detail: impl IntoContext) {
|
pub fn push_extra(&mut self, detail: impl IntoDetails) {
|
||||||
let detail = detail.into_context();
|
let detail = detail.into_details();
|
||||||
if detail.extra.is_empty() {
|
self.extra.push(detail);
|
||||||
self.extra.push(detail.detail);
|
|
||||||
} else {
|
|
||||||
self.extra.push(Detail::Context(detail.into()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn pop_extra(&mut self) -> Option<Detail> {
|
pub fn pop_extra(&mut self) -> Option<DetailTree> {
|
||||||
self.extra.pop()
|
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 Context {
|
impl fmt::Display for DetailTree {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
self.detail.fmt(f)?;
|
self.detail.fmt(f)?;
|
||||||
let opts = ReportOpts::default();
|
let opts = ReportOpts::default();
|
||||||
for detail in &self.extra {
|
let mut iter = self.extra.iter().peekable();
|
||||||
|
while let Some(detail) = iter.next() {
|
||||||
f.write_str("\n")?;
|
f.write_str("\n")?;
|
||||||
report_write!(f, &opts.indent().next(), "- {detail}")?;
|
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -80,14 +114,12 @@ pub enum Detail {
|
||||||
Str(&'static str),
|
Str(&'static str),
|
||||||
String(String),
|
String(String),
|
||||||
Location(Location<'static>),
|
Location(Location<'static>),
|
||||||
#[cfg(feature = "backtrace")]
|
|
||||||
Backtrace(PrivateBacktrace),
|
Backtrace(PrivateBacktrace),
|
||||||
Error(PrivateError),
|
Error(PrivateError),
|
||||||
Context(Box<Context>),
|
HowError(How),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Detail {
|
impl Detail {
|
||||||
#[cfg(feature = "backtrace")]
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn backtrace() -> Self {
|
pub fn backtrace() -> Self {
|
||||||
use std::backtrace::BacktraceStatus::*;
|
use std::backtrace::BacktraceStatus::*;
|
||||||
|
@ -104,7 +136,6 @@ impl Detail {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct PrivateError(pub(crate) Arc<dyn std::error::Error + Send + Sync>);
|
pub struct PrivateError(pub(crate) Arc<dyn std::error::Error + Send + Sync>);
|
||||||
|
|
||||||
#[cfg(feature = "backtrace")]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
||||||
|
@ -113,7 +144,6 @@ pub struct PrivateError(pub(crate) Arc<dyn std::error::Error + Send + Sync>);
|
||||||
pub struct PrivateBacktrace(pub(crate) Backtrace);
|
pub struct PrivateBacktrace(pub(crate) Backtrace);
|
||||||
|
|
||||||
// will be replaced with std::backtrace::Backtrace if and when it is Clone
|
// will be replaced with std::backtrace::Backtrace if and when it is Clone
|
||||||
#[cfg(feature = "backtrace")]
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(feature = "arc-backtrace", derive(Clone))]
|
#[cfg_attr(feature = "arc-backtrace", derive(Clone))]
|
||||||
pub(crate) enum Backtrace {
|
pub(crate) enum Backtrace {
|
||||||
|
@ -125,11 +155,7 @@ pub(crate) enum Backtrace {
|
||||||
Other(std::backtrace::Backtrace),
|
Other(std::backtrace::Backtrace),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(
|
#[cfg(all(feature = "clone-with-caveats", not(feature = "arc-backtrace")))]
|
||||||
feature = "backtrace",
|
|
||||||
feature = "clone-with-caveats",
|
|
||||||
not(feature = "arc-backtrace")
|
|
||||||
))]
|
|
||||||
impl Clone for Backtrace {
|
impl Clone for Backtrace {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
|
@ -145,15 +171,12 @@ impl fmt::Display for Detail {
|
||||||
Self::Str(s) => f.write_str(s),
|
Self::Str(s) => f.write_str(s),
|
||||||
Self::String(s) => f.write_str(s),
|
Self::String(s) => f.write_str(s),
|
||||||
Self::Location(l) => write!(f, "at {l}"),
|
Self::Location(l) => write!(f, "at {l}"),
|
||||||
#[cfg(feature = "backtrace")]
|
|
||||||
Self::Backtrace(PrivateBacktrace(Backtrace::Unsupported)) => f.write_str(
|
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",
|
"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)) => {
|
Self::Backtrace(PrivateBacktrace(Backtrace::Disabled)) => {
|
||||||
f.write_str("If you'd like a backtrace,\n try again with RUST_BACKTRACE=1")
|
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))) => {
|
Self::Backtrace(PrivateBacktrace(Backtrace::Other(bt))) => {
|
||||||
f.write_str(if bt.status() == BacktraceStatus::Captured {
|
f.write_str(if bt.status() == BacktraceStatus::Captured {
|
||||||
"Here is the backtrace:\n"
|
"Here is the backtrace:\n"
|
||||||
|
@ -163,119 +186,119 @@ impl fmt::Display for Detail {
|
||||||
write!(f, "{}", bt)
|
write!(f, "{}", bt)
|
||||||
}
|
}
|
||||||
Self::Error(PrivateError(e)) => e.fmt(f),
|
Self::Error(PrivateError(e)) => e.fmt(f),
|
||||||
Self::Context(c) => c.fmt(f),
|
Self::HowError(e) => e.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoContext: Sized {
|
pub trait IntoDetails: Sized {
|
||||||
fn into_context(self) -> Context;
|
fn into_details(self) -> DetailTree;
|
||||||
|
|
||||||
|
/// Annotates the context with the given detail.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn with(self, other: impl IntoContext) -> Context {
|
fn with(self, detail: impl IntoDetails) -> DetailTree {
|
||||||
self.into_context().with(other)
|
self.into_details().with(detail)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Annotates the context with the caller location.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn with_caller(self) -> Context {
|
fn with_caller(self) -> DetailTree {
|
||||||
self.with(Location::caller())
|
self.with(Location::caller())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Annotates the context with the current backtrace.
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn with_backtrace(self) -> Context {
|
fn with_backtrace(self) -> DetailTree {
|
||||||
#[cfg(feature = "backtrace")]
|
self.with(Detail::backtrace())
|
||||||
return self.with(Detail::backtrace());
|
|
||||||
#[cfg(not(feature = "backtrace"))]
|
|
||||||
self.into_context()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContext for Context {
|
impl IntoDetails for DetailTree {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Chains another piece of context that is a child from a hierarchical perspective.
|
/// Chains another piece of context that is a child from a hierarchical perspective.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with(mut self, other: impl IntoContext) -> Self {
|
fn with(mut self, other: impl IntoDetails) -> Self {
|
||||||
self.push_extra(other);
|
self.push_extra(other);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContext for String {
|
impl IntoDetails for String {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
Detail::String(self).into_context()
|
Detail::String(self).into_details()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContext for &'static str {
|
impl IntoDetails for &'static str {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
Detail::Str(self).into_context()
|
Detail::Str(self).into_details()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContext for Cow<'static, str> {
|
impl IntoDetails for Cow<'static, str> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
match self {
|
match self {
|
||||||
Cow::Borrowed(s) => s.into_context(),
|
Cow::Borrowed(s) => s.into_details(),
|
||||||
Cow::Owned(s) => s.into_context(),
|
Cow::Owned(s) => s.into_details(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoContext for &'a Location<'static> {
|
impl<'a> IntoDetails for &'a Location<'static> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
Location::into_context(*self)
|
Location::into_details(*self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContext for Location<'static> {
|
impl IntoDetails for Location<'static> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
Detail::Location(self).into_context()
|
Detail::Location(self).into_details()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> IntoContext for Arc<E>
|
impl<E> IntoDetails for Arc<E>
|
||||||
where
|
where
|
||||||
E: std::error::Error + Send + Sync + 'static,
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
Detail::Error(PrivateError(self)).into_context()
|
Detail::Error(PrivateError(self)).into_details()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContext for Arc<dyn std::error::Error + Send + Sync> {
|
impl IntoDetails for Arc<dyn std::error::Error + Send + Sync> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
Detail::Error(PrivateError(self)).into_context()
|
Detail::Error(PrivateError(self)).into_details()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoContext for Detail {
|
impl IntoDetails for Detail {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
Context::new(self)
|
DetailTree::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, F> IntoContext for F
|
impl<C, F> IntoDetails for F
|
||||||
where
|
where
|
||||||
C: IntoContext,
|
C: IntoDetails,
|
||||||
F: FnOnce() -> C,
|
F: FnOnce() -> C,
|
||||||
{
|
{
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_context(self) -> Context {
|
fn into_details(self) -> DetailTree {
|
||||||
self().into_context()
|
self().into_details()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,41 +1,31 @@
|
||||||
use std::panic::Location;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{Detail, How, IntoContext};
|
use crate::{How, IntoDetails};
|
||||||
|
|
||||||
pub trait Explain: Sized {
|
pub trait Explain: Sized {
|
||||||
type Output;
|
type Output: Explain;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn context(self, context: impl IntoContext) -> Self::Output;
|
fn without_explanation(self) -> Self::Output;
|
||||||
|
|
||||||
#[inline]
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn frame(self, context: impl IntoContext) -> Self::Output {
|
fn context(self, context: impl IntoDetails) -> Self::Output;
|
||||||
let mut context = context.into_context();
|
|
||||||
|
|
||||||
context.extra.reserve(if cfg!(feature = "extra-backtrace") {
|
|
||||||
2
|
|
||||||
} else {
|
|
||||||
1
|
|
||||||
});
|
|
||||||
context.extra.push(Detail::Location(*Location::caller()));
|
|
||||||
#[cfg(feature = "extra-backtrace")]
|
|
||||||
context.extra.push(Detail::backtrace());
|
|
||||||
|
|
||||||
self.context(context)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Explain for How {
|
impl Explain for How {
|
||||||
type Output = Self;
|
type Output = Self;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn without_explanation(self) -> Self::Output {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn context(mut self, context: impl IntoContext) -> Self {
|
fn context(mut self, context: impl IntoDetails) -> Self {
|
||||||
self.push_context(context.into_context());
|
self.push_context(context.into_details());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,20 +49,44 @@ where
|
||||||
{
|
{
|
||||||
type Output = Result<T, How>;
|
type Output = Result<T, How>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn without_explanation(self) -> Self::Output {
|
||||||
|
#[cold]
|
||||||
|
#[track_caller]
|
||||||
|
fn err_inner<E>(e: E) -> How
|
||||||
|
where
|
||||||
|
E: std::error::Error + 'static,
|
||||||
|
{
|
||||||
|
// TODO: when specialization is stable, or at least *safe*, specialize on `E = How` and
|
||||||
|
// `E: Send + Sync + 'static` and remove the `+ 'static` bound from E on this Explain
|
||||||
|
// impl.
|
||||||
|
match typeid_cast::cast(e) {
|
||||||
|
Ok(e) => e,
|
||||||
|
//Err(e) => How::new(Context(ContextInner::Elem(Detail::Error(Arc::new(e))))),
|
||||||
|
Err(e) => How::new(e.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Ok(t) => Ok(t),
|
||||||
|
Err(e) => Err(err_inner(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||||
#[cold]
|
#[cold]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn into_and_context<E, C>(e: E, c: C) -> How
|
fn into_and_context<E, C>(e: E, c: C) -> How
|
||||||
where
|
where
|
||||||
E: std::error::Error + 'static,
|
E: std::error::Error + 'static,
|
||||||
C: IntoContext,
|
C: IntoDetails,
|
||||||
{
|
{
|
||||||
|
// TODO: when specialization is stable, or at least *safe*, specialize on `E = How` and
|
||||||
|
// `E: Send + Sync + 'static` and remove the `+ 'static` bound from E on this Explain
|
||||||
|
// impl.
|
||||||
match typeid_cast::cast(e) {
|
match typeid_cast::cast(e) {
|
||||||
Ok(e) => e,
|
Ok(e) => e,
|
||||||
// TODO: specialize on Send + Sync at runtime or compile time (possibly via
|
|
||||||
// specialization)
|
|
||||||
//Err(e) => How::new(Context(ContextInner::Elem(Detail::Error(Arc::new(e))))),
|
//Err(e) => How::new(Context(ContextInner::Elem(Detail::Error(Arc::new(e))))),
|
||||||
Err(e) => How::new(e.to_string()),
|
Err(e) => How::new(e.to_string()),
|
||||||
}
|
}
|
||||||
|
@ -91,29 +105,46 @@ where
|
||||||
{
|
{
|
||||||
type Output = How;
|
type Output = How;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn without_explanation(self) -> Self::Output {
|
||||||
|
How::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||||
How::new(self).context(context)
|
self.without_explanation().context(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Explain for Arc<dyn std::error::Error + Send + Sync> {
|
impl Explain for Arc<dyn std::error::Error + Send + Sync> {
|
||||||
type Output = How;
|
type Output = How;
|
||||||
|
|
||||||
|
fn without_explanation(self) -> Self::Output {
|
||||||
|
How::new(self)
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||||
How::new(self).context(context)
|
self.without_explanation().context(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Explain for Option<T> {
|
impl<T> Explain for Option<T> {
|
||||||
type Output = Result<T, How>;
|
type Output = Result<T, How>;
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
fn without_explanation(self) -> Self::Output {
|
||||||
|
match self {
|
||||||
|
Some(t) => Ok(t),
|
||||||
|
None => Err(How::new("called `Option::unwrap()` on a `None` value")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
fn context(self, context: impl IntoDetails) -> Self::Output {
|
||||||
match self {
|
match self {
|
||||||
Some(t) => Ok(t),
|
Some(t) => Ok(t),
|
||||||
None => Err(How::new(context)),
|
None => Err(How::new(context)),
|
||||||
|
|
38
src/how.rs
38
src/how.rs
|
@ -2,7 +2,7 @@ use core::panic::Location;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use crate::report::report_write;
|
use self::report::{fmt_report, DetailIndent};
|
||||||
|
|
||||||
/// The error type.
|
/// The error type.
|
||||||
///
|
///
|
||||||
|
@ -13,29 +13,30 @@ use crate::report::report_write;
|
||||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
||||||
derive(Clone)
|
derive(Clone)
|
||||||
)]
|
)]
|
||||||
pub struct How(Box<HowInner>);
|
pub struct How(pub(crate) Box<HowInner>);
|
||||||
|
|
||||||
struct HowInner {
|
pub(crate) struct HowInner {
|
||||||
location: &'static Location<'static>,
|
location: &'static Location<'static>,
|
||||||
// TODO: consider storing this vec inline (sharing the allocation with rest of the struct.
|
// TODO: consider storing this vec inline (sharing the allocation with rest of the struct.
|
||||||
// Probably move after `backtrace`)
|
// Probably move after `backtrace`)
|
||||||
context: Vec<Context>,
|
pub(crate) context: Vec<DetailTree>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl How {
|
impl How {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(context: impl IntoContext) -> Self {
|
pub fn new(details: impl IntoDetails) -> Self {
|
||||||
let location = Location::caller();
|
let location = Location::caller();
|
||||||
#[allow(unused_mut)]
|
#[allow(unused_mut)]
|
||||||
let mut how = Self(Box::new(HowInner {
|
let mut how = Self(Box::new(HowInner {
|
||||||
location,
|
location,
|
||||||
context: Vec::with_capacity(4),
|
context: Vec::with_capacity(4),
|
||||||
}))
|
}))
|
||||||
.frame(context);
|
.context(details);
|
||||||
#[cfg(all(feature = "backtrace", not(feature = "extra-backtrace")))]
|
if cfg!(not(feature = "extra-backtrace")) {
|
||||||
how.top_mut().extra.push(Detail::backtrace());
|
how.top_mut().push_extra(Detail::backtrace());
|
||||||
|
}
|
||||||
how
|
how
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -45,7 +46,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn top(&self) -> &Context {
|
pub fn top(&self) -> &DetailTree {
|
||||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||||
// is at least 1 value in context.
|
// is at least 1 value in context.
|
||||||
let o = self.0.context.iter().next();
|
let o = self.0.context.iter().next();
|
||||||
|
@ -60,7 +61,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bottom(&self) -> &Context {
|
pub fn bottom(&self) -> &DetailTree {
|
||||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||||
// is at least 1 value in context.
|
// is at least 1 value in context.
|
||||||
let o = self.0.context.iter().next_back();
|
let o = self.0.context.iter().next_back();
|
||||||
|
@ -75,7 +76,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn top_mut(&mut self) -> &mut Context {
|
pub fn top_mut(&mut self) -> &mut DetailTree {
|
||||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||||
// is at least 1 value in context.
|
// is at least 1 value in context.
|
||||||
let o = self.0.context.iter_mut().next();
|
let o = self.0.context.iter_mut().next();
|
||||||
|
@ -90,7 +91,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bottom_mut(&mut self) -> &mut Context {
|
pub fn bottom_mut(&mut self) -> &mut DetailTree {
|
||||||
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
// SAFETY: we only ever push values into context, and the constructor ensures that there
|
||||||
// is at least 1 value in context.
|
// is at least 1 value in context.
|
||||||
let o = self.0.context.iter_mut().next_back();
|
let o = self.0.context.iter_mut().next_back();
|
||||||
|
@ -105,7 +106,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_context(self) -> impl Iterator<Item = Context> {
|
pub fn into_context_iter(self) -> impl Iterator<Item = DetailTree> {
|
||||||
self.0.context.into_iter()
|
self.0.context.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,7 +118,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn push_context(&mut self, context: Context) {
|
pub(crate) fn push_context(&mut self, context: DetailTree) {
|
||||||
self.0.context.push(context);
|
self.0.context.push(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,7 +150,7 @@ impl std::fmt::Display for How {
|
||||||
f.write_str("\n")?;
|
f.write_str("\n")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
report_write!(f, &opts.indent().next(), "{ctx}")?;
|
fmt_report(f, &opts.indent().next(), DetailIndent::<false>, ctx)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -165,3 +166,10 @@ impl std::fmt::Debug for How {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoDetails for How {
|
||||||
|
#[inline]
|
||||||
|
fn into_details(self) -> crate::DetailTree {
|
||||||
|
Detail::HowError(self).into_details()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
#![feature(doc_auto_cfg)]
|
#![feature(doc_auto_cfg)]
|
||||||
|
|
||||||
mod context;
|
mod context;
|
||||||
pub use context::{Context, Detail, IntoContext};
|
pub use context::{Detail, DetailTree, IntoDetails};
|
||||||
|
|
||||||
mod report;
|
mod report;
|
||||||
pub use report::{Report, ReportOpts};
|
pub use report::{Report, ReportOpts};
|
||||||
|
|
145
src/report.rs
145
src/report.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::fmt::Write;
|
use std::fmt::{self, Display, Write};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct ReportOpts {
|
pub struct ReportOpts {
|
||||||
|
@ -31,21 +31,28 @@ impl ReportOpts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! report_write {
|
pub trait Indent {
|
||||||
($f:expr, $opts:expr, $msg:literal$(, $($tt:tt)+)?) => {
|
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result;
|
||||||
<::std::fmt::Arguments<'_> as $crate::Report>::fmt(&::std::format_args!(
|
|
||||||
$msg$(, $($tt)+)?), $f, $opts
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
pub(crate) use report_write;
|
|
||||||
|
|
||||||
#[inline]
|
impl<'a, T: Indent> Indent for &'a T {
|
||||||
fn write_indent(n: usize, f: &mut impl Write) -> std::fmt::Result {
|
#[inline]
|
||||||
for _ in 0..n {
|
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result {
|
||||||
f.write_str(" ")?;
|
T::write(*self, n, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct DetailIndent<const LINED: bool>;
|
||||||
|
|
||||||
|
impl<const LINED: bool> Indent for DetailIndent<LINED> {
|
||||||
|
#[inline]
|
||||||
|
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result {
|
||||||
|
for _ in 0..n {
|
||||||
|
f.write_str(if LINED { "┆ " } else { " " })?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `Report` should format the output in a manner suitable for debugging, similar to
|
/// `Report` should format the output in a manner suitable for debugging, similar to
|
||||||
|
@ -56,62 +63,64 @@ pub trait Report {
|
||||||
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result;
|
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Report for T
|
pub fn fmt_report(
|
||||||
where
|
f: &mut impl Write,
|
||||||
T: std::fmt::Display,
|
opts: &ReportOpts,
|
||||||
{
|
indent: impl Indent,
|
||||||
#[inline(never)]
|
t: &impl Display,
|
||||||
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
use std::fmt::Error;
|
if opts.indentation > 0 {
|
||||||
struct IndentedWrite<'a, W: Write> {
|
if opts.should_indent() {
|
||||||
f: &'a mut W,
|
indent.write(opts.indentation, f)?;
|
||||||
n: usize,
|
|
||||||
}
|
}
|
||||||
impl<'a, W> Write for IndentedWrite<'a, W>
|
IndentedWrite {
|
||||||
where
|
f,
|
||||||
W: Write,
|
n: opts.indentation,
|
||||||
{
|
indent,
|
||||||
#[inline]
|
|
||||||
fn write_str(&mut self, s: &str) -> Result<(), Error> {
|
|
||||||
// TODO: any room for optimization?
|
|
||||||
// iterates over the lines where each str ends with the line terminator.
|
|
||||||
// after giving this a bit of thought I think it is best to indent after any
|
|
||||||
// trailing newline.
|
|
||||||
let mut ss = s.split_inclusive('\n');
|
|
||||||
if let Some(mut s) = ss.next() {
|
|
||||||
self.f.write_str(s)?;
|
|
||||||
for s_next in ss {
|
|
||||||
write_indent(self.n, &mut self.f)?;
|
|
||||||
self.f.write_str(s_next)?;
|
|
||||||
s = s_next;
|
|
||||||
}
|
|
||||||
if matches!(s.chars().rev().next(), Some('\n')) {
|
|
||||||
write_indent(self.n, &mut self.f)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn write_char(&mut self, c: char) -> Result<(), Error> {
|
|
||||||
self.f.write_char(c)?;
|
|
||||||
if c == '\n' {
|
|
||||||
write_indent(self.n, &mut self.f)?;
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opts.indentation > 0 {
|
|
||||||
if opts.should_indent() {
|
|
||||||
write_indent(opts.indentation, f)?;
|
|
||||||
}
|
|
||||||
IndentedWrite {
|
|
||||||
f,
|
|
||||||
n: opts.indentation,
|
|
||||||
}
|
|
||||||
.write_fmt(format_args!("{self}"))
|
|
||||||
} else {
|
|
||||||
f.write_fmt(format_args!("{self}"))
|
|
||||||
}
|
}
|
||||||
|
.write_fmt(format_args!("{t}"))
|
||||||
|
} else {
|
||||||
|
f.write_fmt(format_args!("{t}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct IndentedWrite<'a, I: Indent, W: Write> {
|
||||||
|
f: &'a mut W,
|
||||||
|
indent: I,
|
||||||
|
n: usize,
|
||||||
|
}
|
||||||
|
impl<'a, I, W> Write for IndentedWrite<'a, I, W>
|
||||||
|
where
|
||||||
|
I: Indent,
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
||||||
|
// TODO: any room for optimization?
|
||||||
|
// iterates over the lines where each str ends with the line terminator.
|
||||||
|
// after giving this a bit of thought I think it is best to indent after any
|
||||||
|
// trailing newline.
|
||||||
|
let mut ss = s.split_inclusive('\n');
|
||||||
|
if let Some(mut s) = ss.next() {
|
||||||
|
self.f.write_str(s)?;
|
||||||
|
for s_next in ss {
|
||||||
|
self.indent.write(self.n, &mut self.f)?;
|
||||||
|
self.f.write_str(s_next)?;
|
||||||
|
s = s_next;
|
||||||
|
}
|
||||||
|
if matches!(s.chars().rev().next(), Some('\n')) {
|
||||||
|
self.indent.write(self.n, &mut self.f)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn write_char(&mut self, c: char) -> Result<(), fmt::Error> {
|
||||||
|
self.f.write_char(c)?;
|
||||||
|
if c == '\n' {
|
||||||
|
self.indent.write(self.n, &mut self.f)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue