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;