suzunari_error/
stack_error.rs1use crate::Location;
2use core::error::Error;
3
4pub trait StackError: Error {
56 #[must_use]
58 fn location(&self) -> Location;
59
60 #[must_use]
69 fn type_name(&self) -> &'static str;
70
71 #[must_use]
88 fn stack_source(&self) -> Option<&dyn StackError> {
89 None
90 }
91
92 #[must_use]
100 fn depth(&self) -> usize {
101 let mut count = 0;
105 let mut current = self.source();
106 while let Some(e) = current {
107 count += 1;
108 current = e.source();
109 }
110 count
111 }
112}
113
114#[cfg(feature = "alloc")]
115mod alloc_impls {
116 use super::*;
117 use alloc::boxed::Box;
118 use alloc::sync::Arc;
119
120 impl<T: StackError> StackError for Box<T> {
126 fn location(&self) -> Location {
127 self.as_ref().location()
128 }
129 fn type_name(&self) -> &'static str {
130 self.as_ref().type_name()
131 }
132 fn stack_source(&self) -> Option<&dyn StackError> {
133 self.as_ref().stack_source()
134 }
135 }
136 impl<T: ?Sized + StackError> StackError for Arc<T> {
138 fn location(&self) -> Location {
139 self.as_ref().location()
140 }
141 fn type_name(&self) -> &'static str {
142 self.as_ref().type_name()
143 }
144 fn stack_source(&self) -> Option<&dyn StackError> {
145 self.as_ref().stack_source()
146 }
147 }
148
149 impl Error for Box<dyn StackError> {
151 fn source(&self) -> Option<&(dyn Error + 'static)> {
152 Error::source(Box::as_ref(self))
153 }
154 }
155
156 impl StackError for Box<dyn StackError> {
158 fn location(&self) -> Location {
159 self.as_ref().location()
160 }
161 fn type_name(&self) -> &'static str {
162 self.as_ref().type_name()
163 }
164 fn stack_source(&self) -> Option<&dyn StackError> {
165 self.as_ref().stack_source()
166 }
167 }
168
169 impl Error for Box<dyn StackError + Send + Sync> {
171 fn source(&self) -> Option<&(dyn Error + 'static)> {
172 Error::source(Box::as_ref(self))
173 }
174 }
175
176 impl StackError for Box<dyn StackError + Send + Sync> {
178 fn location(&self) -> Location {
179 self.as_ref().location()
180 }
181 fn type_name(&self) -> &'static str {
182 self.as_ref().type_name()
183 }
184 fn stack_source(&self) -> Option<&dyn StackError> {
185 self.as_ref().stack_source()
186 }
187 }
188}
189
190#[cfg(all(test, feature = "alloc"))]
191mod tests {
192 use super::*;
195 use crate::StackReport;
196 use alloc::boxed::Box;
197 use alloc::format;
198 use alloc::string::String;
199 use alloc::sync::Arc;
200 use snafu::prelude::*;
201
202 #[derive(Debug, Snafu)]
203 #[snafu(display("Simple test error: {}", message))]
204 struct SimpleError {
205 message: String,
206 #[snafu(implicit)]
207 location: Location,
208 }
209 impl StackError for SimpleError {
210 fn location(&self) -> Location {
211 self.location
212 }
213 fn type_name(&self) -> &'static str {
214 "SimpleError"
215 }
216 }
217
218 #[derive(Debug, Snafu)]
219 #[snafu(display("Wrapper error: {}", message))]
220 struct WrapperError {
221 message: String,
222 source: Box<dyn StackError + Send + Sync>,
223 #[snafu(implicit)]
224 location: Location,
225 }
226 impl StackError for WrapperError {
227 fn location(&self) -> Location {
228 self.location
229 }
230 fn type_name(&self) -> &'static str {
231 "WrapperError"
232 }
233 fn stack_source(&self) -> Option<&dyn StackError> {
234 Some(self.source.as_ref())
236 }
237 }
238
239 #[test]
240 fn test_basic_location() {
241 let error = SimpleSnafu {
242 message: "Something went wrong",
243 }
244 .build();
245 assert_eq!(error.location().file(), file!());
246 assert!(error.location().line() > 0);
247 assert!(format!("{}", error).contains("Simple test error"));
248 assert!(format!("{}", error).contains("Something went wrong"));
249
250 handle_stack_error(error)
251 }
252
253 #[test]
254 fn test_error_boxing() {
255 let concrete_error = SimpleSnafu {
256 message: "Original error",
257 }
258 .build();
259 let boxed_error: Box<dyn StackError> = Box::new(concrete_error);
260
261 assert_eq!(boxed_error.location().file(), file!());
262 assert!(boxed_error.location().line() > 0);
263 assert!(format!("{}", boxed_error).contains("Simple test error"));
264 assert!(format!("{}", boxed_error).contains("Original error"));
265
266 handle_stack_error(boxed_error)
267 }
268
269 #[test]
270 fn test_error_chaining() {
271 fn gen_root_error() -> Result<(), Box<dyn StackError + Send + Sync + 'static>> {
272 let root_error = SimpleSnafu {
273 message: "Root cause",
274 }
275 .build();
276 Err(Box::new(root_error))
277 }
278 let root_error = gen_root_error();
279 let root_location = root_error.unwrap_err().location().line();
280
281 let wrapper_error = gen_root_error()
282 .context(WrapperSnafu {
283 message: "Something failed",
284 })
285 .unwrap_err();
286
287 assert!(wrapper_error.location().file().ends_with("stack_error.rs"));
288 assert_ne!(wrapper_error.location().line(), root_location);
289
290 let report = format!("{:?}", StackReport::from(wrapper_error));
291 let file = file!();
292 assert!(report.contains("Error: WrapperError: Wrapper error: Something failed"));
293 assert!(report.contains(&format!(", at {file}:")));
294 assert!(report.contains("Caused by"));
295 assert!(report.contains("1| SimpleError: Simple test error: Root cause"));
296 }
297
298 #[test]
299 fn test_arc_errors() {
300 let error = SimpleSnafu {
301 message: "Arc-wrapped error",
302 }
303 .build();
304 let original_location = error.location().line();
305 let arc_error = Arc::new(error);
306
307 assert_eq!(arc_error.location().line(), original_location);
308
309 let cloned_arc = arc_error.clone();
310 assert_eq!(cloned_arc.location().line(), original_location);
311
312 handle_stack_error(arc_error);
313
314 let arc_error: Arc<dyn StackError> = Arc::new(SimpleSnafu { message: "Simple" }.build());
315 handle_stack_error(arc_error);
316 }
317
318 #[test]
319 fn test_from_implementation() {
320 let concrete_error = SimpleSnafu {
321 message: "Converted error",
322 }
323 .build();
324 let original_location = concrete_error.location().line();
325 let boxed_error: Box<dyn StackError + Send + Sync + 'static> = Box::new(concrete_error);
326
327 assert_eq!(boxed_error.location().line(), original_location);
328 handle_stack_error(boxed_error);
329 }
330
331 #[test]
332 fn test_depth_one() {
333 fn gen_root() -> Result<(), Box<dyn StackError + Send + Sync + 'static>> {
334 let root = SimpleSnafu { message: "root" }.build();
335 Err(Box::new(root))
336 }
337 let wrapper = gen_root()
338 .context(WrapperSnafu { message: "wrapper" })
339 .unwrap_err();
340 assert_eq!(wrapper.depth(), 1);
342 }
343
344 #[test]
345 fn test_box_concrete_stack_error() {
346 let concrete = SimpleSnafu {
348 message: "boxed concrete",
349 }
350 .build();
351 let original_line = concrete.location().line();
352 let boxed: Box<SimpleError> = Box::new(concrete);
353
354 assert_eq!(boxed.location().line(), original_line);
355 assert_eq!(boxed.type_name(), "SimpleError");
356 assert!(boxed.stack_source().is_none());
357 handle_stack_error(boxed);
358 }
359
360 fn handle_stack_error<T: StackError>(_: T) {}
361
362 #[test]
364 fn test_box_dyn_stack_error_non_send_sync() {
365 let concrete = SimpleSnafu {
366 message: "boxed non-send-sync",
367 }
368 .build();
369 let original_line = concrete.location().line();
370 let boxed: Box<dyn StackError> = Box::new(concrete);
371
372 assert_eq!(boxed.location().line(), original_line);
374 assert_eq!(boxed.type_name(), "SimpleError");
375 assert!(boxed.stack_source().is_none());
376
377 let err: &dyn Error = &boxed;
379 assert!(format!("{err}").contains("boxed non-send-sync"));
380 }
381}