Skip to main content

orion_error/core/
error.rs

1use std::{fmt::Display, ops::Deref, sync::Arc};
2
3use crate::ErrorWith;
4
5use super::{
6    context::{CallContext, OperationContext},
7    domain::DomainReason,
8    ContextAdd, ErrorCode,
9};
10use thiserror::Error;
11
12#[macro_export]
13macro_rules! location {
14    () => {
15        format!("{}:{}:{}", file!(), line!(), column!())
16    };
17}
18
19pub trait StructErrorTrait<T: DomainReason> {
20    fn get_reason(&self) -> &T;
21    fn get_detail(&self) -> Option<&String>;
22    fn get_target(&self) -> Option<String>;
23}
24
25impl<T: DomainReason + ErrorCode> ErrorCode for StructError<T> {
26    fn error_code(&self) -> i32 {
27        self.reason.error_code()
28    }
29}
30
31/// Structured error type containing detailed error information
32/// including error source, contextual data, and debugging information.
33#[derive(Error, Debug, Clone, PartialEq)]
34pub struct StructError<T: DomainReason> {
35    imp: Box<StructErrorImpl<T>>,
36}
37
38#[cfg(feature = "serde")]
39impl<T: DomainReason> serde::Serialize for StructError<T>
40where
41    StructErrorImpl<T>: serde::Serialize,
42{
43    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
44    where
45        S: serde::Serializer,
46    {
47        self.imp.serialize(serializer)
48    }
49}
50
51impl<T: DomainReason> StructError<T> {
52    pub fn imp(&self) -> &StructErrorImpl<T> {
53        &self.imp
54    }
55}
56
57impl<T: DomainReason> Deref for StructError<T> {
58    type Target = StructErrorImpl<T>;
59
60    fn deref(&self) -> &Self::Target {
61        &self.imp
62    }
63}
64impl<T: DomainReason> StructError<T> {
65    pub fn new(
66        reason: T,
67        detail: Option<String>,
68        position: Option<String>,
69        context: Vec<OperationContext>,
70    ) -> Self {
71        StructError {
72            imp: Box::new(StructErrorImpl {
73                reason,
74                detail,
75                position,
76                context: Arc::new(context),
77            }),
78        }
79    }
80}
81
82impl<T> From<T> for StructError<T>
83where
84    T: DomainReason,
85{
86    fn from(value: T) -> Self {
87        StructError::new(value, None, None, Vec::new())
88    }
89}
90
91#[derive(Error, Debug, Clone, PartialEq)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize))]
93pub struct StructErrorImpl<T: DomainReason> {
94    reason: T,
95    detail: Option<String>,
96    position: Option<String>,
97    context: Arc<Vec<OperationContext>>,
98}
99
100impl<T: DomainReason> StructErrorImpl<T> {
101    pub fn reason(&self) -> &T {
102        &self.reason
103    }
104
105    pub fn detail(&self) -> &Option<String> {
106        &self.detail
107    }
108
109    pub fn position(&self) -> &Option<String> {
110        &self.position
111    }
112
113    pub fn context(&self) -> &Arc<Vec<OperationContext>> {
114        &self.context
115    }
116}
117
118pub fn convert_error<R1, R2>(other: StructError<R1>) -> StructError<R2>
119where
120    R1: DomainReason,
121    R2: DomainReason + From<R1>,
122{
123    StructError::new(
124        other.imp.reason.into(),
125        other.imp.detail,
126        other.imp.position,
127        Arc::try_unwrap(other.imp.context).unwrap_or_else(|arc| (*arc).clone()),
128    )
129}
130
131impl<T: DomainReason> StructError<T> {
132    pub fn builder(reason: T) -> StructErrorBuilder<T> {
133        StructErrorBuilder {
134            reason,
135            detail: None,
136            position: None,
137            contexts: Vec::new(),
138        }
139    }
140
141    /// 使用示例
142    ///self.with_position(location!());
143    #[must_use]
144    pub fn with_position(mut self, position: impl Into<String>) -> Self {
145        self.imp.position = Some(position.into());
146        self
147    }
148    #[must_use]
149    pub fn with_context(mut self, context: CallContext) -> Self {
150        Arc::make_mut(&mut self.imp.context).push(OperationContext::from(context));
151        self
152    }
153
154    pub fn contexts(&self) -> &[OperationContext] {
155        self.imp.context.as_ref()
156    }
157
158    // 提供修改方法
159    #[must_use]
160    pub fn with_detail(mut self, detail: impl Into<String>) -> Self {
161        self.imp.detail = Some(detail.into());
162        self
163    }
164    pub fn err<V>(self) -> Result<V, Self> {
165        Err(self)
166    }
167    pub fn target(&self) -> Option<String> {
168        self.context.first().and_then(|x| x.target().clone())
169    }
170}
171
172impl<T: DomainReason> StructErrorTrait<T> for StructError<T> {
173    fn get_reason(&self) -> &T {
174        &self.reason
175    }
176
177    fn get_detail(&self) -> Option<&String> {
178        self.detail.as_ref()
179    }
180
181    fn get_target(&self) -> Option<String> {
182        self.target()
183    }
184}
185
186/*
187impl<S1: Into<String>, S2: Into<String>, T: DomainReason> ContextAdd<(S1, S2)> for StructError<T> {
188    fn add_context(&mut self, val: (S1, S2)) {
189        self.imp.context.items.push((val.0.into(), val.1.into()));
190    }
191}
192*/
193
194impl<T: DomainReason> ContextAdd<&OperationContext> for StructError<T> {
195    fn add_context(&mut self, ctx: &OperationContext) {
196        Arc::make_mut(&mut self.imp.context).push(ctx.clone());
197    }
198}
199impl<T: DomainReason> ContextAdd<OperationContext> for StructError<T> {
200    fn add_context(&mut self, ctx: OperationContext) {
201        Arc::make_mut(&mut self.imp.context).push(ctx);
202    }
203}
204
205impl<T: std::fmt::Display + DomainReason + ErrorCode> Display for StructError<T> {
206    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
207        // 核心错误信息
208        write!(f, "[{}] {reason}", self.error_code(), reason = self.reason)?;
209
210        // 位置信息优先显示
211        if let Some(pos) = &self.position {
212            write!(f, "\n  -> At: {pos}")?;
213        }
214
215        // 目标资源信息
216        if let Some(target) = &self.target() {
217            write!(f, "\n  -> Want: {target}")?;
218        }
219
220        // 技术细节
221        if let Some(detail) = &self.detail {
222            write!(f, "\n  -> Details: {detail}")?;
223        }
224
225        // 上下文信息
226        if !self.context.is_empty() {
227            writeln!(f, "\n  -> Context stack:")?;
228
229            for (i, c) in self.context.iter().enumerate() {
230                writeln!(f, "context {i}: ")?;
231                writeln!(f, "{c}")?;
232            }
233        }
234
235        Ok(())
236    }
237}
238
239pub struct StructErrorBuilder<T: DomainReason> {
240    reason: T,
241    detail: Option<String>,
242    position: Option<String>,
243    contexts: Vec<OperationContext>,
244}
245
246impl<T: DomainReason> StructErrorBuilder<T> {
247    pub fn detail(mut self, detail: impl Into<String>) -> Self {
248        self.detail = Some(detail.into());
249        self
250    }
251
252    pub fn position(mut self, position: impl Into<String>) -> Self {
253        self.position = Some(position.into());
254        self
255    }
256
257    pub fn context(mut self, ctx: OperationContext) -> Self {
258        self.contexts.push(ctx);
259        self
260    }
261
262    pub fn context_ref(mut self, ctx: &OperationContext) -> Self {
263        self.contexts.push(ctx.clone());
264        self
265    }
266
267    pub fn finish(self) -> StructError<T> {
268        StructError::new(self.reason, self.detail, self.position, self.contexts)
269    }
270}
271
272impl<T: DomainReason> ErrorWith for StructError<T> {
273    fn want<S: Into<String>>(mut self, desc: S) -> Self {
274        let desc = desc.into();
275        let ctx_stack = Arc::make_mut(&mut self.imp.context);
276        if ctx_stack.is_empty() {
277            ctx_stack.push(OperationContext::want(desc));
278        } else if let Some(x) = ctx_stack.last_mut() {
279            x.with_want(desc);
280        }
281        self
282    }
283    fn position<S: Into<String>>(mut self, pos: S) -> Self {
284        self.imp.position = Some(pos.into());
285        self
286    }
287
288    fn with<C: Into<OperationContext>>(mut self, ctx: C) -> Self {
289        let ctx = ctx.into();
290        self.add_context(ctx);
291        self
292    }
293}
294
295#[cfg(all(test, feature = "serde"))]
296mod tests {
297
298    use crate::UvsReason;
299
300    use super::*;
301    use derive_more::From;
302
303    // Define a simple DomainReason for testing
304    #[derive(Debug, Clone, PartialEq, Error, From)]
305    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
306    enum TestDomainReason {
307        #[error("test error")]
308        TestError,
309        #[error("{0}")]
310        Uvs(UvsReason),
311    }
312
313    impl ErrorCode for TestDomainReason {
314        fn error_code(&self) -> i32 {
315            match self {
316                TestDomainReason::TestError => 1001,
317                TestDomainReason::Uvs(uvs_reason) => uvs_reason.error_code(),
318            }
319        }
320    }
321
322    #[test]
323    fn test_struct_error_serialization() {
324        // Create a context
325        let mut context = CallContext::default();
326        context
327            .items
328            .push(("key1".to_string(), "value1".to_string()));
329        context
330            .items
331            .push(("key2".to_string(), "value2".to_string()));
332
333        // Create a StructError
334        let error = StructError::new(
335            TestDomainReason::TestError,
336            Some("Detailed error description".to_string()),
337            Some("file.rs:10:5".to_string()),
338            vec![OperationContext::from(context)],
339        );
340
341        // Serialize to JSON
342        let json_value = serde_json::to_value(&error).unwrap();
343        println!("{json_value:#}");
344    }
345}