Skip to main content

orion_error/traits/
conversion.rs

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    // 定义测试用的 DomainReason
90    #[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    // 定义另一个 DomainReason 用于测试转换
116    #[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        // 测试 ErrorConv trait 的 err_conv 方法
153        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        // 测试成功情况下的转换
163        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        // 测试 ConvStructError trait 的 conv 方法
173        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        // 测试带有 UvsReason 的转换
180        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        // 测试 ToStructError trait 的 to_err 方法
191        let reason = TestReason::TestError;
192        let error: StructError<TestReason> = reason.to_err();
193
194        assert_eq!(error.reason().error_code(), 1001);
195
196        // 测试 ToStructError trait 的 err_result 方法
197        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        // 测试使用 UvsReason
205        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}