#![doc = r#"
<pre>
<span style="color:#4E9A06;"><b>❯</b></span> RUST_BACKTRACE=1 cargo run --example simple-report
<span style="color:#4E9A06;"><b>Finished</b></span> `dev` profile [unoptimized + debuginfo] target(s) in 0.48s
<span style="color:#4E9A06;"><b>Running</b></span> `target/debug/examples/simple-report`
<span style="color:#DD3311;"><b>unable to load configuration</b></span>
├╴<span style="color:#888888;">at crates/reportify/examples/simple-report.rs:25:10</span>
├╴path: "path/does/not/exist.toml"
│
╰─▶ <span style="color:#DD3311;"><b>configuration file not found</b></span>
├╴<span style="color:#888888;">at crates/reportify/examples/simple-report.rs:14:48</span>
├╴BACKTRACE (1)
│
╰─▶ <span style="color:#DD3311;"><b>No such file or directory (os error 2)</b></span>
━━━━ BACKTRACE (1)
⋮ skipped 10 frames
11: <span style="color: #0099DD;">simple_report::read_config</span> <span style="color:#888888;">(0x100004467)</span>
at reportify/examples/simple-report.rs:14:18
12: <span style="color: #0099DD;">simple_report::run</span> <span style="color:#888888;">(0x1000045ef)</span>
at reportify/examples/simple-report.rs:27:5
13: <span style="color: #0099DD;">simple_report::main</span> <span style="color:#888888;">(0x10000467b)</span>
at reportify/examples/simple-report.rs:34:26
⋮ skipped 6 frames
</pre>
"#]
use std::{
any::Any,
error::Error as StdError,
fmt::{Debug, Display},
};
use backtrace::BacktraceImpl;
mod backtrace;
mod renderer;
pub trait Error: 'static + Send + Any + Debug {
fn description(&self) -> Option<&dyn Display>;
fn as_std_error(&self) -> Option<&(dyn 'static + StdError + Send)> {
None
}
fn name(&self) -> &'static str {
std::any::type_name::<Self>()
}
fn boxed_dyn(self) -> Box<dyn Error>
where
Self: Sized,
{
Box::new(self)
}
}
impl<E: 'static + StdError + Send> Error for E {
fn description(&self) -> Option<&dyn Display> {
Some(self)
}
fn as_std_error(&self) -> Option<&(dyn 'static + StdError + Send)> {
Some(self)
}
}
trait AnyReport {
fn error(&self) -> &dyn Error;
fn meta(&self) -> &ReportMeta;
}
impl<E: Error> AnyReport for ReportInner<E> {
fn error(&self) -> &dyn Error {
&self.error
}
fn meta(&self) -> &ReportMeta {
&self.meta
}
}
struct ErasedReport {
inner: Box<dyn AnyReport>,
}
impl AnyReport for ErasedReport {
fn error(&self) -> &dyn Error {
self.inner.error()
}
fn meta(&self) -> &ReportMeta {
self.inner.meta()
}
}
pub struct Report<E> {
inner: Box<ReportInner<E>>,
}
impl<E: Error> AnyReport for Report<E> {
fn error(&self) -> &dyn Error {
&self.inner.error
}
fn meta(&self) -> &ReportMeta {
&self.inner.meta
}
}
struct ReportInner<E> {
error: E,
meta: ReportMeta,
}
#[derive(Default)]
struct ReportMeta {
description: Option<String>,
location: Option<&'static std::panic::Location<'static>>,
backtrace: Option<backtrace::Backtrace>,
#[cfg(feature = "spantrace")]
spantrace: Option<tracing_error::SpanTrace>,
causes: Vec<ErasedReport>,
info: Vec<Box<dyn Printable>>,
}
#[must_use]
pub struct ReportBuilder<E = ()> {
inner: ReportInner<E>,
}
impl ReportMeta {
fn capture_backtrace(&mut self) {
self.backtrace = Some(backtrace::Backtrace::capture());
#[cfg(feature = "spantrace")]
{
self.spantrace = Some(tracing_error::SpanTrace::capture());
}
}
#[track_caller]
fn capture_location(&mut self) {
self.location = Some(std::panic::Location::caller());
}
fn add_info<I: Printable>(&mut self, info: I) {
self.info.push(Box::new(info))
}
}
impl ReportBuilder {
pub fn new() -> Self {
Self {
inner: ReportInner {
error: (),
meta: ReportMeta::default(),
},
}
}
}
impl<E> ReportBuilder<E> {
pub fn with_backtrace(mut self) -> Self {
self.inner.meta.capture_backtrace();
self
}
#[track_caller]
pub fn with_location(mut self) -> Self {
self.inner.meta.capture_location();
self
}
pub fn with_error<F: Error>(self, error: F) -> ReportBuilder<F> {
ReportBuilder {
inner: ReportInner {
error,
meta: self.inner.meta,
},
}
}
pub fn with_info<I: Printable>(mut self, info: I) -> Self {
self.inner.meta.add_info(info);
self
}
pub fn with_cause<C: private::Report>(mut self, cause: C) -> Self {
self.inner.meta.causes.push(cause.into_report().erased());
self
}
pub fn with_description<D: Printable>(mut self, description: D) -> Self {
self.inner.meta.description = Some(description.to_string());
self
}
}
impl<E: Error> ReportBuilder<E> {
pub fn build(self) -> Report<E> {
Report {
inner: Box::new(self.inner),
}
}
}
impl<E> Report<E> {
pub fn error(&self) -> &E {
&self.inner.error
}
pub fn error_mut(&mut self) -> &mut E {
&mut self.inner.error
}
pub fn into_error(self) -> E {
self.inner.error
}
}
impl<E: Error> Report<E> {
#[track_caller]
pub fn new(error: E) -> Self {
ReportBuilder::new()
.with_backtrace()
.with_location()
.with_error(error)
.build()
}
fn erased(self) -> ErasedReport {
ErasedReport { inner: self.inner }
}
pub fn add_info<I: Printable>(&mut self, info: I) {
self.inner.meta.add_info(info);
}
pub fn with_context<I: Printable>(mut self, info: I) -> Self {
self.add_info(info);
self
}
}
impl<E: Error> Debug for Report<E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if f.alternate() {
f.debug_struct("Report")
.field("error", &self.inner.error)
.finish_non_exhaustive()
} else {
f.write_str(&renderer::render_report(self))
}
}
}
pub trait Printable: 'static + Send + Display {}
impl<P: 'static + Send + Display> Printable for P {}
impl Debug for dyn Printable {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Printable({:?})", self.to_string())
}
}
pub trait ReportAs<E>: Error {
fn report_as(self, builder: ReportBuilder) -> ReportBuilder<E>;
}
impl<E, F> ReportAs<E> for F
where
F: Into<E> + Error,
E: Error,
{
fn report_as(self, builder: ReportBuilder) -> ReportBuilder<E> {
builder.with_error(self.into())
}
}
impl<E, F> From<E> for Report<F>
where
E: ReportAs<F> + Error,
F: Error,
{
#[track_caller]
fn from(value: E) -> Self {
value.report_as(ReportBuilder::new()).build()
}
}
pub trait PropagateAs<E>: Sized {
fn propagate_as(report: Report<Self>) -> Report<E>;
}
impl<E, F> PropagateAs<F> for E
where
E: Error,
F: Error + for<'error> From<&'error E>,
{
fn propagate_as(report: Report<Self>) -> Report<F> {
ReportBuilder::new()
.with_error(F::from(report.error()))
.with_cause(report)
.build()
}
}
pub trait Whatever: Error + Sized {
fn new() -> Self;
#[expect(unused_variables)]
fn from_error<E>(error: &E) -> Self
where
E: Error,
{
Self::new()
}
}
mod private {
use crate::Printable;
pub trait Report {
type Error: crate::Error;
fn into_report(self) -> crate::Report<Self::Error>;
fn as_report(&self) -> &crate::Report<Self::Error>;
fn as_report_mut(&mut self) -> &mut crate::Report<Self::Error>;
fn add_context<C>(&mut self, ctx: C)
where
C: Printable;
}
impl<E: crate::Error> Report for crate::Report<E> {
type Error = E;
fn into_report(self) -> crate::Report<Self::Error> {
self
}
fn as_report(&self) -> &crate::Report<Self::Error> {
self
}
fn as_report_mut(&mut self) -> &mut crate::Report<Self::Error> {
self
}
fn add_context<C>(&mut self, ctx: C)
where
C: Printable,
{
self.inner.meta.add_info(ctx);
}
}
pub enum MaybeReport<E> {
Error(E),
Report(crate::Report<E>),
}
pub trait ReportOrError {
type Error: crate::Error;
fn maybe_report(self) -> MaybeReport<Self::Error>;
}
impl<E: crate::Error> ReportOrError for E {
type Error = E;
#[inline(always)]
fn maybe_report(self) -> MaybeReport<Self::Error> {
MaybeReport::Error(self)
}
}
impl<E: crate::Error> ReportOrError for crate::Report<E> {
type Error = E;
#[inline(always)]
fn maybe_report(self) -> MaybeReport<Self::Error> {
MaybeReport::Report(self)
}
}
}
pub trait ErrorExt {
fn report<F>(self) -> Report<F>
where
Self: ReportAs<F>,
F: Error;
fn whatever<F, C>(self, description: C) -> Report<F>
where
F: Whatever,
C: Printable;
fn whatever_with<F, C, X>(self, description: X) -> Report<F>
where
F: Whatever,
X: FnOnce(&Self) -> C,
C: Printable;
}
impl<E: private::ReportOrError> ErrorExt for E {
#[track_caller]
fn report<F>(self) -> Report<F>
where
Self: ReportAs<F>,
F: Error,
{
self.report_as(ReportBuilder::new()).build()
}
#[track_caller]
fn whatever<F, C>(self, description: C) -> Report<F>
where
F: Whatever,
C: Printable,
{
self.whatever_with(|_| description)
}
#[track_caller]
fn whatever_with<F, C, X>(self, description: X) -> Report<F>
where
F: Whatever,
X: FnOnce(&Self) -> C,
C: Printable,
{
let description = description(&self);
match self.maybe_report() {
private::MaybeReport::Error(error) => ReportBuilder::new()
.with_backtrace()
.with_location()
.with_error(F::from_error(&error))
.with_cause(ReportBuilder::new().with_error(error).build()),
private::MaybeReport::Report(report) => ReportBuilder::new()
.with_location()
.with_error(F::from_error(report.error()))
.with_cause(report),
}
.with_description(description)
.build()
}
}
#[track_caller]
pub fn whatever<E, C>(ctx: C) -> Report<E>
where
E: Whatever,
C: Printable,
{
Report::new(E::new()).with_context(ctx)
}
#[macro_export]
macro_rules! bail {
($($args:tt)*) => {
return Err($crate::whatever(format!($($args)*)))
};
}
pub trait ResultExt<T, E> {
fn report<F>(self) -> Result<T, Report<F>>
where
E: ReportAs<F>,
F: Error;
fn whatever<F, C>(self, description: C) -> Result<T, Report<F>>
where
F: Whatever,
E: private::ReportOrError,
C: Printable;
fn whatever_with<F, C, X>(self, description: X) -> Result<T, Report<F>>
where
F: Whatever,
E: private::ReportOrError,
X: FnOnce(&E) -> C,
C: Printable;
fn propagate<F>(self) -> Result<T, Report<F>>
where
F: Error,
E: private::Report,
E::Error: PropagateAs<F>;
fn with_info<C, X>(self, ctx: X) -> Self
where
E: private::Report,
X: FnOnce(&E::Error) -> C,
C: Printable;
}
impl<T, E> ResultExt<T, E> for Result<T, E> {
#[track_caller]
fn report<F>(self) -> Result<T, Report<F>>
where
E: ReportAs<F>,
F: Error,
{
self.map_err(|error| error.report_as(ReportBuilder::new()).build())
}
#[track_caller]
fn whatever<F, C>(self, description: C) -> Result<T, Report<F>>
where
F: Whatever,
E: private::ReportOrError,
C: Printable,
{
match self {
Ok(value) => Ok(value),
Err(error) => Err(error.whatever(description)),
}
}
#[track_caller]
fn whatever_with<F, C, X>(self, description: X) -> Result<T, Report<F>>
where
F: Whatever,
E: private::ReportOrError,
X: FnOnce(&E) -> C,
C: Printable,
{
match self {
Ok(value) => Ok(value),
Err(error) => Err(error.whatever_with(description)),
}
}
fn propagate<F>(self) -> Result<T, Report<F>>
where
F: Error,
E: private::Report,
E::Error: PropagateAs<F>,
{
self.map_err(|report| E::Error::propagate_as(report.into_report()))
}
fn with_info<C, X>(mut self, ctx: X) -> Self
where
E: private::Report,
X: FnOnce(&E::Error) -> C,
C: Printable,
{
if let Err(report) = &mut self {
let ctx = ctx(report.as_report().error());
report.add_context(ctx);
}
self
}
}
#[macro_export]
macro_rules! new_whatever_type {
($(#[$meta:meta])* $name:ident) => {
$(#[$meta])*
#[derive(Debug)]
pub struct $name(());
impl $crate::Error for $name {
fn description(&self) -> Option<&dyn ::std::fmt::Display> {
None
}
}
impl $crate::Whatever for $name {
fn new() -> Self {
$name(())
}
}
};
}