Skip to main content

track_error/
lib.rs

1//! This library provides a convenient macro and function to track the location of error where it first happened.
2//!
3//! # Examples
4//!
5//! # Add dependency
6//!
7//! ```toml
8//! track-error = { version = "0.1"}
9//! ```
10//!
11//! # How to use
12//! ```
13//! use track_error::{Track, Tracked, track_error};
14//!
15//! fn error_func() -> Result<(), String> {
16//!     Err("something went wrong".to_string())
17//! }
18//!
19//! fn track_result() -> Result<(), Tracked<String>> {
20//!     error_func().track()?;
21//!     Ok(())
22//! }
23//!
24//! fn track_macro() -> Result<(), Tracked<String>> {
25//!     let _ = error_func().map_err(|e| track_error!(e))?;
26//!     Ok(())
27//! }
28//!
29//! fn main() {
30//!     if let Err(e) = track_result() {
31//!         println!("Error: {}", e);
32//!         println!("Location: {}:{}", e.location().0, e.location().1);
33//!     }
34//!     
35//!      if let Err(e) = track_macro() {
36//!         println!("Error: {}", e);
37//!         println!("Location: {}:{}", e.location().0, e.location().1);
38//!     }
39//! }
40//! ```
41use std::fmt;
42
43pub trait Track {
44    type Ok;
45    type Err;
46
47    #[track_caller]
48    fn track(self) -> Result<Self::Ok, Tracked<Self::Err>>;
49}
50
51#[derive(Debug, Clone)]
52pub struct Tracked<E> {
53    inner: E,
54    file: &'static str,
55    line: u32,
56}
57
58impl<E> Tracked<E> {
59    pub fn new(error: E, file: &'static str, line: u32) -> Self {
60        Self {
61            inner: error,
62            file,
63            line,
64        }
65    }
66
67    pub fn inner(&self) -> &E {
68        &self.inner
69    }
70
71    pub fn into_inner(self) -> E {
72        self.inner
73    }
74
75    pub fn location(&self) -> (&'static str, u32) {
76        (self.file, self.line)
77    }
78
79    pub fn file(&self) -> &'static str {
80        self.file
81    }
82
83    pub fn line(&self) -> u32 {
84        self.line
85    }
86}
87
88impl<E: fmt::Display> fmt::Display for Tracked<E> {
89    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
90        write!(f, "{} at {}:{}", self.inner, self.file, self.line)
91    }
92}
93
94impl<E: std::error::Error + 'static> std::error::Error for Tracked<E> {
95    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
96        Some(&self.inner)
97    }
98}
99
100impl<T, E> Track for Result<T, E> {
101    type Ok = T;
102    type Err = E;
103
104    #[track_caller]
105    fn track(self) -> Result<T, Tracked<E>> {
106        match self {
107            Ok(val) => Ok(val),
108            Err(e) => {
109                let location = std::panic::Location::caller();
110                Err(Tracked::new(e, location.file(), location.line()))
111            }
112        }
113    }
114}
115
116#[macro_export]
117macro_rules! track {
118    ($expr:expr) => {
119        match $expr {
120            Ok(val) => Ok(val),
121            Err(e) => Err($crate::Tracked::new(e, file!(), line!())),
122        }
123    };
124}
125
126#[macro_export]
127macro_rules! track_error {
128    ($error:expr) => {
129        $crate::Tracked::new($error, file!(), line!())
130    };
131    ($error:expr, $($arg:tt)*) => {
132        $crate::Tracked::new(format!($($arg)*, $error), file!(), line!())
133    };
134}
135
136#[cfg(test)]
137mod test;