studiole_report/
structured_error.rs1use crate::prelude::*;
4
5pub struct StructuredError {
7 pub(crate) context: Box<dyn StdError + Send + Sync + 'static>,
9 code: String,
11 pub(crate) attached: Attached,
13 source: Option<Box<dyn StdError + Send + Sync + 'static>>,
15}
16
17impl StructuredError {
18 pub fn new<T: StdError + Send + Sync + 'static>(context: T) -> Self {
20 let code = short_code(&context);
21 Self {
22 context: Box::new(context),
23 code,
24 attached: Attached::new(),
25 source: None,
26 }
27 }
28
29 #[must_use]
31 pub fn change_context<T: StdError + Send + Sync + 'static>(
32 self,
33 new_context: T,
34 ) -> StructuredError {
35 StructuredError {
36 source: Some(Box::new(self)),
37 ..StructuredError::new(new_context)
38 }
39 }
40
41 #[must_use]
43 pub fn current_context(&self) -> &(dyn StdError + Send + Sync + 'static) {
44 self.context.as_ref()
45 }
46}
47
48impl Display for StructuredError {
49 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
50 Display::fmt(&self.context, f)?;
51 write!(f, "{}", self.attached)?;
52 Ok(())
53 }
54}
55
56impl Debug for StructuredError {
57 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
58 Display::fmt(self, f)?;
59 let mut source = self.source();
60 while let Some(err) = source {
61 write!(f, "\n Caused by: {err}")?;
62 source = err.source();
63 }
64 Ok(())
65 }
66}
67
68impl StdError for StructuredError {
69 #[expect(
70 clippy::as_conversions,
71 reason = "cast from boxed trait object to trait reference"
72 )]
73 fn source(&self) -> Option<&(dyn StdError + 'static)> {
74 self.source
75 .as_ref()
76 .map(|s| s.as_ref() as &(dyn StdError + 'static))
77 }
78}
79
80impl Diagnostic for StructuredError {
81 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
82 Some(Box::new(self.code.as_str()))
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn structured_error_display() {
92 let error = StructuredError::new(OuterError::Operation);
94 let display = error.to_string();
96 assert_snapshot!(display, @"Outer operation failed");
98 }
99
100 #[test]
101 fn structured_error_display__with_attach() {
102 let error = StructuredError::new(OuterError::Operation)
104 .attach("path", "/tmp/file.txt")
105 .attach("retries", 3);
106 let display = error.to_string();
108 assert_snapshot!(display);
110 }
111
112 #[test]
113 fn structured_error_debug__with_source() {
114 let inner = StructuredError::new(InnerError::Operation);
116 let outer = inner.change_context(OuterError::Operation);
117 let debug = format!("{outer:?}");
119 assert_snapshot!(debug);
121 }
122
123 #[test]
124 fn structured_error_diagnostic_code() {
125 let error = StructuredError::new(OuterError::Operation);
127 let code = error.code().expect("should have code").to_string();
129 assert_eq!(code, "studiole_report::OuterError::Operation");
131 }
132
133 #[test]
134 fn structured_error_current_context() {
135 let error = StructuredError::new(OuterError::Operation);
137 let context = error.current_context();
139 let downcast = context
141 .downcast_ref::<OuterError>()
142 .expect("should be OuterError");
143 assert_eq!(downcast, &OuterError::Operation);
144 }
145}