1pub trait TraceableError: std::fmt::Debug + std::fmt::Display {
33 fn layer_code(&self) -> String;
35
36 fn chain_codes(&self) -> Vec<String>;
40
41 fn diagnostic_code(&self) -> String {
43 self.chain_codes().join("-")
44 }
45}
46
47impl TraceableError for anyhow::Error {
48 fn layer_code(&self) -> String {
49 if let Some(boxed) = self.downcast_ref::<TraceableBox>() {
50 boxed.layer_code()
51 } else {
52 "anyhow".to_string()
53 }
54 }
55
56 fn chain_codes(&self) -> Vec<String> {
57 if let Some(boxed) = self.downcast_ref::<TraceableBox>() {
58 boxed.chain_codes()
62 } else {
63 vec!["anyhow".to_string()]
64 }
65 }
66}
67
68#[derive(Debug)]
93pub struct TraceableBox(pub Box<dyn TraceableError + Send + Sync + 'static>);
94
95impl<E: TraceableError + Send + Sync + 'static> From<E> for TraceableBox {
96 fn from(err: E) -> Self {
97 TraceableBox(Box::new(err))
98 }
99}
100
101impl TraceableBox {
102 pub fn layer_code(&self) -> String {
103 self.0.layer_code()
104 }
105
106 pub fn chain_codes(&self) -> Vec<String> {
107 self.0.chain_codes()
108 }
109
110 pub fn diagnostic_code(&self) -> String {
111 self.0.diagnostic_code()
112 }
113}
114
115impl std::fmt::Display for TraceableBox {
116 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
117 let mut err_str = self.0.to_string();
118 let chain = self.0.chain_codes();
119 let diag_code = self.0.diagnostic_code();
120
121 let mut sub_slice = diag_code.as_str();
122 for (i, layer) in chain.iter().enumerate() {
123 let suffix = format!(" [{}]", sub_slice);
124 if let Some(stripped) = err_str.strip_suffix(&suffix) {
125 err_str = stripped.to_string();
126 break;
127 }
128 if i + 1 < chain.len() {
129 sub_slice = &sub_slice[layer.len() + 1..];
130 }
131 }
132
133 if err_str.is_empty() {
134 write!(f, "[{}]", diag_code)
135 } else {
136 write!(f, "{} [{}]", err_str, diag_code)
137 }
138 }
139}
140
141impl std::error::Error for TraceableBox {}
142
143#[cfg(test)]
144mod tests {
145 use super::*;
146
147 #[derive(Debug)]
148 struct DummyError;
149 impl std::fmt::Display for DummyError {
150 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
151 write!(f, "DummyError")
152 }
153 }
154 impl TraceableError for DummyError {
155 fn layer_code(&self) -> String {
156 "DummyError".to_string()
157 }
158 fn chain_codes(&self) -> Vec<String> {
159 vec![self.layer_code()]
160 }
161 }
162
163 #[test]
164 fn test_traceable_error() {
165 let _err = DummyError;
166 }
167
168 #[test]
169 fn test_traceable_box_display_deduplicates_suffix() {
170 #[derive(Debug)]
171 struct InnerError;
172 impl std::fmt::Display for InnerError {
173 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
174 write!(f, "root failure")
175 }
176 }
177 impl TraceableError for InnerError {
178 fn layer_code(&self) -> String {
179 "Inner".to_string()
180 }
181 fn chain_codes(&self) -> Vec<String> {
182 vec![self.layer_code()]
183 }
184 }
185
186 #[derive(Debug)]
187 struct OuterError(TraceableBox);
188 impl std::fmt::Display for OuterError {
189 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190 write!(f, "outer wrapper: {}", self.0)
191 }
192 }
193 impl TraceableError for OuterError {
194 fn layer_code(&self) -> String {
195 "Outer".to_string()
196 }
197 fn chain_codes(&self) -> Vec<String> {
198 let mut c = self.0.chain_codes();
199 c.insert(0, self.layer_code());
200 c
201 }
202 }
203
204 let inner_box = TraceableBox::from(InnerError);
205 assert_eq!(inner_box.to_string(), "root failure [Inner]");
206
207 let outer_box = TraceableBox::from(OuterError(inner_box));
208 assert_eq!(outer_box.to_string(), "outer wrapper: root failure [Outer-Inner]");
209 }
210
211 #[test]
212 fn test_anyhow_traceable() {
213 let err = anyhow::anyhow!("boom");
214 assert_eq!(err.chain_codes().len(), 1);
215 assert_eq!(err.chain_codes()[0], "anyhow");
216 }
217 #[test]
218 fn test_traceable_box_conversion() {
219 fn produce_anyhow() -> anyhow::Result<()> {
220 Err(anyhow::anyhow!("root failure"))
221 }
222
223 fn consume_box() -> Result<(), TraceableBox> {
224 produce_anyhow()?;
225 Ok(())
226 }
227
228 let boxed_err = consume_box().unwrap_err();
229 assert_eq!(boxed_err.chain_codes().len(), 1);
230 assert!(boxed_err.to_string().contains("root failure"));
231 assert!(boxed_err.to_string().contains("[anyhow]"));
232 }
233
234 #[test]
235 fn test_nested_traceable_box_display() {
236 let root_err = DummyError;
237 let boxed_root: TraceableBox = root_err.into();
238 let anyhow_err = anyhow::Error::new(boxed_root);
239 let boxed_anyhow: TraceableBox = anyhow_err.into();
240
241 let display_str = boxed_anyhow.to_string();
242 let occurrences = display_str.matches("[DummyError]").count();
243 assert_eq!(
244 occurrences, 1,
245 "Expected '[DummyError]' to appear only once in: {}",
246 display_str
247 );
248 }
249}