track-error 0.1.0

This library provides serveral convenient macros to track the location of error where it first happened.
Documentation
//! This library provides a convenient macro and function to track the location of error where it first happened.
//!
//! # Examples
//!
//! # Add dependency
//!
//! ```toml
//! track-error = { version = "0.1"}
//! ```
//!
//! # How to use
//! ```
//! use track_error::{Track, Tracked, track_error};
//!
//! fn error_func() -> Result<(), String> {
//!     Err("something went wrong".to_string())
//! }
//!
//! fn track_result() -> Result<(), Tracked<String>> {
//!     error_func().track()?;
//!     Ok(())
//! }
//!
//! fn track_macro() -> Result<(), Tracked<String>> {
//!     let _ = error_func().map_err(|e| track_error!(e))?;
//!     Ok(())
//! }
//!
//! fn main() {
//!     if let Err(e) = track_result() {
//!         println!("Error: {}", e);
//!         println!("Location: {}:{}", e.location().0, e.location().1);
//!     }
//!     
//!      if let Err(e) = track_macro() {
//!         println!("Error: {}", e);
//!         println!("Location: {}:{}", e.location().0, e.location().1);
//!     }
//! }
//! ```
use std::fmt;

pub trait Track {
    type Ok;
    type Err;

    #[track_caller]
    fn track(self) -> Result<Self::Ok, Tracked<Self::Err>>;
}

#[derive(Debug, Clone)]
pub struct Tracked<E> {
    inner: E,
    file: &'static str,
    line: u32,
}

impl<E> Tracked<E> {
    pub fn new(error: E, file: &'static str, line: u32) -> Self {
        Self {
            inner: error,
            file,
            line,
        }
    }

    pub fn inner(&self) -> &E {
        &self.inner
    }

    pub fn into_inner(self) -> E {
        self.inner
    }

    pub fn location(&self) -> (&'static str, u32) {
        (self.file, self.line)
    }

    pub fn file(&self) -> &'static str {
        self.file
    }

    pub fn line(&self) -> u32 {
        self.line
    }
}

impl<E: fmt::Display> fmt::Display for Tracked<E> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{} at {}:{}", self.inner, self.file, self.line)
    }
}

impl<E: std::error::Error + 'static> std::error::Error for Tracked<E> {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        Some(&self.inner)
    }
}

impl<T, E> Track for Result<T, E> {
    type Ok = T;
    type Err = E;

    #[track_caller]
    fn track(self) -> Result<T, Tracked<E>> {
        match self {
            Ok(val) => Ok(val),
            Err(e) => {
                let location = std::panic::Location::caller();
                Err(Tracked::new(e, location.file(), location.line()))
            }
        }
    }
}

#[macro_export]
macro_rules! track {
    ($expr:expr) => {
        match $expr {
            Ok(val) => Ok(val),
            Err(e) => Err($crate::Tracked::new(e, file!(), line!())),
        }
    };
}

#[macro_export]
macro_rules! track_error {
    ($error:expr) => {
        $crate::Tracked::new($error, file!(), line!())
    };
    ($error:expr, $($arg:tt)*) => {
        $crate::Tracked::new(format!($($arg)*, $error), file!(), line!())
    };
}

#[cfg(test)]
mod test;