thistrace 0.1.0

Callsite provenance (file/line/col) for thiserror #[from] conversions via #[track_caller]
Documentation
use thistrace::{origin, HasTrace, OneLineTrace};

#[derive(thiserror::Error, Debug)]
#[error("leaf boom")]
struct LeafError;

#[derive(thiserror::Error, Debug)]
enum AppError {
    #[error("leaf")]
    Leaf {
        #[source]
        source: thistrace::Origin<LeafError>,
        trace: thistrace::Trace,
    },

    #[error("bubble")]
    Bubble {
        #[source]
        source: Box<AppError>,
        trace: thistrace::Trace,
    },
}

impl HasTrace for AppError {
    fn trace(&self) -> Option<&thistrace::Trace> {
        match self {
            Self::Leaf { trace, .. } => Some(trace),
            Self::Bubble { trace, .. } => Some(trace),
        }
    }
}

impl From<thistrace::Origin<LeafError>> for AppError {
    #[track_caller]
    fn from(source: thistrace::Origin<LeafError>) -> Self {
        let (leaf, mut trace) = source.into_parts();
        let loc = std::panic::Location::caller();
        trace.push(thistrace::Frame::from_location(loc));
        Self::Leaf {
            source: thistrace::Origin::new(leaf, trace.clone()),
            trace,
        }
    }
}

impl From<thistrace::Bubbled<AppError>> for AppError {
    fn from(source: thistrace::Bubbled<AppError>) -> Self {
        let (inner, trace) = source.into_parts();
        Self::Bubble {
            source: Box::new(inner),
            trace,
        }
    }
}

fn make_leaf() -> Result<(), thistrace::Origin<LeafError>> {
    Err(origin(LeafError))
}

fn layer1() -> Result<(), AppError> {
    make_leaf()?;
    Ok(())
}

fn layer2() -> Result<(), AppError> {
    layer1().map_err(thistrace::bubble_into!(AppError))?;
    Ok(())
}

fn layer3() -> Result<(), AppError> {
    layer2().map_err(thistrace::bubble_into!(AppError))?;
    Ok(())
}

#[test]
fn single_enum_multiple_layers_can_accumulate_frames() {
    let err = layer3().unwrap_err();
    let frames = thistrace::trace_frames(&err);

    // leaf origin + layer1 conversion + layer2 bubble + layer3 bubble
    assert!(frames.len() >= 4);

    let one_line = format!("{}", OneLineTrace::new(&err));
    assert!(!one_line.contains('\n'));
}