1use crate::{core::convert_error, core::DomainReason, StructError};
2
3pub trait ErrorConv<T, R: DomainReason>: Sized {
4 fn err_conv(self) -> Result<T, StructError<R>>;
5}
6
7pub trait ConvStructError<R: DomainReason>: Sized {
8 fn conv(self) -> StructError<R>;
9}
10
11
12pub trait ErrorWrapAs<T, R: DomainReason>: Sized {
13 fn wrap_as(self, reason: R, detail: impl Into<String>) -> Result<T, StructError<R>>;
14}
15
16pub trait WrapStructErrorAs<R: DomainReason>: Sized {
17 fn wrap_as(self, reason: R, detail: impl Into<String>) -> StructError<R>;
18}
19
20impl<T, R1, R2> ErrorConv<T, R2> for Result<T, StructError<R1>>
21where
22 R1: DomainReason,
23 R2: DomainReason + From<R1>,
24{
25 fn err_conv(self) -> Result<T, StructError<R2>> {
26 match self {
27 Ok(o) => Ok(o),
28 Err(e) => Err(convert_error::<R1, R2>(e)),
29 }
30 }
31}
32
33impl<R1, R2> ConvStructError<R2> for StructError<R1>
34where
35 R1: DomainReason,
36 R2: DomainReason + From<R1>,
37{
38 fn conv(self) -> StructError<R2> {
39 convert_error::<R1, R2>(self)
40 }
41}
42
43
44impl<T, R1, R2> ErrorWrapAs<T, R2> for Result<T, StructError<R1>>
45where
46 R1: DomainReason,
47 R2: DomainReason,
48{
49 fn wrap_as(self, reason: R2, detail: impl Into<String>) -> Result<T, StructError<R2>> {
50 let detail = detail.into();
51 self.map_err(|e| e.wrap_as(reason, detail))
52 }
53}
54
55impl<R1, R2> WrapStructErrorAs<R2> for StructError<R1>
56where
57 R1: DomainReason,
58 R2: DomainReason,
59{
60 fn wrap_as(self, reason: R2, detail: impl Into<String>) -> StructError<R2> {
61 self.wrap(reason).with_detail(detail)
62 }
63}
64
65pub trait ToStructError<R>
66where
67 R: DomainReason,
68{
69 fn to_err(self) -> StructError<R>;
70 fn err_result<T>(self) -> Result<T, StructError<R>>;
71}
72impl<R> ToStructError<R> for R
73where
74 R: DomainReason,
75{
76 fn to_err(self) -> StructError<R> {
77 StructError::from(self)
78 }
79 fn err_result<T>(self) -> Result<T, StructError<R>> {
80 Err(StructError::from(self))
81 }
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87 use crate::{core::DomainReason, ErrorCode, OperationContext, StructError, UvsReason};
88
89 #[derive(Debug, Clone, PartialEq, thiserror::Error)]
91 enum TestReason {
92 #[error("test error")]
93 TestError,
94 #[error("{0}")]
95 Uvs(UvsReason),
96 }
97
98 impl ErrorCode for TestReason {
99 fn error_code(&self) -> i32 {
100 match self {
101 TestReason::TestError => 1001,
102 TestReason::Uvs(uvs) => uvs.error_code(),
103 }
104 }
105 }
106
107 impl DomainReason for TestReason {}
108
109 impl From<UvsReason> for TestReason {
110 fn from(uvs: UvsReason) -> Self {
111 TestReason::Uvs(uvs)
112 }
113 }
114
115 #[derive(Debug, Clone, PartialEq, thiserror::Error)]
117 enum AnotherReason {
118 #[error("another error")]
119 AnotherError,
120 #[error("{0}")]
121 Uvs(UvsReason),
122 }
123
124 impl ErrorCode for AnotherReason {
125 fn error_code(&self) -> i32 {
126 match self {
127 AnotherReason::AnotherError => 2001,
128 AnotherReason::Uvs(uvs) => uvs.error_code(),
129 }
130 }
131 }
132
133 impl DomainReason for AnotherReason {}
134
135 impl From<UvsReason> for AnotherReason {
136 fn from(uvs: UvsReason) -> Self {
137 AnotherReason::Uvs(uvs)
138 }
139 }
140
141 impl From<TestReason> for AnotherReason {
142 fn from(test: TestReason) -> Self {
143 match test {
144 TestReason::TestError => AnotherReason::AnotherError,
145 TestReason::Uvs(uvs) => AnotherReason::Uvs(uvs),
146 }
147 }
148 }
149
150 #[test]
151 fn test_error_conv_trait() {
152 let original_result: Result<i32, StructError<TestReason>> =
154 Err(TestReason::TestError.to_err());
155
156 let converted_result: Result<i32, StructError<AnotherReason>> = original_result.err_conv();
157
158 assert!(converted_result.is_err());
159 let converted_error = converted_result.unwrap_err();
160 assert_eq!(converted_error.reason().error_code(), 2001);
161
162 let success_result: Result<i32, StructError<TestReason>> = Ok(42);
164 let converted_success: Result<i32, StructError<AnotherReason>> = success_result.err_conv();
165
166 assert!(converted_success.is_ok());
167 assert_eq!(converted_success.unwrap(), 42);
168 }
169
170 #[test]
171 fn test_conv_struct_error_trait() {
172 let original_error: StructError<TestReason> = TestReason::TestError.to_err();
174
175 let converted_error: StructError<AnotherReason> = original_error.conv();
176
177 assert_eq!(converted_error.reason().error_code(), 2001);
178
179 let uvs_error: StructError<TestReason> =
181 TestReason::Uvs(UvsReason::network_error()).to_err();
182
183 let converted_uvs_error: StructError<AnotherReason> = uvs_error.conv();
184
185 assert_eq!(converted_uvs_error.reason().error_code(), 202);
186 }
187
188 #[test]
189 fn test_to_struct_error_trait() {
190 let reason = TestReason::TestError;
192 let error: StructError<TestReason> = reason.to_err();
193
194 assert_eq!(error.reason().error_code(), 1001);
195
196 let reason2 = TestReason::TestError;
198 let result: Result<String, StructError<TestReason>> = reason2.err_result();
199
200 assert!(result.is_err());
201 let error_from_result = result.unwrap_err();
202 assert_eq!(error_from_result.reason().error_code(), 1001);
203
204 let uvs_reason1 = UvsReason::validation_error();
206 let uvs_error: StructError<UvsReason> = uvs_reason1.to_err();
207
208 assert_eq!(uvs_error.reason().error_code(), 100);
209
210 let uvs_reason2 = UvsReason::validation_error();
211 let uvs_result: Result<i32, StructError<UvsReason>> = uvs_reason2.err_result();
212 assert!(uvs_result.is_err());
213 assert_eq!(uvs_result.unwrap_err().reason().error_code(), 100);
214 }
215
216 #[test]
217 fn test_err_conv_preserves_source() {
218 let source = std::io::Error::other("db unavailable");
219 let original: Result<i32, StructError<TestReason>> =
220 Err(StructError::from(TestReason::TestError).with_std_source(source));
221
222 let converted: Result<i32, StructError<AnotherReason>> = original.err_conv();
223 let err = converted.unwrap_err();
224
225 assert_eq!(err.reason().error_code(), 2001);
226 assert_eq!(err.source_ref().unwrap().to_string(), "db unavailable");
227 }
228
229 #[test]
230 fn test_wrap_as_preserves_previous_struct_error_chain() {
231 let original: Result<i32, StructError<TestReason>> =
232 Err(StructError::from(TestReason::TestError)
233 .with_detail("repo layer failed")
234 .with_std_source(std::io::Error::other("db unavailable")));
235
236 let wrapped: Result<i32, StructError<AnotherReason>> =
237 original.wrap_as(AnotherReason::AnotherError, "service layer failed");
238 let err = wrapped.unwrap_err();
239
240 assert_eq!(err.reason().error_code(), 2001);
241 assert_eq!(err.detail().as_deref(), Some("service layer failed"));
242 assert_eq!(
243 err.source_ref().unwrap().to_string(),
244 "test error\n -> Details: repo layer failed\n -> Source: db unavailable"
245 );
246 assert_eq!(err.root_cause().unwrap().to_string(), "db unavailable");
247 assert_eq!(err.source_chain().len(), 2);
248 assert_eq!(err.source_frames()[0].message, "test error");
249 assert!(err.source_frames()[0]
250 .display
251 .as_ref()
252 .unwrap()
253 .contains("repo layer failed"));
254 assert_eq!(err.source_frames()[0].error_code, None);
255 assert_eq!(err.source_frames()[0].reason.as_deref(), Some("test error"));
256 assert_eq!(
257 err.source_frames()[0].detail.as_deref(),
258 Some("repo layer failed")
259 );
260 assert_eq!(err.source_frames()[1].message, "db unavailable");
261 assert!(err.source_frames()[1].is_root_cause);
262 }
263
264 #[test]
265 fn test_err_conv_preserves_context_metadata() {
266 let original: Result<i32, StructError<TestReason>> =
267 Err(StructError::from(TestReason::TestError).with_context(
268 OperationContext::doing("load sink defaults")
269 .with_meta("config.kind", "sink_defaults"),
270 ));
271
272 let converted: Result<i32, StructError<AnotherReason>> = original.err_conv();
273 let err = converted.unwrap_err();
274
275 assert_eq!(
276 err.context_metadata().get_str("config.kind"),
277 Some("sink_defaults")
278 );
279 }
280
281 #[test]
282 fn test_wrap_as_preserves_source_frame_metadata() {
283 let original: Result<i32, StructError<TestReason>> =
284 Err(StructError::from(TestReason::TestError).with_context(
285 OperationContext::doing("load sink defaults")
286 .with_meta("config.kind", "sink_defaults"),
287 ));
288
289 let wrapped: Result<i32, StructError<AnotherReason>> =
290 original.wrap_as(AnotherReason::AnotherError, "service layer failed");
291 let err = wrapped.unwrap_err();
292
293 assert_eq!(err.detail().as_deref(), Some("service layer failed"));
294 assert_eq!(
295 err.source_frames()[0].metadata.get_str("config.kind"),
296 Some("sink_defaults")
297 );
298 }
299
300 #[test]
301 fn test_wrap_as_preserves_detail_source_chain_and_metadata() {
302 let original: Result<i32, StructError<TestReason>> =
303 Err(StructError::from(TestReason::TestError)
304 .with_detail("repo layer failed")
305 .with_context(
306 OperationContext::doing("load sink defaults")
307 .with_meta("config.kind", "sink_defaults"),
308 )
309 .with_std_source(std::io::Error::other("db unavailable")));
310
311 let wrapped: Result<i32, StructError<AnotherReason>> =
312 original.wrap_as(AnotherReason::AnotherError, "service layer failed");
313 let err = wrapped.unwrap_err();
314
315 assert_eq!(err.reason().error_code(), 2001);
316 assert_eq!(err.detail().as_deref(), Some("service layer failed"));
317 assert!(err.source_ref().unwrap().to_string().contains("test error"));
318 assert_eq!(err.root_cause().unwrap().to_string(), "db unavailable");
319 assert_eq!(err.source_chain().len(), 2);
320 assert_eq!(err.source_frames()[0].message, "test error");
321 assert_eq!(err.source_frames()[0].error_code, None);
322 assert_eq!(
323 err.source_frames()[0].detail.as_deref(),
324 Some("repo layer failed")
325 );
326 assert_eq!(
327 err.source_frames()[0].metadata.get_str("config.kind"),
328 Some("sink_defaults")
329 );
330 assert_eq!(err.source_frames()[1].message, "db unavailable");
331 assert!(err.source_frames()[1].is_root_cause);
332 }
333}