1use 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;