1use std::fmt;
34
35pub use thistrace_macros::traceable;
36
37pub mod prelude {
38 pub use crate::{
39 bubble, bubble_into, bubble_err, from_with_trace, map_err_with_trace, origin, rebubble,
40 rebubble_err, trace_frames, traceable, Bubbled, DisplayTrace, Frame, HasTrace, OneLineTrace,
41 Origin, Trace,
42 };
43}
44
45#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
46pub struct Frame {
47 pub file: &'static str,
48 pub line: u32,
49 pub column: u32,
50}
51
52impl Frame {
53 pub fn from_location(location: &'static std::panic::Location<'static>) -> Self {
54 Self {
55 file: location.file(),
56 line: location.line(),
57 column: location.column(),
58 }
59 }
60}
61
62#[derive(Clone, Debug, Default, Eq, PartialEq)]
63pub struct Trace {
64 frames: Vec<Frame>,
65}
66
67impl Trace {
68 pub fn empty() -> Self {
69 Self { frames: Vec::new() }
70 }
71
72 pub fn from_frame(frame: Frame) -> Self {
73 Self { frames: vec![frame] }
74 }
75
76 pub fn push(&mut self, frame: Frame) {
77 self.frames.push(frame);
78 }
79
80 pub fn frames(&self) -> &[Frame] {
81 &self.frames
82 }
83}
84
85pub trait HasTrace {
86 fn trace(&self) -> Option<&Trace>;
87}
88
89pub fn trace_frames<T: HasTrace + ?Sized>(err: &T) -> &[Frame] {
90 static EMPTY: [Frame; 0] = [];
91 err.trace().map(Trace::frames).unwrap_or(&EMPTY)
92}
93
94#[track_caller]
95pub fn map_err_with_trace<T, E, O, F>(res: Result<T, E>, f: F) -> Result<T, O>
96where
97 F: FnOnce(E, Trace) -> O,
98{
99 let loc = std::panic::Location::caller();
100 let trace = Trace::from_frame(Frame::from_location(loc));
101 res.map_err(|e| f(e, trace))
102}
103
104#[macro_export]
105macro_rules! from_with_trace {
106 ($expr:expr, $variant:path { $($field:ident),* $(,)? }) => {
107 $crate::from_with_trace!($expr, $variant { $($field: $field),* })
108 };
109 ($expr:expr, $variant:path { $($field:ident : _),* $(,)? }) => {
110 $crate::from_with_trace!(
111 $expr,
112 $variant { $($field: ::core::default::Default::default()),* }
113 )
114 };
115 ($expr:expr, $variant:path { $($field:ident : $value:expr),* $(,)? }) => {
116 $crate::map_err_with_trace($expr, |source, trace| {
117 $variant { source, trace, $($field: $value),* }
118 })
119 };
120}
121
122#[derive(Debug)]
123pub struct Origin<E> {
124 source: E,
125 trace: Trace,
126}
127
128impl<E> Origin<E> {
129 pub fn new(source: E, trace: Trace) -> Self {
130 Self { source, trace }
131 }
132
133 pub fn into_inner(self) -> E {
134 self.source
135 }
136
137 pub fn into_parts(self) -> (E, Trace) {
138 (self.source, self.trace)
139 }
140}
141
142#[track_caller]
143pub fn origin<E>(source: E) -> Origin<E> {
144 let loc = std::panic::Location::caller();
145 let frame = Frame::from_location(loc);
146 Origin::new(source, Trace::from_frame(frame))
147}
148
149#[derive(Debug)]
150pub struct Bubbled<E> {
151 source: E,
152 trace: Trace,
153}
154
155impl<E> Bubbled<E> {
156 pub fn new(source: E, trace: Trace) -> Self {
157 Self { source, trace }
158 }
159
160 pub fn into_parts(self) -> (E, Trace) {
161 (self.source, self.trace)
162 }
163}
164
165#[track_caller]
166pub fn bubble<E>(source: E) -> Bubbled<E>
167where
168 E: std::error::Error + HasTrace,
169{
170 let loc = std::panic::Location::caller();
171 let frame = Frame::from_location(loc);
172 let mut trace = source.trace().cloned().unwrap_or_else(Trace::empty);
173 trace.push(frame);
174 Bubbled::new(source, trace)
175}
176
177#[track_caller]
178pub fn rebubble<E>(source: Bubbled<E>) -> Bubbled<E>
179where
180 E: std::error::Error,
181{
182 let (inner, mut trace) = source.into_parts();
183 let loc = std::panic::Location::caller();
184 trace.push(Frame::from_location(loc));
185 Bubbled::new(inner, trace)
186}
187
188#[macro_export]
189macro_rules! bubble_err {
190 () => {
191 |e| $crate::bubble(e)
192 };
193}
194
195#[macro_export]
196macro_rules! rebubble_err {
197 () => {
198 |e| $crate::rebubble(e)
199 };
200}
201
202#[macro_export]
203macro_rules! bubble_into {
204 ($ty:ty) => {
205 |e| <$ty as ::core::convert::From<_>>::from($crate::bubble(e))
206 };
207}
208
209impl<E> HasTrace for Bubbled<E> {
210 fn trace(&self) -> Option<&Trace> {
211 Some(&self.trace)
212 }
213}
214
215impl<E> fmt::Display for Bubbled<E>
216where
217 E: fmt::Display,
218{
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 self.source.fmt(f)
221 }
222}
223
224impl<E> std::error::Error for Bubbled<E>
225where
226 E: std::error::Error + 'static,
227{
228 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
229 self.source.source()
230 }
231}
232
233impl<E> HasTrace for Origin<E> {
234 fn trace(&self) -> Option<&Trace> {
235 Some(&self.trace)
236 }
237}
238
239impl<E> fmt::Display for Origin<E>
240where
241 E: fmt::Display,
242{
243 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
244 self.source.fmt(f)
245 }
246}
247
248impl<E> std::error::Error for Origin<E>
249where
250 E: std::error::Error + 'static,
251{
252 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
253 self.source.source()
254 }
255}
256
257pub struct DisplayTrace<'a, E: ?Sized> {
258 err: &'a E,
259}
260
261impl<'a, E: ?Sized> DisplayTrace<'a, E> {
262 pub fn new(err: &'a E) -> Self {
263 Self { err }
264 }
265}
266
267impl<E> fmt::Display for DisplayTrace<'_, E>
268where
269 E: std::error::Error + HasTrace,
270{
271 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
272 write!(f, "{}", self.err)?;
273
274 if let Some(trace) = self.err.trace() {
275 writeln!(f)?;
276 for frame in trace.frames() {
277 writeln!(
278 f,
279 " at {}:{}:{}",
280 frame.file, frame.line, frame.column
281 )?;
282 }
283 }
284
285 let mut cur: Option<&(dyn std::error::Error + 'static)> = self.err.source();
286 while let Some(e) = cur {
287 writeln!(f, "\ncaused by: {e}")?;
288 cur = e.source();
289 }
290
291 Ok(())
292 }
293}
294
295pub struct OneLineTrace<'a, E: ?Sized> {
296 err: &'a E,
297}
298
299impl<'a, E: ?Sized> OneLineTrace<'a, E> {
300 pub fn new(err: &'a E) -> Self {
301 Self { err }
302 }
303}
304
305impl<E> fmt::Display for OneLineTrace<'_, E>
306where
307 E: std::error::Error + HasTrace,
308{
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 write!(f, "{}", self.err)?;
311
312 if let Some(trace) = self.err.trace() {
313 write!(f, " [trace")?;
314 for frame in trace.frames() {
315 write!(f, " {}:{}:{}", frame.file, frame.line, frame.column)?;
316 }
317 write!(f, " ]")?;
318 }
319
320 let mut cur: Option<&(dyn std::error::Error + 'static)> = self.err.source();
321 while let Some(e) = cur {
322 write!(f, " | caused by: {e}")?;
323 cur = e.source();
324 }
325
326 Ok(())
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::*;
333
334 #[test]
335 fn trace_from_location_copies_fields() {
336 #[track_caller]
337 fn capture() -> Frame {
338 Frame::from_location(std::panic::Location::caller())
339 }
340
341 let frame = capture();
342 assert!(frame.file.ends_with("lib.rs"));
343 assert!(frame.line > 0);
344 assert!(frame.column > 0);
345 }
346}