Skip to main content

orion_error/traits/
conversion.rs

1use crate::{core::convert_error, core::DomainReason, StructError};
2
3/// Convert a `Result<T, StructError<R1>>` into `Result<T, StructError<R2>>`.
4///
5/// Requires `R2: From<R1>`. Preserves all detail, position, context, and source state.
6///
7/// # Example
8///
9/// ```rust,ignore
10/// // This example requires the `derive` feature (enabled by default).
11/// use orion_error::prelude::*;
12/// use orion_error::UnifiedReason;
13///
14/// #[derive(Debug, Clone, PartialEq, OrionError)]
15/// enum SubReason {
16///     #[orion_error(identity = "biz.sub")]
17///     Sub,
18/// }
19///
20/// #[derive(Debug, Clone, PartialEq, OrionError)]
21/// enum MainReason {
22///     #[orion_error(identity = "biz.main")]
23///     Main,
24/// }
25///
26/// impl From<SubReason> for MainReason {
27///     fn from(_: SubReason) -> Self { MainReason::Main }
28/// }
29///
30/// // conv_err converts StructError<SubReason> → StructError<MainReason>
31/// let result: Result<(), StructError<SubReason>> =
32///     Err(StructError::from(SubReason::Sub).with_detail("inner"));
33/// let converted: Result<(), StructError<MainReason>> = result.conv_err();
34/// assert!(converted.is_err());
35/// ```
36///
37/// # Design note
38///
39/// A blanket `From<StructError<R1>> for StructError<R2>` is blocked by Rust's
40/// orphan rule: neither `From` (std) nor `StructError` (orion-error) are local
41/// to the user's crate. An explicit trait method is the intended path forward.
42pub trait ConvErr<T, R: DomainReason>: Sized {
43    fn conv_err(self) -> Result<T, StructError<R>>;
44
45    /// Deprecated: use [`conv_err`](Self::conv_err) instead.
46    #[deprecated(since = "0.9.0", note = "renamed to conv_err")]
47    fn err_conv(self) -> Result<T, StructError<R>> {
48        self.conv_err()
49    }
50}
51
52/// Convert a `StructError<R1>` into `StructError<R2>`.
53///
54/// Requires `R2: From<R1>`. Preserves all detail, position, context, and source state.
55pub trait ConvStructError<R: DomainReason>: Sized {
56    fn conv(self) -> StructError<R>;
57}
58
59impl<T, R1, R2> ConvErr<T, R2> for Result<T, StructError<R1>>
60where
61    R1: DomainReason,
62    R2: DomainReason + From<R1>,
63{
64    fn conv_err(self) -> Result<T, StructError<R2>> {
65        match self {
66            Ok(o) => Ok(o),
67            Err(e) => Err(convert_error::<R1, R2>(e)),
68        }
69    }
70}
71
72impl<R1, R2> ConvStructError<R2> for StructError<R1>
73where
74    R1: DomainReason,
75    R2: DomainReason + From<R1>,
76{
77    fn conv(self) -> StructError<R2> {
78        convert_error::<R1, R2>(self)
79    }
80}
81
82/// Convenience to wrap any [`DomainReason`] value into a [`StructError`].
83///
84/// Provides both direct `to_err()` and `err_result::<T>()` via blanket impl
85/// for all `R: DomainReason`.
86pub trait ToStructError<R>
87where
88    R: DomainReason,
89{
90    fn to_err(self) -> StructError<R>;
91    fn err_result<T>(self) -> Result<T, StructError<R>>;
92}
93impl<R> ToStructError<R> for R
94where
95    R: DomainReason,
96{
97    fn to_err(self) -> StructError<R> {
98        StructError::from(self)
99    }
100    fn err_result<T>(self) -> Result<T, StructError<R>> {
101        Err(StructError::from(self))
102    }
103}
104
105#[cfg(test)]
106mod tests {
107    use super::*;
108    use crate::reason::ErrorCode;
109    use crate::{core::DomainReason, OperationContext, StructError, UnifiedReason};
110
111    // 定义测试用的 DomainReason
112    #[derive(Debug, Clone, PartialEq, thiserror::Error)]
113    enum TestReason {
114        #[error("test error")]
115        TestError,
116        #[error("{0}")]
117        General(UnifiedReason),
118    }
119
120    impl ErrorCode for TestReason {
121        fn error_code(&self) -> i32 {
122            match self {
123                TestReason::TestError => 1001,
124                TestReason::General(uvs) => uvs.error_code(),
125            }
126        }
127    }
128
129    impl DomainReason for TestReason {}
130
131    impl From<UnifiedReason> for TestReason {
132        fn from(uvs: UnifiedReason) -> Self {
133            TestReason::General(uvs)
134        }
135    }
136
137    // 定义另一个 DomainReason 用于测试转换
138    #[derive(Debug, Clone, PartialEq, thiserror::Error)]
139    enum AnotherReason {
140        #[error("another error")]
141        AnotherError,
142        #[error("{0}")]
143        General(UnifiedReason),
144    }
145
146    impl ErrorCode for AnotherReason {
147        fn error_code(&self) -> i32 {
148            match self {
149                AnotherReason::AnotherError => 2001,
150                AnotherReason::General(uvs) => uvs.error_code(),
151            }
152        }
153    }
154
155    impl DomainReason for AnotherReason {}
156
157    impl From<UnifiedReason> for AnotherReason {
158        fn from(uvs: UnifiedReason) -> Self {
159            AnotherReason::General(uvs)
160        }
161    }
162
163    impl From<TestReason> for AnotherReason {
164        fn from(test: TestReason) -> Self {
165            match test {
166                TestReason::TestError => AnotherReason::AnotherError,
167                TestReason::General(uvs) => AnotherReason::General(uvs),
168            }
169        }
170    }
171
172    #[test]
173    fn test_error_conv_trait() {
174        // 测试 ErrorConv trait 的 upcast 方法
175        let original_result: Result<i32, StructError<TestReason>> =
176            Err(TestReason::TestError.to_err());
177
178        let converted_result: Result<i32, StructError<AnotherReason>> = original_result.conv_err();
179
180        assert!(converted_result.is_err());
181        let converted_error = converted_result.unwrap_err();
182        assert_eq!(converted_error.reason().error_code(), 2001);
183
184        // 测试成功情况下的转换
185        let success_result: Result<i32, StructError<TestReason>> = Ok(42);
186        let converted_success: Result<i32, StructError<AnotherReason>> = success_result.conv_err();
187
188        assert!(converted_success.is_ok());
189        assert_eq!(converted_success.unwrap(), 42);
190    }
191
192    #[test]
193    fn test_conv_struct_error_trait() {
194        // 测试 ConvStructError trait 的 conv 方法
195        let original_error: StructError<TestReason> = TestReason::TestError.to_err();
196
197        let converted_error: StructError<AnotherReason> = original_error.conv();
198
199        assert_eq!(converted_error.reason().error_code(), 2001);
200
201        // 测试带有 UnifiedReason 的转换
202        let uvs_error: StructError<TestReason> =
203            TestReason::General(UnifiedReason::network_error()).to_err();
204
205        let converted_uvs_error: StructError<AnotherReason> = uvs_error.conv();
206
207        assert_eq!(converted_uvs_error.reason().error_code(), 202);
208    }
209
210    #[test]
211    fn test_to_struct_error_trait() {
212        // 测试 ToStructError trait 的 to_err 方法
213        let reason = TestReason::TestError;
214        let error: StructError<TestReason> = reason.to_err();
215
216        assert_eq!(error.reason().error_code(), 1001);
217
218        // 测试 ToStructError trait 的 err_result 方法
219        let reason2 = TestReason::TestError;
220        let result: Result<String, StructError<TestReason>> = reason2.err_result();
221
222        assert!(result.is_err());
223        let error_from_result = result.unwrap_err();
224        assert_eq!(error_from_result.reason().error_code(), 1001);
225
226        // 测试使用 UnifiedReason
227        let uvs_reason1 = UnifiedReason::validation_error();
228        let uvs_error: StructError<UnifiedReason> = uvs_reason1.to_err();
229
230        assert_eq!(uvs_error.reason().error_code(), 100);
231
232        let uvs_reason2 = UnifiedReason::validation_error();
233        let uvs_result: Result<i32, StructError<UnifiedReason>> = uvs_reason2.err_result();
234        assert!(uvs_result.is_err());
235        assert_eq!(uvs_result.unwrap_err().reason().error_code(), 100);
236    }
237
238    #[test]
239    fn test_upcast_preserves_source() {
240        let source = std::io::Error::other("db unavailable");
241        let original: Result<i32, StructError<TestReason>> =
242            Err(StructError::from(TestReason::TestError).with_std_source(source));
243
244        let converted: Result<i32, StructError<AnotherReason>> = original.conv_err();
245        let err = converted.unwrap_err();
246
247        assert_eq!(err.reason().error_code(), 2001);
248        assert_eq!(err.source_ref().unwrap().to_string(), "db unavailable");
249    }
250
251    #[test]
252    fn test_upcast_preserves_context_metadata() {
253        let original: Result<i32, StructError<TestReason>> =
254            Err(StructError::from(TestReason::TestError).with_context(
255                OperationContext::doing("load sink defaults")
256                    .with_meta("config.kind", "sink_defaults"),
257            ));
258
259        let converted: Result<i32, StructError<AnotherReason>> = original.conv_err();
260        let err = converted.unwrap_err();
261
262        assert_eq!(
263            err.context_metadata().get_str("config.kind"),
264            Some("sink_defaults")
265        );
266    }
267}