use std::fmt;
pub use thistrace_macros::traceable;
pub mod prelude {
pub use crate::{
bubble, bubble_into, bubble_err, from_with_trace, map_err_with_trace, origin, rebubble,
rebubble_err, trace_frames, traceable, Bubbled, DisplayTrace, Frame, HasTrace, OneLineTrace,
Origin, Trace,
};
}
#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub struct Frame {
pub file: &'static str,
pub line: u32,
pub column: u32,
}
impl Frame {
pub fn from_location(location: &'static std::panic::Location<'static>) -> Self {
Self {
file: location.file(),
line: location.line(),
column: location.column(),
}
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct Trace {
frames: Vec<Frame>,
}
impl Trace {
pub fn empty() -> Self {
Self { frames: Vec::new() }
}
pub fn from_frame(frame: Frame) -> Self {
Self { frames: vec![frame] }
}
pub fn push(&mut self, frame: Frame) {
self.frames.push(frame);
}
pub fn frames(&self) -> &[Frame] {
&self.frames
}
}
pub trait HasTrace {
fn trace(&self) -> Option<&Trace>;
}
pub fn trace_frames<T: HasTrace + ?Sized>(err: &T) -> &[Frame] {
static EMPTY: [Frame; 0] = [];
err.trace().map(Trace::frames).unwrap_or(&EMPTY)
}
#[track_caller]
pub fn map_err_with_trace<T, E, O, F>(res: Result<T, E>, f: F) -> Result<T, O>
where
F: FnOnce(E, Trace) -> O,
{
let loc = std::panic::Location::caller();
let trace = Trace::from_frame(Frame::from_location(loc));
res.map_err(|e| f(e, trace))
}
#[macro_export]
macro_rules! from_with_trace {
($expr:expr, $variant:path { $($field:ident),* $(,)? }) => {
$crate::from_with_trace!($expr, $variant { $($field: $field),* })
};
($expr:expr, $variant:path { $($field:ident : _),* $(,)? }) => {
$crate::from_with_trace!(
$expr,
$variant { $($field: ::core::default::Default::default()),* }
)
};
($expr:expr, $variant:path { $($field:ident : $value:expr),* $(,)? }) => {
$crate::map_err_with_trace($expr, |source, trace| {
$variant { source, trace, $($field: $value),* }
})
};
}
#[derive(Debug)]
pub struct Origin<E> {
source: E,
trace: Trace,
}
impl<E> Origin<E> {
pub fn new(source: E, trace: Trace) -> Self {
Self { source, trace }
}
pub fn into_inner(self) -> E {
self.source
}
pub fn into_parts(self) -> (E, Trace) {
(self.source, self.trace)
}
}
#[track_caller]
pub fn origin<E>(source: E) -> Origin<E> {
let loc = std::panic::Location::caller();
let frame = Frame::from_location(loc);
Origin::new(source, Trace::from_frame(frame))
}
#[derive(Debug)]
pub struct Bubbled<E> {
source: E,
trace: Trace,
}
impl<E> Bubbled<E> {
pub fn new(source: E, trace: Trace) -> Self {
Self { source, trace }
}
pub fn into_parts(self) -> (E, Trace) {
(self.source, self.trace)
}
}
#[track_caller]
pub fn bubble<E>(source: E) -> Bubbled<E>
where
E: std::error::Error + HasTrace,
{
let loc = std::panic::Location::caller();
let frame = Frame::from_location(loc);
let mut trace = source.trace().cloned().unwrap_or_else(Trace::empty);
trace.push(frame);
Bubbled::new(source, trace)
}
#[track_caller]
pub fn rebubble<E>(source: Bubbled<E>) -> Bubbled<E>
where
E: std::error::Error,
{
let (inner, mut trace) = source.into_parts();
let loc = std::panic::Location::caller();
trace.push(Frame::from_location(loc));
Bubbled::new(inner, trace)
}
#[macro_export]
macro_rules! bubble_err {
() => {
|e| $crate::bubble(e)
};
}
#[macro_export]
macro_rules! rebubble_err {
() => {
|e| $crate::rebubble(e)
};
}
#[macro_export]
macro_rules! bubble_into {
($ty:ty) => {
|e| <$ty as ::core::convert::From<_>>::from($crate::bubble(e))
};
}
impl<E> HasTrace for Bubbled<E> {
fn trace(&self) -> Option<&Trace> {
Some(&self.trace)
}
}
impl<E> fmt::Display for Bubbled<E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.source.fmt(f)
}
}
impl<E> std::error::Error for Bubbled<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.source()
}
}
impl<E> HasTrace for Origin<E> {
fn trace(&self) -> Option<&Trace> {
Some(&self.trace)
}
}
impl<E> fmt::Display for Origin<E>
where
E: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.source.fmt(f)
}
}
impl<E> std::error::Error for Origin<E>
where
E: std::error::Error + 'static,
{
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
self.source.source()
}
}
pub struct DisplayTrace<'a, E: ?Sized> {
err: &'a E,
}
impl<'a, E: ?Sized> DisplayTrace<'a, E> {
pub fn new(err: &'a E) -> Self {
Self { err }
}
}
impl<E> fmt::Display for DisplayTrace<'_, E>
where
E: std::error::Error + HasTrace,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.err)?;
if let Some(trace) = self.err.trace() {
writeln!(f)?;
for frame in trace.frames() {
writeln!(
f,
" at {}:{}:{}",
frame.file, frame.line, frame.column
)?;
}
}
let mut cur: Option<&(dyn std::error::Error + 'static)> = self.err.source();
while let Some(e) = cur {
writeln!(f, "\ncaused by: {e}")?;
cur = e.source();
}
Ok(())
}
}
pub struct OneLineTrace<'a, E: ?Sized> {
err: &'a E,
}
impl<'a, E: ?Sized> OneLineTrace<'a, E> {
pub fn new(err: &'a E) -> Self {
Self { err }
}
}
impl<E> fmt::Display for OneLineTrace<'_, E>
where
E: std::error::Error + HasTrace,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.err)?;
if let Some(trace) = self.err.trace() {
write!(f, " [trace")?;
for frame in trace.frames() {
write!(f, " {}:{}:{}", frame.file, frame.line, frame.column)?;
}
write!(f, " ]")?;
}
let mut cur: Option<&(dyn std::error::Error + 'static)> = self.err.source();
while let Some(e) = cur {
write!(f, " | caused by: {e}")?;
cur = e.source();
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn trace_from_location_copies_fields() {
#[track_caller]
fn capture() -> Frame {
Frame::from_location(std::panic::Location::caller())
}
let frame = capture();
assert!(frame.file.ends_with("lib.rs"));
assert!(frame.line > 0);
assert!(frame.column > 0);
}
}