ohno/
error_trace.rs

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use std::borrow::Cow;
5use std::error::Error as StdError;
6
7use crate::{OhnoCore, TraceInfo};
8
9/// Base trait for adding error trace to error types.
10///
11/// This trait provides the fundamental error trace addition method and is dyn-compatible.
12/// It serves as the base for the more ergonomic `ErrorTraceExt` trait.
13pub trait ErrorTrace {
14    /// Adds error trace information to the error.
15    ///
16    /// This is the core method that other error trace methods build upon.
17    fn add_error_trace(&mut self, trace: TraceInfo);
18}
19
20/// Extension trait providing ergonomic error trace addition methods.
21///
22/// This trait extends `ErrorTrace` with convenient methods for adding error traces
23/// when converting or working with errors. It provides both immediate and
24/// lazy evaluation options.
25pub trait ErrorTraceExt: ErrorTrace {
26    /// Wraps the error with error trace.
27    #[must_use]
28    fn error_trace(mut self, trace: impl Into<Cow<'static, str>>) -> Self
29    where
30        Self: Sized,
31    {
32        self.add_error_trace(TraceInfo::new(trace));
33        self
34    }
35
36    /// Wraps the error with detailed error trace including file and line information.
37    #[must_use]
38    fn detailed_error_trace(mut self, trace: impl Into<Cow<'static, str>>, file: &'static str, line: u32) -> Self
39    where
40        Self: Sized,
41    {
42        self.add_error_trace(TraceInfo::detailed(trace, file, line));
43        self
44    }
45
46    /// Wraps the error with lazily evaluated error trace.
47    #[must_use]
48    fn with_error_trace<F, R>(mut self, f: F) -> Self
49    where
50        F: FnOnce() -> R,
51        R: Into<Cow<'static, str>>,
52        Self: Sized,
53    {
54        self.add_error_trace(TraceInfo::new(f()));
55        self
56    }
57
58    /// Wraps the error with lazily evaluated detailed error trace including file and line information.
59    #[must_use]
60    fn with_detailed_error_trace<F, R>(mut self, f: F, file: &'static str, line: u32) -> Self
61    where
62        F: FnOnce() -> R,
63        R: Into<Cow<'static, str>>,
64        Self: Sized,
65    {
66        self.add_error_trace(TraceInfo::detailed(f(), file, line));
67        self
68    }
69}
70
71impl ErrorTrace for OhnoCore {
72    fn add_error_trace(&mut self, trace: TraceInfo) {
73        self.data.context.push(trace);
74    }
75}
76
77impl<T, E> ErrorTrace for Result<T, E>
78where
79    E: StdError + ErrorTrace,
80{
81    fn add_error_trace(&mut self, trace: TraceInfo) {
82        if let Err(e) = self {
83            e.add_error_trace(trace);
84        }
85    }
86}
87
88// Blanket implementation: all types that implement ErrorTrace automatically get ErrorTraceExt
89impl<T: ErrorTrace> ErrorTraceExt for T {}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[derive(Default, ohno::Error)]
96    pub struct TestError {
97        pub data: OhnoCore,
98    }
99
100    #[test]
101    fn test_error_trace() {
102        let mut error = TestError::default();
103        error.add_error_trace(TraceInfo::new("Test trace"));
104        assert_eq!(error.data.data.context.len(), 1);
105        assert_eq!(error.data.data.context[0].message, "Test trace");
106        assert!(error.data.data.context[0].location.is_none());
107
108        error.add_error_trace(TraceInfo::detailed("Test trace", "test.rs", 10));
109        assert_eq!(error.data.data.context.len(), 2);
110        assert_eq!(error.data.data.context[1].message, "Test trace");
111        let location = error.data.data.context[1].location.as_ref().unwrap();
112        assert_eq!(location.file, "test.rs");
113        assert_eq!(location.line, 10);
114    }
115
116    #[test]
117    fn test_error_trace_ext() {
118        let error = TestError::default();
119        let mut result: Result<(), _> = Err(error);
120
121        result.add_error_trace(TraceInfo::new("Immediate trace"));
122
123        let err = result.unwrap_err();
124        assert_eq!(err.data.data.context.len(), 1);
125        assert_eq!(err.data.data.context[0].message, "Immediate trace");
126        assert!(err.data.data.context[0].location.is_none());
127
128        result = Err(err).detailed_error_trace("Detailed trace", "test.rs", 20);
129        let err = result.unwrap_err();
130
131        assert_eq!(err.data.data.context.len(), 2);
132        assert_eq!(err.data.data.context[1].message, "Detailed trace");
133        let location = err.data.data.context[1].location.as_ref().unwrap();
134        assert_eq!(location.file, "test.rs");
135        assert_eq!(location.line, 20);
136    }
137}