1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Error transformation methods for Report.
//!
//! This module contains the `map_err` method and related functionality
//! for transforming the inner error type while preserving diagnostic data.
use core::error::Error;
use super::types::build_origin_source_chain;
use super::{Report, SeverityState};
use alloc::boxed::Box;
impl<E, State> Report<E, State>
where
State: SeverityState,
{
/// Maps the inner error type while preserving all diagnostic data.
///
/// When source chain accumulation is enabled via [`ReportOptions::accumulate_src_chain`],
/// this method also accumulates the origin source error chain.
///
/// # Source Chain Accumulation
///
/// If `accumulate_src_chain` is `true`:
/// - The current report's origin source chain (if any) is preserved
/// - The old inner error is added as a source of the new outer error
/// - The resulting chain reflects: `outer -> old_inner -> ...old sources`
///
/// If `accumulate_src_chain` is `false`:
/// - Only the error type is transformed
/// - No source chain manipulation occurs
///
/// # Example
///
/// ```rust
/// use diagweave::prelude::Report;
/// use diagweave::Error;
///
/// #[derive(Debug, Error)]
/// #[display("inner error")]
/// struct InnerError;
///
/// #[derive(Debug, Error)]
/// #[display("outer error")]
/// struct OuterError;
///
/// impl From<InnerError> for OuterError {
/// fn from(e: InnerError) -> Self {
/// OuterError
/// }
/// }
///
/// let inner_error = InnerError;
/// let inner_report = Report::new(inner_error);
///
/// // Transform error type while preserving diagnostics
/// let report: Report<OuterError> = inner_report.map_err(|e| OuterError::from(e));
///
/// // Control source chain accumulation per-report
/// let _report = report.set_accumulate_src_chain(false); // Disable accumulation
/// ```
///
/// # Preserved Data
///
/// The following diagnostic data is preserved during transformation:
/// - Attachments (notes and payloads)
/// - Context (business and system)
/// - Display causes
/// - Stack traces
/// - Metadata (error code, category, retryable)
/// - Diagnostic source errors
/// - Trace context (when `trace` feature is enabled)
///
/// # Performance
///
/// When source chain accumulation is disabled (the default), this method
/// performs minimal work - it simply transforms the error type and moves
/// the diagnostic data. When accumulation is enabled, there is additional
/// overhead for building and preserving the source chain.
///
/// # Type Constraints
///
/// Both the input error type `E` and output error type `Outer` must implement:
/// - `Error` - The core error trait
/// - `Send + Sync` - Required for thread safety
/// - `'static` - Required for storing in the report
pub fn map_err<Outer>(self, map: impl FnOnce(E) -> Outer) -> Report<Outer, State>
where
E: Error + Send + Sync + 'static,
Outer: Error + Send + Sync + 'static,
{
let Self { inner, data } = self;
let super::ReportData {
metadata,
options,
#[cfg(feature = "trace")]
trace,
bag,
} = *data;
// Check if source chain accumulation is enabled for this report
if options.resolve_accumulate_src_chain() {
// Build origin source chain with the old inner as the new root
let origin_source_errors = build_origin_source_chain(&inner, bag.inner());
// Now create the outer error
let outer = map(inner);
// Build new bag with the origin source chain using the helper method
let new_bag = bag.with_origin_srcs(origin_source_errors);
Report {
inner: outer,
data: Box::new(super::ReportData {
metadata,
options,
#[cfg(feature = "trace")]
trace,
bag: new_bag,
}),
}
} else {
// Simple transformation without source chain accumulation
let outer = map(inner);
Report {
inner: outer,
data: Box::new(super::ReportData {
metadata,
options,
#[cfg(feature = "trace")]
trace,
bag,
}),
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::report::{HasSeverity, MissingSeverity, Report, Severity};
#[derive(Debug)]
struct TestError;
impl core::fmt::Display for TestError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("test error")
}
}
impl Error for TestError {}
#[derive(Debug)]
struct OuterError;
impl core::fmt::Display for OuterError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str("outer error")
}
}
impl Error for OuterError {}
impl From<TestError> for OuterError {
fn from(_: TestError) -> Self {
OuterError
}
}
#[test]
fn test_map_err_preserves_severity() {
let report: Report<TestError, MissingSeverity> = Report::new(TestError);
let mapped: Report<OuterError, MissingSeverity> =
Report::<TestError, MissingSeverity>::map_err(report, |_| OuterError);
assert!(Report::<OuterError, MissingSeverity>::severity(&mapped).is_none());
}
#[test]
fn test_map_err_with_severity() {
let report = Report::<TestError, MissingSeverity>::new(TestError);
let report = Report::<TestError, MissingSeverity>::set_severity(report, Severity::Error);
let mapped: Report<OuterError, HasSeverity> = report.map_err(|_| OuterError);
assert_eq!(mapped.severity(), Some(Severity::Error));
}
}