Skip to main content

orion_error/traits/
conversion.rs

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    // 定义测试用的 DomainReason
119    #[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    // 定义另一个 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 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        // 测试 ErrorConv trait 的 err_conv 方法
178        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        // 测试成功情况下的转换
188        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        // 测试 ConvStructError trait 的 conv 方法
198        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        // 测试带有 UvsReason 的转换
205        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        // 测试 ToStructError trait 的 to_err 方法
216        let reason = TestReason::TestError;
217        let error: StructError<TestReason> = reason.to_err();
218
219        assert_eq!(error.error_code(), 1001);
220
221        // 测试 ToStructError trait 的 err_result 方法
222        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        // 测试使用 UvsReason
230        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}