Skip to main content

orion_error/traits/
conversion.rs

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    // 定义测试用的 DomainReason
117    #[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    // 定义另一个 DomainReason 用于测试转换
143    #[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        // 测试 ErrorConv trait 的 err_conv 方法
180        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        // 测试成功情况下的转换
190        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        // 测试 ConvStructError trait 的 conv 方法
200        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        // 测试带有 UvsReason 的转换
207        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        // 测试 ToStructError trait 的 to_err 方法
218        let reason = TestReason::TestError;
219        let error: StructError<TestReason> = reason.to_err();
220
221        assert_eq!(error.error_code(), 1001);
222
223        // 测试 ToStructError trait 的 err_result 方法
224        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        // 测试使用 UvsReason
232        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}