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