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
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
43impl<T, R1, R2> ErrorWrap<T, R2> for Result<T, StructError<R1>>
44where
45 R1: DomainReason + ErrorCode + Display + std::fmt::Debug + Send + Sync + 'static,
46 R2: DomainReason,
47{
48 fn err_wrap(self, reason: R2) -> Result<T, StructError<R2>> {
49 self.map_err(|e| e.wrap(reason))
50 }
51}
52
53impl<R1, R2> WrapStructError<R2> for StructError<R1>
54where
55 R1: DomainReason + ErrorCode + Display + std::fmt::Debug + Send + Sync + 'static,
56 R2: DomainReason,
57{
58 fn wrap(self, reason: R2) -> StructError<R2> {
59 StructError::from(reason).with_struct_source(self)
60 }
61}
62
63pub trait ToStructError<R>
64where
65 R: DomainReason,
66{
67 fn to_err(self) -> StructError<R>;
68 fn err_result<T>(self) -> Result<T, StructError<R>>;
69}
70impl<R> ToStructError<R> for R
71where
72 R: DomainReason,
73{
74 fn to_err(self) -> StructError<R> {
75 StructError::from(self)
76 }
77 fn err_result<T>(self) -> Result<T, StructError<R>> {
78 Err(StructError::from(self))
79 }
80}
81
82#[cfg(test)]
83mod tests {
84 use std::error::Error as StdError;
85
86 use super::*;
87 use crate::{ErrorCode, ErrorWith, 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 From<UvsReason> for TestReason {
108 fn from(uvs: UvsReason) -> Self {
109 TestReason::Uvs(uvs)
110 }
111 }
112
113 #[derive(Debug, Clone, PartialEq, thiserror::Error)]
115 enum AnotherReason {
116 #[error("another error")]
117 AnotherError,
118 #[error("{0}")]
119 Uvs(UvsReason),
120 }
121
122 impl ErrorCode for AnotherReason {
123 fn error_code(&self) -> i32 {
124 match self {
125 AnotherReason::AnotherError => 2001,
126 AnotherReason::Uvs(uvs) => uvs.error_code(),
127 }
128 }
129 }
130
131 impl From<UvsReason> for AnotherReason {
132 fn from(uvs: UvsReason) -> Self {
133 AnotherReason::Uvs(uvs)
134 }
135 }
136
137 impl From<TestReason> for AnotherReason {
138 fn from(test: TestReason) -> Self {
139 match test {
140 TestReason::TestError => AnotherReason::AnotherError,
141 TestReason::Uvs(uvs) => AnotherReason::Uvs(uvs),
142 }
143 }
144 }
145
146 #[test]
147 fn test_error_conv_trait() {
148 let original_result: Result<i32, StructError<TestReason>> =
150 Err(TestReason::TestError.to_err());
151
152 let converted_result: Result<i32, StructError<AnotherReason>> = original_result.err_conv();
153
154 assert!(converted_result.is_err());
155 let converted_error = converted_result.unwrap_err();
156 assert_eq!(converted_error.error_code(), 2001);
157
158 let success_result: Result<i32, StructError<TestReason>> = Ok(42);
160 let converted_success: Result<i32, StructError<AnotherReason>> = success_result.err_conv();
161
162 assert!(converted_success.is_ok());
163 assert_eq!(converted_success.unwrap(), 42);
164 }
165
166 #[test]
167 fn test_conv_struct_error_trait() {
168 let original_error: StructError<TestReason> = TestReason::TestError.to_err();
170
171 let converted_error: StructError<AnotherReason> = original_error.conv();
172
173 assert_eq!(converted_error.error_code(), 2001);
174
175 let uvs_error: StructError<TestReason> =
177 TestReason::Uvs(UvsReason::network_error()).to_err();
178
179 let converted_uvs_error: StructError<AnotherReason> = uvs_error.conv();
180
181 assert_eq!(converted_uvs_error.error_code(), 202);
182 }
183
184 #[test]
185 fn test_to_struct_error_trait() {
186 let reason = TestReason::TestError;
188 let error: StructError<TestReason> = reason.to_err();
189
190 assert_eq!(error.error_code(), 1001);
191
192 let reason2 = TestReason::TestError;
194 let result: Result<String, StructError<TestReason>> = reason2.err_result();
195
196 assert!(result.is_err());
197 let error_from_result = result.unwrap_err();
198 assert_eq!(error_from_result.error_code(), 1001);
199
200 let uvs_reason1 = UvsReason::validation_error();
202 let uvs_error: StructError<UvsReason> = uvs_reason1.to_err();
203
204 assert_eq!(uvs_error.error_code(), 100);
205
206 let uvs_reason2 = UvsReason::validation_error();
207 let uvs_result: Result<i32, StructError<UvsReason>> = uvs_reason2.err_result();
208 assert!(uvs_result.is_err());
209 assert_eq!(uvs_result.unwrap_err().error_code(), 100);
210 }
211
212 #[test]
213 fn test_err_conv_preserves_source() {
214 let source = std::io::Error::other("db unavailable");
215 let original: Result<i32, StructError<TestReason>> =
216 Err(StructError::from(TestReason::TestError).with_source(source));
217
218 let converted: Result<i32, StructError<AnotherReason>> = original.err_conv();
219 let err = converted.unwrap_err();
220
221 assert_eq!(err.error_code(), 2001);
222 assert_eq!(
223 StdError::source(&err).unwrap().to_string(),
224 "db unavailable"
225 );
226 }
227
228 #[test]
229 fn test_err_wrap_preserves_previous_struct_error_chain() {
230 let original: Result<i32, StructError<TestReason>> =
231 Err(StructError::from(TestReason::TestError)
232 .with_detail("repo layer failed")
233 .with_source(std::io::Error::other("db unavailable")));
234
235 let wrapped: Result<i32, StructError<AnotherReason>> =
236 original.err_wrap(AnotherReason::AnotherError);
237 let err = wrapped.unwrap_err();
238
239 assert_eq!(err.error_code(), 2001);
240 assert_eq!(
241 StdError::source(&err).unwrap().to_string(),
242 "[1001] test error\n -> Details: repo layer failed\n -> Source: db unavailable"
243 );
244 assert_eq!(err.root_cause().unwrap().to_string(), "db unavailable");
245 assert_eq!(err.source_chain().len(), 2);
246 assert_eq!(err.source_frames()[0].message, "test error");
247 assert!(err.source_frames()[0]
248 .display
249 .as_ref()
250 .unwrap()
251 .contains("repo layer failed"));
252 assert_eq!(err.source_frames()[0].error_code, Some(1001));
253 assert_eq!(err.source_frames()[0].reason.as_deref(), Some("test error"));
254 assert_eq!(
255 err.source_frames()[0].detail.as_deref(),
256 Some("repo layer failed")
257 );
258 assert_eq!(err.source_frames()[1].message, "db unavailable");
259 assert!(err.source_frames()[1].is_root_cause);
260 }
261
262 #[test]
263 fn test_err_conv_preserves_context_metadata() {
264 let original: Result<i32, StructError<TestReason>> =
265 Err(StructError::from(TestReason::TestError).with(
266 OperationContext::want("load sink defaults")
267 .with_meta("config.kind", "sink_defaults"),
268 ));
269
270 let converted: Result<i32, StructError<AnotherReason>> = original.err_conv();
271 let err = converted.unwrap_err();
272
273 assert_eq!(
274 err.context_metadata().get_str("config.kind"),
275 Some("sink_defaults")
276 );
277 }
278
279 #[test]
280 fn test_err_wrap_preserves_source_frame_metadata() {
281 let original: Result<i32, StructError<TestReason>> =
282 Err(StructError::from(TestReason::TestError).with(
283 OperationContext::want("load sink defaults")
284 .with_meta("config.kind", "sink_defaults"),
285 ));
286
287 let wrapped: Result<i32, StructError<AnotherReason>> =
288 original.err_wrap(AnotherReason::AnotherError);
289 let err = wrapped.unwrap_err();
290
291 assert_eq!(
292 err.source_frames()[0].metadata.get_str("config.kind"),
293 Some("sink_defaults")
294 );
295 }
296}