studiole_report/
report.rs1use crate::prelude::*;
3use std::marker::PhantomData;
4use std::ops::Deref;
5
6pub struct Report<T> {
8 pub(crate) inner: StructuredError,
10 _marker: PhantomData<T>,
12}
13
14impl<T: StdError + Send + Sync + 'static> Report<T> {
15 pub fn new(context: T) -> Self {
17 Self {
18 inner: StructuredError::new(context),
19 _marker: PhantomData,
20 }
21 }
22
23 pub(crate) fn from_inner(inner: StructuredError) -> Self {
25 Self {
26 inner,
27 _marker: PhantomData,
28 }
29 }
30
31 #[must_use]
33 pub fn current_context(&self) -> &T {
34 self.inner
35 .context
36 .downcast_ref::<T>()
37 .expect("Report<T> inner context should be of type T")
38 }
39
40 pub fn change_context<U: StdError + Send + Sync + 'static>(self, new_context: U) -> Report<U> {
42 Report {
43 inner: self.inner.change_context(new_context),
44 _marker: PhantomData,
45 }
46 }
47}
48
49impl<T: StdError + Send + Sync + 'static> From<Report<T>> for StructuredError {
50 fn from(report: Report<T>) -> Self {
51 report.inner
52 }
53}
54
55impl<T: StdError + Send + Sync + 'static> Deref for Report<T> {
56 type Target = StructuredError;
57
58 fn deref(&self) -> &StructuredError {
59 &self.inner
60 }
61}
62
63impl<T: StdError + Send + Sync + 'static> From<T> for Report<T> {
64 fn from(error: T) -> Self {
65 Self::new(error)
66 }
67}
68
69impl<T: StdError + Send + Sync + 'static> Debug for Report<T> {
70 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
71 Debug::fmt(&self.inner, f)
72 }
73}
74
75impl<T: StdError + Send + Sync + 'static> Display for Report<T> {
76 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
77 Display::fmt(&self.inner, f)
78 }
79}
80
81impl<T: StdError + Send + Sync + 'static> StdError for Report<T> {
82 fn source(&self) -> Option<&(dyn StdError + 'static)> {
83 self.inner.source()
84 }
85}
86
87impl<T: StdError + Send + Sync + 'static> Diagnostic for Report<T> {
88 fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
89 self.inner.code()
90 }
91}
92
93#[cfg(test)]
94mod tests {
95 use super::*;
96
97 #[test]
98 fn report_display() {
99 let report = Report::new(OuterError::Operation);
101 let display = report.to_string();
103 assert_snapshot!(display, @"Outer operation failed");
105 }
106
107 #[test]
108 fn report_display__with_attach() {
109 let report = Report::new(OuterError::Operation)
111 .attach("path", "/tmp/file.txt")
112 .attach("retries", 3);
113 let display = report.to_string();
115 assert_snapshot!(display);
117 }
118
119 #[test]
120 fn report_display__with_attach_with() {
121 let report = Report::new(OuterError::Operation).attach_with("count", || 42);
123 let display = report.to_string();
125 assert_snapshot!(display);
127 }
128
129 #[test]
130 fn report_debug() {
131 let report = Report::new(OuterError::Operation);
133 let debug = format!("{report:?}");
135 assert_snapshot!(debug, @"Outer operation failed");
137 }
138
139 #[test]
140 fn report_debug__with_source() {
141 let inner = Report::new(InnerError::Operation);
143 let outer = inner.change_context(OuterError::Operation);
144 let debug = format!("{outer:?}");
146 assert_snapshot!(debug);
148 }
149
150 #[test]
151 fn report_debug__with_additional_and_source() {
152 let inner = Report::new(InnerError::Operation).attach("key", "inner_val");
154 let outer = inner
155 .change_context(OuterError::Operation)
156 .attach("key", "outer_val");
157 let debug = format!("{outer:?}");
159 assert_snapshot!(debug);
161 }
162
163 #[test]
164 fn report_source__none_without_wrapping() {
165 let report = Report::new(OuterError::Operation);
167 let source = report.source();
169 assert!(source.is_none());
171 }
172
173 #[test]
174 fn report_source__set_after_change_context() {
175 let inner = Report::new(InnerError::Operation);
177 let outer = inner.change_context(OuterError::Operation);
178 let source = outer.source().expect("should have source");
180 assert_eq!(source.to_string(), "Inner operation failed");
182 }
183
184 #[test]
185 fn report_change_context__preserves_context() {
186 let inner = Report::new(InnerError::Operation);
188 let outer = inner.change_context(OuterError::Operation);
190 assert_eq!(*outer.current_context(), OuterError::Operation);
192 }
193
194 #[test]
195 fn report_change_context__clears_additional() {
196 let inner = Report::new(InnerError::Operation).attach("key", "value");
198 let outer = inner.change_context(OuterError::Operation);
200 assert_eq!(outer.inner.attached.len(), 0);
202 }
203
204 #[test]
205 fn report_attach_path() {
206 let report = Report::new(OuterError::Operation).attach_path("/tmp/data.bin");
208 let display = report.to_string();
210 assert_snapshot!(display);
212 }
213
214 #[test]
215 fn report_from__converts_error_via_question_mark() {
216 fn fallible() -> Result<(), OuterError> {
218 Err(OuterError::Operation)
219 }
220 fn wrapper() -> Result<(), Report<OuterError>> {
221 fallible()?;
222 Ok(())
223 }
224 let report = wrapper().expect_err("should be err");
226 assert_eq!(*report.current_context(), OuterError::Operation);
228 assert!(report.source().is_none());
229 }
230
231 #[test]
232 fn structured_error_from() {
233 let report = Report::new(OuterError::Operation).attach("key", "value");
235 let error = StructuredError::from(report);
237 assert_eq!(error.to_string(), "Outer operation failed\n▷ key: value");
239 }
240}