use smallvec::{SmallVec, smallvec};
use std::error::Error as StdError;
use std::fmt;
use std::panic::Location;
pub use traced_macro::traced;
pub struct ContextItem {
pub loc: &'static Location<'static>,
pub payload: Box<dyn fmt::Display + Send + Sync>,
}
pub struct Traced<E> {
pub error: E,
pub trace: SmallVec<[&'static Location<'static>; 4]>,
pub context: SmallVec<[ContextItem; 2]>,
}
impl<E> Traced<E> {
#[inline]
#[track_caller]
pub fn new(error: E) -> Self {
Traced {
error,
trace: smallvec![Location::caller()],
context: SmallVec::new(),
}
}
#[inline]
pub fn bare(error: E) -> Self {
Traced { error, trace: SmallVec::new(), context: SmallVec::new() }
}
#[inline]
pub fn append(mut self, loc: &'static Location<'static>) -> Self {
self.trace.push(loc);
self
}
#[inline]
pub fn map_err<F>(self, f: impl FnOnce(E) -> F) -> Traced<F> {
Traced {
error: f(self.error),
trace: self.trace,
context: self.context,
}
}
#[inline]
#[track_caller]
pub fn ctx<C: fmt::Display + Send + Sync + 'static>(mut self, c: C) -> Self {
self.context.push(ContextItem {
loc: Location::caller(),
payload: Box::new(c),
});
self
}
}
fn fmt_traced<E>(
e: &E,
trace: &[&'static Location<'static>],
context: &[ContextItem],
f: &mut fmt::Formatter<'_>,
write_err: impl FnOnce(&E, &mut fmt::Formatter<'_>) -> fmt::Result,
) -> fmt::Result {
write_err(e, f)?;
writeln!(f)?;
for (i, loc) in trace.iter().enumerate() {
writeln!(f, " {:>2}: at {}:{}:{}", i, loc.file(), loc.line(), loc.column())?;
}
if !context.is_empty() {
writeln!(f, "context:")?;
for item in context {
writeln!(
f,
" - {} (at {}:{}:{})",
item.payload,
item.loc.file(),
item.loc.line(),
item.loc.column()
)?;
}
}
Ok(())
}
impl<E: fmt::Debug> fmt::Debug for Traced<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_traced(&self.error, &self.trace, &self.context, f, |e, f| {
write!(f, "{:?}", e)
})
}
}
impl<E: fmt::Display> fmt::Display for Traced<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt_traced(&self.error, &self.trace, &self.context, f, |e, f| {
write!(f, "{}", e)
})
}
}
impl<E> StdError for Traced<E>
where
E: StdError + 'static,
{
fn source(&self) -> Option<&(dyn StdError + 'static)> {
Some(&self.error)
}
}
pub trait Context<T, E>: Sized {
fn ctx<C: fmt::Display + Send + Sync + 'static>(self, c: C) -> Result<T, Traced<E>>;
fn with_ctx<C, G>(self, g: G) -> Result<T, Traced<E>>
where
C: fmt::Display + Send + Sync + 'static,
G: FnOnce() -> C;
}
impl<T, E> Context<T, E> for Result<T, Traced<E>> {
#[inline]
#[track_caller]
fn ctx<C: fmt::Display + Send + Sync + 'static>(self, c: C) -> Result<T, Traced<E>> {
match self {
Ok(v) => Ok(v),
Err(t) => Err(t.ctx(c)),
}
}
#[inline]
#[track_caller]
fn with_ctx<C, G>(self, g: G) -> Result<T, Traced<E>>
where
C: fmt::Display + Send + Sync + 'static,
G: FnOnce() -> C,
{
match self {
Ok(v) => Ok(v),
Err(t) => Err(t.ctx(g())),
}
}
}
pub trait ContextRaw<T, F>: Sized {
fn ctx_lift<C: fmt::Display + Send + Sync + 'static>(self, c: C) -> Result<T, Traced<F>>;
fn with_ctx_lift<C, G>(self, g: G) -> Result<T, Traced<F>>
where
C: fmt::Display + Send + Sync + 'static,
G: FnOnce() -> C;
}
impl<T, F> ContextRaw<T, F> for Result<T, F> {
#[inline]
#[track_caller]
fn ctx_lift<C: fmt::Display + Send + Sync + 'static>(self, c: C) -> Result<T, Traced<F>> {
match self {
Ok(v) => Ok(v),
Err(e) => Err(Traced::bare(e).ctx(c)),
}
}
#[inline]
#[track_caller]
fn with_ctx_lift<C, G>(self, g: G) -> Result<T, Traced<F>>
where
C: fmt::Display + Send + Sync + 'static,
G: FnOnce() -> C,
{
match self {
Ok(v) => Ok(v),
Err(e) => Err(Traced::bare(e).ctx(g())),
}
}
}
pub mod prelude {
pub use super::{Context, ContextRaw, Traced, traced};
}
pub mod __macro {
use super::Traced;
use smallvec::{SmallVec, smallvec};
use std::panic::Location;
pub struct WrapErr<F>(pub F);
impl<F> WrapErr<Traced<F>> {
#[inline]
pub fn __resolve<E: From<F>>(self, loc: &'static Location<'static>) -> Traced<E> {
self.0.map_err(E::from).append(loc)
}
}
pub trait ResolveFallback {
type Inner;
fn __resolve<E: From<Self::Inner>>(
self,
loc: &'static Location<'static>,
) -> Traced<E>;
}
impl<F> ResolveFallback for WrapErr<F> {
type Inner = F;
#[inline]
fn __resolve<E: From<F>>(self, loc: &'static Location<'static>) -> Traced<E> {
Traced {
error: E::from(self.0),
trace: smallvec![loc],
context: SmallVec::new(),
}
}
}
}