1use crate::{core::convert_error, 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 std::error::Error as StdError;
114
115 use super::*;
116 use crate::{ErrorCode, ErrorWith, OperationContext, StructError, UvsReason};
117
118 #[derive(Debug, Clone, PartialEq, thiserror::Error)]
120 enum TestReason {
121 #[error("test error")]
122 TestError,
123 #[error("{0}")]
124 Uvs(UvsReason),
125 }
126
127 impl ErrorCode for TestReason {
128 fn error_code(&self) -> i32 {
129 match self {
130 TestReason::TestError => 1001,
131 TestReason::Uvs(uvs) => uvs.error_code(),
132 }
133 }
134 }
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 From<UvsReason> for AnotherReason {
161 fn from(uvs: UvsReason) -> Self {
162 AnotherReason::Uvs(uvs)
163 }
164 }
165
166 impl From<TestReason> for AnotherReason {
167 fn from(test: TestReason) -> Self {
168 match test {
169 TestReason::TestError => AnotherReason::AnotherError,
170 TestReason::Uvs(uvs) => AnotherReason::Uvs(uvs),
171 }
172 }
173 }
174
175 #[test]
176 fn test_error_conv_trait() {
177 let original_result: Result<i32, StructError<TestReason>> =
179 Err(TestReason::TestError.to_err());
180
181 let converted_result: Result<i32, StructError<AnotherReason>> = original_result.err_conv();
182
183 assert!(converted_result.is_err());
184 let converted_error = converted_result.unwrap_err();
185 assert_eq!(converted_error.error_code(), 2001);
186
187 let success_result: Result<i32, StructError<TestReason>> = Ok(42);
189 let converted_success: Result<i32, StructError<AnotherReason>> = success_result.err_conv();
190
191 assert!(converted_success.is_ok());
192 assert_eq!(converted_success.unwrap(), 42);
193 }
194
195 #[test]
196 fn test_conv_struct_error_trait() {
197 let original_error: StructError<TestReason> = TestReason::TestError.to_err();
199
200 let converted_error: StructError<AnotherReason> = original_error.conv();
201
202 assert_eq!(converted_error.error_code(), 2001);
203
204 let uvs_error: StructError<TestReason> =
206 TestReason::Uvs(UvsReason::network_error()).to_err();
207
208 let converted_uvs_error: StructError<AnotherReason> = uvs_error.conv();
209
210 assert_eq!(converted_uvs_error.error_code(), 202);
211 }
212
213 #[test]
214 fn test_to_struct_error_trait() {
215 let reason = TestReason::TestError;
217 let error: StructError<TestReason> = reason.to_err();
218
219 assert_eq!(error.error_code(), 1001);
220
221 let reason2 = TestReason::TestError;
223 let result: Result<String, StructError<TestReason>> = reason2.err_result();
224
225 assert!(result.is_err());
226 let error_from_result = result.unwrap_err();
227 assert_eq!(error_from_result.error_code(), 1001);
228
229 let uvs_reason1 = UvsReason::validation_error();
231 let uvs_error: StructError<UvsReason> = uvs_reason1.to_err();
232
233 assert_eq!(uvs_error.error_code(), 100);
234
235 let uvs_reason2 = UvsReason::validation_error();
236 let uvs_result: Result<i32, StructError<UvsReason>> = uvs_reason2.err_result();
237 assert!(uvs_result.is_err());
238 assert_eq!(uvs_result.unwrap_err().error_code(), 100);
239 }
240
241 #[test]
242 fn test_err_conv_preserves_source() {
243 let source = std::io::Error::other("db unavailable");
244 let original: Result<i32, StructError<TestReason>> =
245 Err(StructError::from(TestReason::TestError).with_source(source));
246
247 let converted: Result<i32, StructError<AnotherReason>> = original.err_conv();
248 let err = converted.unwrap_err();
249
250 assert_eq!(err.error_code(), 2001);
251 assert_eq!(
252 StdError::source(&err).unwrap().to_string(),
253 "db unavailable"
254 );
255 }
256
257 #[test]
258 fn test_err_wrap_preserves_previous_struct_error_chain() {
259 let original: Result<i32, StructError<TestReason>> =
260 Err(StructError::from(TestReason::TestError)
261 .with_detail("repo layer failed")
262 .with_source(std::io::Error::other("db unavailable")));
263
264 let wrapped: Result<i32, StructError<AnotherReason>> =
265 original.err_wrap(AnotherReason::AnotherError);
266 let err = wrapped.unwrap_err();
267
268 assert_eq!(err.error_code(), 2001);
269 assert_eq!(
270 StdError::source(&err).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(
295 OperationContext::want("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_err_wrap_preserves_source_frame_metadata() {
310 let original: Result<i32, StructError<TestReason>> =
311 Err(StructError::from(TestReason::TestError).with(
312 OperationContext::want("load sink defaults")
313 .with_meta("config.kind", "sink_defaults"),
314 ));
315
316 let wrapped: Result<i32, StructError<AnotherReason>> =
317 original.err_wrap(AnotherReason::AnotherError);
318 let err = wrapped.unwrap_err();
319
320 assert_eq!(
321 err.source_frames()[0].metadata.get_str("config.kind"),
322 Some("sink_defaults")
323 );
324 }
325
326 #[test]
327 fn test_wrap_as_preserves_detail_source_chain_and_metadata() {
328 let original: Result<i32, StructError<TestReason>> =
329 Err(StructError::from(TestReason::TestError)
330 .with_detail("repo layer failed")
331 .with(
332 OperationContext::want("load sink defaults")
333 .with_meta("config.kind", "sink_defaults"),
334 )
335 .with_source(std::io::Error::other("db unavailable")));
336
337 let wrapped: Result<i32, StructError<AnotherReason>> =
338 original.wrap_as(AnotherReason::AnotherError, "service layer failed");
339 let err = wrapped.unwrap_err();
340
341 assert_eq!(err.error_code(), 2001);
342 assert_eq!(err.detail().as_deref(), Some("service layer failed"));
343 assert!(StdError::source(&err)
344 .unwrap()
345 .to_string()
346 .contains("test error"));
347 assert_eq!(err.root_cause().unwrap().to_string(), "db unavailable");
348 assert_eq!(err.source_chain().len(), 2);
349 assert_eq!(err.source_frames()[0].message, "test error");
350 assert_eq!(err.source_frames()[0].error_code, Some(1001));
351 assert_eq!(
352 err.source_frames()[0].detail.as_deref(),
353 Some("repo layer failed")
354 );
355 assert_eq!(
356 err.source_frames()[0].metadata.get_str("config.kind"),
357 Some("sink_defaults")
358 );
359 assert_eq!(err.source_frames()[1].message, "db unavailable");
360 assert!(err.source_frames()[1].is_root_cause);
361 }
362}