Compare commits
No commits in common. "db01cce71cdb1e29b8c9a7f5af177d669af8ed6c" and "8517408765fd7a60e01b51c6f996868bc365205b" have entirely different histories.
db01cce71c
...
8517408765
|
@ -5,6 +5,8 @@ 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"]
|
||||||
|
@ -13,9 +15,6 @@ termination = ["dep:ansee"]
|
||||||
ansee = { git = "https://git.pfaff.dev/michael/ansee", optional = true }
|
ansee = { git = "https://git.pfaff.dev/michael/ansee", optional = true }
|
||||||
typeid-cast = "0.1"
|
typeid-cast = "0.1"
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
how = { path = ".", features = ["termination"] }
|
|
||||||
|
|
||||||
[[example]]
|
[[example]]
|
||||||
name = "output"
|
name = "output"
|
||||||
required-features = ["termination"]
|
required-features = [ "termination" ]
|
||||||
|
|
205
src/context.rs
205
src/context.rs
|
@ -1,38 +1,31 @@
|
||||||
use std::backtrace::BacktraceStatus;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
|
#[cfg(feature = "backtrace")]
|
||||||
|
use std::backtrace::BacktraceStatus;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::panic::Location;
|
use std::panic::Location;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::ReportOpts;
|
||||||
report::{fmt_report, DetailIndent},
|
use crate::report::report_write;
|
||||||
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)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(any(feature = "arc-backtrace", feature = "clone-with-caveats"), derive(Clone))]
|
||||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
pub struct Context {
|
||||||
derive(Clone)
|
|
||||||
)]
|
|
||||||
pub struct DetailTree {
|
|
||||||
pub(crate) detail: Detail,
|
pub(crate) detail: Detail,
|
||||||
pub(crate) extra: Vec<DetailTree>,
|
pub(crate) extra: Vec<Detail>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DetailTree {
|
impl Context {
|
||||||
pub(crate) fn new(detail: Detail) -> Self {
|
pub(crate) fn new(detail: Detail) -> Self {
|
||||||
Self {
|
Self { detail, extra: Vec::new() }
|
||||||
detail,
|
|
||||||
extra: Vec::new(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn detail(&self) -> &Detail {
|
pub fn detail(&self) -> &Detail {
|
||||||
&self.detail
|
&self.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extra(&self) -> &[DetailTree] {
|
pub fn extra(&self) -> &[Detail] {
|
||||||
&self.extra
|
&self.extra
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,86 +33,52 @@ impl DetailTree {
|
||||||
&mut self.detail
|
&mut self.detail
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn extra_mut(&mut self) -> &mut [DetailTree] {
|
pub fn extra_mut(&mut self) -> &mut [Detail] {
|
||||||
&mut self.extra
|
&mut self.extra
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn push_extra(&mut self, detail: impl IntoDetails) {
|
pub fn push_extra(&mut self, detail: impl IntoContext) {
|
||||||
let detail = detail.into_details();
|
let detail = detail.into_context();
|
||||||
self.extra.push(detail);
|
if detail.extra.is_empty() {
|
||||||
}
|
self.extra.push(detail.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 {
|
} else {
|
||||||
true
|
self.extra.push(Detail::Context(detail.into()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn pop_extra(&mut self) -> Option<Detail> {
|
||||||
|
self.extra.pop()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for DetailTree {
|
impl fmt::Display for Context {
|
||||||
#[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();
|
||||||
let mut iter = self.extra.iter().peekable();
|
for detail in &self.extra {
|
||||||
while let Some(detail) = iter.next() {
|
|
||||||
f.write_str("\n")?;
|
f.write_str("\n")?;
|
||||||
if iter.peek().is_none() {
|
report_write!(f, &opts.indent().next(), "- {detail}")?;
|
||||||
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(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[cfg_attr(
|
#[cfg_attr(any(feature = "arc-backtrace", feature = "clone-with-caveats"), derive(Clone))]
|
||||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
|
||||||
derive(Clone)
|
|
||||||
)]
|
|
||||||
#[non_exhaustive]
|
#[non_exhaustive]
|
||||||
pub enum Detail {
|
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),
|
||||||
HowError(How),
|
Context(Box<Context>),
|
||||||
}
|
}
|
||||||
|
|
||||||
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::*;
|
||||||
|
@ -136,14 +95,13 @@ 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"), derive(Clone))]
|
||||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
|
||||||
derive(Clone)
|
|
||||||
)]
|
|
||||||
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 {
|
||||||
|
@ -155,12 +113,12 @@ pub(crate) enum Backtrace {
|
||||||
Other(std::backtrace::Backtrace),
|
Other(std::backtrace::Backtrace),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(feature = "clone-with-caveats", not(feature = "arc-backtrace")))]
|
#[cfg(all(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 {
|
||||||
Self::Unsupported => Self::Unsupported,
|
Self::Unsupported => Self::Unsupported,
|
||||||
_ => Self::Disabled,
|
_ => Self::Disabled
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -171,12 +129,11 @@ 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}"),
|
||||||
Self::Backtrace(PrivateBacktrace(Backtrace::Unsupported)) => f.write_str(
|
#[cfg(feature = "backtrace")]
|
||||||
"I'd like to show you a backtrace,\n but it's not supported on your platform",
|
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)) => {
|
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"
|
||||||
|
@ -184,121 +141,121 @@ impl fmt::Display for Detail {
|
||||||
"I can't tell if backtraces are working,\n but I'll give it a go:\n"
|
"I can't tell if backtraces are working,\n but I'll give it a go:\n"
|
||||||
})?;
|
})?;
|
||||||
write!(f, "{}", bt)
|
write!(f, "{}", bt)
|
||||||
}
|
},
|
||||||
Self::Error(PrivateError(e)) => e.fmt(f),
|
Self::Error(PrivateError(e)) => e.fmt(f),
|
||||||
Self::HowError(e) => e.fmt(f),
|
Self::Context(c) => c.fmt(f),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait IntoDetails: Sized {
|
pub trait IntoContext: Sized {
|
||||||
fn into_details(self) -> DetailTree;
|
fn into_context(self) -> Context;
|
||||||
|
|
||||||
/// Annotates the context with the given detail.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn with(self, detail: impl IntoDetails) -> DetailTree {
|
fn with(self, other: impl IntoContext) -> Context {
|
||||||
self.into_details().with(detail)
|
self.into_context().with(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Annotates the context with the caller location.
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn with_caller(self) -> DetailTree {
|
fn with_caller(self) -> Context {
|
||||||
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) -> DetailTree {
|
fn with_backtrace(self) -> Context {
|
||||||
self.with(Detail::backtrace())
|
#[cfg(feature = "backtrace")]
|
||||||
|
return self.with(Detail::backtrace());
|
||||||
|
#[cfg(not(feature = "backtrace"))]
|
||||||
|
self.into_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDetails for DetailTree {
|
impl IntoContext for Context {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
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 IntoDetails) -> Self {
|
fn with(mut self, other: impl IntoContext) -> Self {
|
||||||
self.push_extra(other);
|
self.push_extra(other);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDetails for String {
|
impl IntoContext for String {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
Detail::String(self).into_details()
|
Detail::String(self).into_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDetails for &'static str {
|
impl IntoContext for &'static str {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
Detail::Str(self).into_details()
|
Detail::Str(self).into_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDetails for Cow<'static, str> {
|
impl IntoContext for Cow<'static, str> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
match self {
|
match self {
|
||||||
Cow::Borrowed(s) => s.into_details(),
|
Cow::Borrowed(s) => s.into_context(),
|
||||||
Cow::Owned(s) => s.into_details(),
|
Cow::Owned(s) => s.into_context(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> IntoDetails for &'a Location<'static> {
|
impl<'a> IntoContext for &'a Location<'static> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
Location::into_details(*self)
|
Location::into_context(*self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDetails for Location<'static> {
|
impl IntoContext for Location<'static> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
Detail::Location(self).into_details()
|
Detail::Location(self).into_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<E> IntoDetails for Arc<E>
|
impl<E> IntoContext for Arc<E>
|
||||||
where
|
where
|
||||||
E: std::error::Error + Send + Sync + 'static,
|
E: std::error::Error + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
Detail::Error(PrivateError(self)).into_details()
|
Detail::Error(PrivateError(self)).into_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDetails for Arc<dyn std::error::Error + Send + Sync> {
|
impl IntoContext for Arc<dyn std::error::Error + Send + Sync> {
|
||||||
#[inline]
|
#[inline]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
Detail::Error(PrivateError(self)).into_details()
|
Detail::Error(PrivateError(self)).into_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IntoDetails for Detail {
|
impl IntoContext for Detail {
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
DetailTree::new(self)
|
Context::new(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C, F> IntoDetails for F
|
impl<C, F> IntoContext for F
|
||||||
where
|
where
|
||||||
C: IntoDetails,
|
C: IntoContext,
|
||||||
F: FnOnce() -> C,
|
F: FnOnce() -> C,
|
||||||
{
|
{
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn into_details(self) -> DetailTree {
|
fn into_context(self) -> Context {
|
||||||
self().into_details()
|
self().into_context()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
104
src/explain.rs
104
src/explain.rs
|
@ -1,92 +1,65 @@
|
||||||
|
use std::panic::Location;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{How, IntoDetails};
|
use crate::{How, IntoContext, Detail};
|
||||||
|
|
||||||
pub trait Explain: Sized {
|
pub trait Explain: Sized {
|
||||||
type Output: Explain;
|
type Output;
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn without_explanation(self) -> Self::Output;
|
fn context(self, context: impl IntoContext) -> Self::Output;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
#[must_use]
|
#[must_use]
|
||||||
fn context(self, context: impl IntoDetails) -> Self::Output;
|
fn frame(self, context: impl IntoContext) -> 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 IntoDetails) -> Self {
|
fn context(mut self, context: impl IntoContext) -> Self {
|
||||||
self.push_context(context.into_details());
|
self.push_context(context.into_context());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*impl<E> Explain for E
|
|
||||||
where
|
|
||||||
E: std::error::Error,
|
|
||||||
{
|
|
||||||
type Output = How;
|
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
#[track_caller]
|
|
||||||
fn context(self, context: impl IntoContext) -> Self::Output {
|
|
||||||
How::new(self.to_string()).context(context)
|
|
||||||
}
|
|
||||||
}*/
|
|
||||||
|
|
||||||
impl<T, E> Explain for Result<T, E>
|
impl<T, E> Explain for Result<T, E>
|
||||||
where
|
where
|
||||||
E: std::error::Error + 'static,
|
E: std::error::Error + 'static,
|
||||||
{
|
{
|
||||||
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 IntoDetails) -> Self::Output {
|
fn context(self, context: impl IntoContext) -> 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: IntoDetails,
|
C: IntoContext,
|
||||||
{
|
{
|
||||||
// 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()),
|
||||||
}
|
}
|
||||||
|
@ -105,46 +78,29 @@ 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 IntoDetails) -> Self::Output {
|
fn context(self, context: impl IntoContext) -> Self::Output {
|
||||||
self.without_explanation().context(context)
|
How::new(self).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 IntoDetails) -> Self::Output {
|
fn context(self, context: impl IntoContext) -> Self::Output {
|
||||||
self.without_explanation().context(context)
|
How::new(self).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 IntoDetails) -> Self::Output {
|
fn context(self, context: impl IntoContext) -> Self::Output {
|
||||||
match self {
|
match self {
|
||||||
Some(t) => Ok(t),
|
Some(t) => Ok(t),
|
||||||
None => Err(How::new(context)),
|
None => Err(How::new(context)),
|
||||||
|
|
44
src/how.rs
44
src/how.rs
|
@ -2,41 +2,37 @@ use core::panic::Location;
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
|
|
||||||
use self::report::{fmt_report, DetailIndent};
|
use crate::report::report_write;
|
||||||
|
|
||||||
/// The error type.
|
/// The error type.
|
||||||
///
|
///
|
||||||
/// By default, does not implement [`Clone`] because [`std::backtrace::Backtrace`] does not
|
/// By default, does not implement [`Clone`] because [`std::backtrace::Backtrace`] does not
|
||||||
/// implement [`Clone`]. However, the `clone-with-caveats` feature may be used to enable a
|
/// implement [`Clone`]. However, the `clone-with-caveats` feature may be used to enable a
|
||||||
/// [`Clone`] impl that sets the cloned `backtrace` to [`std::backtrace::Backtrace::disabled`].
|
/// [`Clone`] impl that sets the cloned `backtrace` to [`std::backtrace::Backtrace::disabled`].
|
||||||
#[cfg_attr(
|
#[cfg_attr(any(feature = "arc-backtrace", feature = "clone-with-caveats"), derive(Clone))]
|
||||||
any(feature = "arc-backtrace", feature = "clone-with-caveats"),
|
pub struct How(Box<HowInner>);
|
||||||
derive(Clone)
|
|
||||||
)]
|
|
||||||
pub struct How(pub(crate) Box<HowInner>);
|
|
||||||
|
|
||||||
pub(crate) struct HowInner {
|
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`)
|
||||||
pub(crate) context: Vec<DetailTree>,
|
context: Vec<Context>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl How {
|
impl How {
|
||||||
#[must_use]
|
#[must_use]
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(details: impl IntoDetails) -> Self {
|
pub fn new(context: impl IntoContext) -> 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),
|
||||||
}))
|
}))
|
||||||
.context(details);
|
.frame(context);
|
||||||
if cfg!(not(feature = "extra-backtrace")) {
|
#[cfg(all(feature = "backtrace", not(feature = "extra-backtrace")))]
|
||||||
how.top_mut().push_extra(Detail::backtrace());
|
how.top_mut().extra.push(Detail::backtrace());
|
||||||
}
|
|
||||||
how
|
how
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +42,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn top(&self) -> &DetailTree {
|
pub fn top(&self) -> &Context {
|
||||||
// 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();
|
||||||
|
@ -61,7 +57,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bottom(&self) -> &DetailTree {
|
pub fn bottom(&self) -> &Context {
|
||||||
// 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();
|
||||||
|
@ -76,7 +72,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn top_mut(&mut self) -> &mut DetailTree {
|
pub fn top_mut(&mut self) -> &mut Context {
|
||||||
// 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();
|
||||||
|
@ -91,7 +87,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn bottom_mut(&mut self) -> &mut DetailTree {
|
pub fn bottom_mut(&mut self) -> &mut Context {
|
||||||
// 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();
|
||||||
|
@ -106,7 +102,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn into_context_iter(self) -> impl Iterator<Item = DetailTree> {
|
pub fn into_context(self) -> impl Iterator<Item = Context> {
|
||||||
self.0.context.into_iter()
|
self.0.context.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +114,7 @@ impl How {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub(crate) fn push_context(&mut self, context: DetailTree) {
|
pub(crate) fn push_context(&mut self, context: Context) {
|
||||||
self.0.context.push(context);
|
self.0.context.push(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -149,8 +145,7 @@ impl std::fmt::Display for How {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
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(())
|
||||||
}
|
}
|
||||||
|
@ -166,10 +161,3 @@ 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::{Detail, DetailTree, IntoDetails};
|
pub use context::{Context, Detail, IntoContext};
|
||||||
|
|
||||||
mod report;
|
mod report;
|
||||||
pub use report::{Report, ReportOpts};
|
pub use report::{Report, ReportOpts};
|
||||||
|
|
141
src/report.rs
141
src/report.rs
|
@ -1,4 +1,4 @@
|
||||||
use std::fmt::{self, Display, Write};
|
use std::fmt::Write;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct ReportOpts {
|
pub struct ReportOpts {
|
||||||
|
@ -31,96 +31,83 @@ impl ReportOpts {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait Indent {
|
macro_rules! report_write {
|
||||||
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result;
|
($f:expr, $opts:expr, $msg:literal$(, $($tt:tt)+)?) => {
|
||||||
|
<::std::fmt::Arguments<'_> as $crate::Report>::fmt(&::std::format_args!($msg$(, $($tt)+)?), $f, $opts)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
pub(crate) use report_write;
|
||||||
|
|
||||||
impl<'a, T: Indent> Indent for &'a T {
|
#[inline]
|
||||||
#[inline]
|
fn write_indent(n: usize, f: &mut impl Write) -> std::fmt::Result {
|
||||||
fn write(&self, n: usize, f: &mut impl Write) -> fmt::Result {
|
for _ in 0..n {
|
||||||
T::write(*self, n, f)
|
f.write_str(" ")?;
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
/// `Report` should format the output in a manner suitable for debugging, similar to [`std::fmt::Debug`] in terms of detail, but similar to [`std::fmt::Display`] in terms of readability. The options passed to [`Self::fmt`] must be respected by all implementations.
|
||||||
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(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Report` should format the output in a manner suitable for debugging, similar to
|
|
||||||
/// [`std::fmt::Debug`] in terms of detail, but similar to [`std::fmt::Display`] in terms of
|
|
||||||
/// readability. The options passed to [`Self::fmt`] must be respected by all implementations.
|
|
||||||
pub trait Report {
|
pub trait Report {
|
||||||
/// Formats the value using the given formatter and options.
|
/// Formats the value using the given formatter and options.
|
||||||
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result;
|
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fmt_report(
|
impl<T> Report for T
|
||||||
f: &mut impl Write,
|
|
||||||
opts: &ReportOpts,
|
|
||||||
indent: impl Indent,
|
|
||||||
t: &impl Display,
|
|
||||||
) -> std::fmt::Result {
|
|
||||||
if opts.indentation > 0 {
|
|
||||||
if opts.should_indent() {
|
|
||||||
indent.write(opts.indentation, f)?;
|
|
||||||
}
|
|
||||||
IndentedWrite {
|
|
||||||
f,
|
|
||||||
n: opts.indentation,
|
|
||||||
indent,
|
|
||||||
}
|
|
||||||
.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
|
where
|
||||||
I: Indent,
|
T: std::fmt::Display,
|
||||||
W: Write,
|
|
||||||
{
|
{
|
||||||
#[inline]
|
#[inline(never)]
|
||||||
fn write_str(&mut self, s: &str) -> Result<(), fmt::Error> {
|
fn fmt(&self, f: &mut impl Write, opts: &ReportOpts) -> std::fmt::Result {
|
||||||
// TODO: any room for optimization?
|
use std::fmt::Error;
|
||||||
// iterates over the lines where each str ends with the line terminator.
|
struct IndentedWrite<'a, W: Write> {
|
||||||
// after giving this a bit of thought I think it is best to indent after any
|
f: &'a mut W,
|
||||||
// trailing newline.
|
n: usize,
|
||||||
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(())
|
impl<'a, W> Write for IndentedWrite<'a, W>
|
||||||
}
|
where
|
||||||
|
W: Write,
|
||||||
|
{
|
||||||
|
#[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]
|
#[inline]
|
||||||
fn write_char(&mut self, c: char) -> Result<(), fmt::Error> {
|
fn write_char(&mut self, c: char) -> Result<(), Error> {
|
||||||
self.f.write_char(c)?;
|
self.f.write_char(c)?;
|
||||||
if c == '\n' {
|
if c == '\n' {
|
||||||
self.indent.write(self.n, &mut self.f)?;
|
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}"))
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue