Skip to main content

zenith_foundation/
error.rs

1//! 统一错误类型与错误处理规范
2//!
3//! 本模块定义 Zenith 框架的统一错误类型,
4//! 所有错误都必须携带上下文信息,禁止返回裸错误。
5
6use std::fmt;
7
8/// 核心错误类型
9#[derive(Debug, thiserror::Error)]
10pub enum CoreError {
11    /// 资源配额超限
12    #[error("quota exceeded: limit={limit}, requested={requested}, resource={resource}")]
13    QuotaExceeded {
14        /// 资源类型
15        resource: &'static str,
16        /// 配额上限
17        limit: u64,
18        /// 请求数量
19        requested: u64,
20    },
21
22    /// 资源未找到
23    #[error("resource not found: id={id}, type={resource_type}")]
24    ResourceNotFound {
25        /// 资源ID
26        id: u64,
27        /// 资源类型
28        resource_type: &'static str,
29    },
30
31    /// 资源已存在(重复创建)
32    #[error("resource already exists: id={id}, type={resource_type}")]
33    ResourceAlreadyExists {
34        /// 资源ID
35        id: u64,
36        /// 资源类型
37        resource_type: &'static str,
38    },
39
40    /// 资源已损坏
41    #[error("resource poisoned: id={id}, reason={reason}")]
42    ResourcePoisoned {
43        /// 资源ID
44        id: u64,
45        /// 损坏原因
46        reason: String,
47    },
48
49    /// 所有权错误
50    #[error("ownership violation: expected={expected}, actual={actual}")]
51    OwnershipViolation {
52        /// 期望的所有者
53        expected: &'static str,
54        /// 实际的所有者
55        actual: &'static str,
56    },
57
58    /// 地址范围无效
59    #[error("invalid address range: addr={addr:x}, len={len}, max={max:x}")]
60    InvalidAddressRange {
61        /// 起始地址
62        addr: u64,
63        /// 长度
64        len: u64,
65        /// 最大地址
66        max: u64,
67    },
68
69    /// 算术溢出
70    #[error("arithmetic overflow: operation={op}, a={a}, b={b}")]
71    ArithmeticOverflow {
72        /// 操作符
73        op: &'static str,
74        /// 操作数a
75        a: u64,
76        /// 操作数b
77        b: u64,
78    },
79
80    /// 状态冲突
81    #[error("state conflict: current={current}, expected={expected}")]
82    StateConflict {
83        /// 当前状态
84        current: String,
85        /// 期望状态
86        expected: String,
87    },
88
89    /// 无效配置
90    #[error("invalid config: {field} — {reason}")]
91    InvalidConfig {
92        /// 配置字段名
93        field: &'static str,
94        /// 原因
95        reason: &'static str,
96    },
97
98    /// 内部错误
99    #[error("internal error: {0}")]
100    Internal(String),
101
102    /// 未知错误
103    #[error("unknown error: {0}")]
104    Unknown(String),
105}
106
107/// 核心结果类型
108pub type CoreResult<T> = Result<T, CoreError>;
109
110impl CoreError {
111    /// 创建配额超限错误
112    pub fn quota_exceeded(resource: &'static str, limit: u64, requested: u64) -> Self {
113        CoreError::QuotaExceeded {
114            resource,
115            limit,
116            requested,
117        }
118    }
119
120    /// 创建资源未找到错误
121    pub fn resource_not_found(id: u64, resource_type: &'static str) -> Self {
122        CoreError::ResourceNotFound { id, resource_type }
123    }
124
125    /// 创建资源已存在错误
126    pub fn resource_already_exists(id: u64, resource_type: &'static str) -> Self {
127        CoreError::ResourceAlreadyExists {
128            id,
129            resource_type,
130        }
131    }
132
133    /// 创建资源损坏错误
134    pub fn resource_poisoned(id: u64, reason: impl Into<String>) -> Self {
135        CoreError::ResourcePoisoned {
136            id,
137            reason: reason.into(),
138        }
139    }
140
141    /// 创建所有权违规错误
142    pub fn ownership_violation(expected: &'static str, actual: &'static str) -> Self {
143        CoreError::OwnershipViolation { expected, actual }
144    }
145
146    /// 创建地址范围无效错误
147    pub fn invalid_address_range(addr: u64, len: u64, max: u64) -> Self {
148        CoreError::InvalidAddressRange { addr, len, max }
149    }
150
151    /// 创建算术溢出错误
152    pub fn arithmetic_overflow(op: &'static str, a: u64, b: u64) -> Self {
153        CoreError::ArithmeticOverflow { op, a, b }
154    }
155
156    /// 创建状态冲突错误
157    pub fn state_conflict(current: impl Into<String>, expected: impl Into<String>) -> Self {
158        CoreError::StateConflict {
159            current: current.into(),
160            expected: expected.into(),
161        }
162    }
163
164    /// 创建无效配置错误
165    pub fn invalid_config(field: &'static str, reason: &'static str) -> Self {
166        CoreError::InvalidConfig { field, reason }
167    }
168
169    /// 创建内部错误
170    pub fn internal(msg: impl Into<String>) -> Self {
171        CoreError::Internal(msg.into())
172    }
173
174    /// 创建未知错误
175    pub fn unknown(msg: impl Into<String>) -> Self {
176        CoreError::Unknown(msg.into())
177    }
178}
179
180/// 可记录的错误特征
181pub trait LoggableError: fmt::Display {
182    /// 是否为可恢复错误
183    fn is_recoverable(&self) -> bool;
184
185    /// 是否为安全相关错误
186    fn is_security_related(&self) -> bool;
187
188    /// 获取错误严重级别
189    fn severity(&self) -> ErrorSeverity;
190}
191
192/// 错误严重级别(全 workspace 唯一定义)
193///
194/// 四级语义与其他平台错误体系的映射:
195/// `Info`/`Warning`/`Error` 同名对应;`Critical` 即致命级
196/// (等同部分平台错误体系中的 `Fatal`:系统不可用、不可恢复)。
197#[derive(Debug, Clone, Copy, PartialEq, Eq)]
198pub enum ErrorSeverity {
199    /// 信息级别(可恢复)
200    Info,
201    /// 警告级别(需要关注)
202    Warning,
203    /// 错误级别(操作失败)
204    Error,
205    /// 严重错误(致命级,等同 `Fatal`:系统不可用)
206    Critical,
207}
208
209impl LoggableError for CoreError {
210    fn is_recoverable(&self) -> bool {
211        matches!(
212            self,
213            CoreError::QuotaExceeded { .. }
214                | CoreError::ResourceNotFound { .. }
215                | CoreError::ResourceAlreadyExists { .. }
216        )
217    }
218
219    fn is_security_related(&self) -> bool {
220        matches!(
221            self,
222            CoreError::OwnershipViolation { .. } | CoreError::InvalidAddressRange { .. }
223        )
224    }
225
226    fn severity(&self) -> ErrorSeverity {
227        match self {
228            CoreError::OwnershipViolation { .. } => ErrorSeverity::Critical,
229            CoreError::ResourcePoisoned { .. } => ErrorSeverity::Critical,
230            CoreError::InvalidAddressRange { .. } => ErrorSeverity::Error,
231            CoreError::ArithmeticOverflow { .. } => ErrorSeverity::Error,
232            CoreError::StateConflict { .. } => ErrorSeverity::Warning,
233            CoreError::InvalidConfig { .. } => ErrorSeverity::Warning,
234            CoreError::QuotaExceeded { .. } => ErrorSeverity::Warning,
235            CoreError::ResourceAlreadyExists { .. } => ErrorSeverity::Warning,
236            CoreError::ResourceNotFound { .. } => ErrorSeverity::Info,
237            CoreError::Internal(_) => ErrorSeverity::Error,
238            CoreError::Unknown(_) => ErrorSeverity::Error,
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use super::*;
246
247    #[test]
248    fn test_quota_exceeded_error() {
249        let err = CoreError::quota_exceeded("frame", 1024, 2048);
250        assert!(err.is_recoverable());
251        assert!(!err.is_security_related());
252        assert_eq!(err.severity(), ErrorSeverity::Warning);
253        assert!(err.to_string().contains("frame"));
254    }
255
256    #[test]
257    fn test_ownership_violation_error() {
258        let err = CoreError::ownership_violation("pool", "other");
259        assert!(!err.is_recoverable());
260        assert!(err.is_security_related());
261        assert_eq!(err.severity(), ErrorSeverity::Critical);
262    }
263
264    #[test]
265    fn test_invalid_address_range_error() {
266        let err = CoreError::invalid_address_range(0x1000, 256, 0x2000);
267        assert!(!err.is_recoverable());
268        assert!(err.is_security_related());
269        assert_eq!(err.severity(), ErrorSeverity::Error);
270    }
271
272    #[test]
273    fn test_arithmetic_overflow_error() {
274        let err = CoreError::arithmetic_overflow("add", u64::MAX, 1);
275        assert_eq!(err.severity(), ErrorSeverity::Error);
276    }
277
278    #[test]
279    fn test_state_conflict_error() {
280        let err = CoreError::state_conflict("active", "idle");
281        assert_eq!(err.severity(), ErrorSeverity::Warning);
282    }
283
284    #[test]
285    fn test_internal_error() {
286        let err = CoreError::internal("something went wrong");
287        assert_eq!(err.severity(), ErrorSeverity::Error);
288    }
289
290    // ===== CoreError 所有变体构造函数和 Display 输出 =====
291
292    #[test]
293    fn test_quota_exceeded_display() {
294        let err = CoreError::quota_exceeded("memory", 1024, 2048);
295        let msg = err.to_string();
296        assert!(msg.contains("quota exceeded"));
297        assert!(msg.contains("memory"));
298        assert!(msg.contains("1024"));
299        assert!(msg.contains("2048"));
300    }
301
302    #[test]
303    fn test_resource_not_found_constructor_and_display() {
304        let err = CoreError::resource_not_found(42, "frame");
305        assert!(err.is_recoverable());
306        assert!(!err.is_security_related());
307        assert_eq!(err.severity(), ErrorSeverity::Info);
308
309        let msg = err.to_string();
310        assert!(msg.contains("resource not found"));
311        assert!(msg.contains("42"));
312        assert!(msg.contains("frame"));
313    }
314
315    #[test]
316    fn test_resource_already_exists_constructor_and_display() {
317        let err = CoreError::resource_already_exists(7, "connection");
318        assert!(err.is_recoverable());
319        assert!(!err.is_security_related());
320        assert_eq!(err.severity(), ErrorSeverity::Warning);
321
322        let msg = err.to_string();
323        assert!(msg.contains("resource already exists"));
324        assert!(msg.contains("7"));
325        assert!(msg.contains("connection"));
326    }
327
328    #[test]
329    fn test_resource_poisoned_constructor_and_display() {
330        let err = CoreError::resource_poisoned(100, "corrupted data");
331        assert!(!err.is_recoverable());
332        assert!(!err.is_security_related());
333        assert_eq!(err.severity(), ErrorSeverity::Critical);
334
335        let msg = err.to_string();
336        assert!(msg.contains("resource poisoned"));
337        assert!(msg.contains("100"));
338        assert!(msg.contains("corrupted data"));
339    }
340
341    #[test]
342    fn test_ownership_violation_display() {
343        let err = CoreError::ownership_violation("pool_a", "pool_b");
344        let msg = err.to_string();
345        assert!(msg.contains("ownership violation"));
346        assert!(msg.contains("pool_a"));
347        assert!(msg.contains("pool_b"));
348    }
349
350    #[test]
351    fn test_invalid_address_range_display() {
352        let err = CoreError::invalid_address_range(0x1000, 256, 0x2000);
353        let msg = err.to_string();
354        assert!(msg.contains("invalid address range"));
355        assert!(msg.contains(&format!("{:x}", 0x1000)));
356        assert!(msg.contains("256"));
357    }
358
359    #[test]
360    fn test_arithmetic_overflow_display() {
361        let err = CoreError::arithmetic_overflow("mul", 100, 200);
362        let msg = err.to_string();
363        assert!(msg.contains("arithmetic overflow"));
364        assert!(msg.contains("mul"));
365        assert!(msg.contains("100"));
366        assert!(msg.contains("200"));
367    }
368
369    #[test]
370    fn test_state_conflict_constructor_and_display() {
371        let err = CoreError::state_conflict("running", "stopped");
372        assert!(!err.is_recoverable());
373        assert!(!err.is_security_related());
374        assert_eq!(err.severity(), ErrorSeverity::Warning);
375
376        let msg = err.to_string();
377        assert!(msg.contains("state conflict"));
378        assert!(msg.contains("running"));
379        assert!(msg.contains("stopped"));
380    }
381
382    #[test]
383    fn test_internal_error_constructor_and_display() {
384        let err = CoreError::internal("fatal crash");
385        assert!(!err.is_recoverable());
386        assert!(!err.is_security_related());
387        assert_eq!(err.severity(), ErrorSeverity::Error);
388
389        let msg = err.to_string();
390        assert!(msg.contains("internal error"));
391        assert!(msg.contains("fatal crash"));
392    }
393
394    #[test]
395    fn test_unknown_error_constructor_and_display() {
396        let err = CoreError::unknown("mystery error");
397        assert!(!err.is_recoverable());
398        assert!(!err.is_security_related());
399        assert_eq!(err.severity(), ErrorSeverity::Error);
400
401        let msg = err.to_string();
402        assert!(msg.contains("unknown error"));
403        assert!(msg.contains("mystery error"));
404    }
405
406    // ===== ErrorSeverity 排序和比较 =====
407
408    #[test]
409    fn test_error_severity_equality() {
410        assert_eq!(ErrorSeverity::Info, ErrorSeverity::Info);
411        assert_eq!(ErrorSeverity::Warning, ErrorSeverity::Warning);
412        assert_eq!(ErrorSeverity::Error, ErrorSeverity::Error);
413        assert_eq!(ErrorSeverity::Critical, ErrorSeverity::Critical);
414    }
415
416    #[test]
417    fn test_error_severity_clone_copy() {
418        let s = ErrorSeverity::Warning;
419        let s2 = s;
420        assert_eq!(s, s2);
421        let s3 = s;
422        assert_eq!(s, s3);
423    }
424
425    #[test]
426    fn test_error_severity_debug() {
427        let s = format!("{:?}", ErrorSeverity::Critical);
428        assert_eq!(s, "Critical");
429    }
430
431    // ===== is_recoverable / is_security_related 完整覆盖 =====
432
433    #[test]
434    fn test_all_recoverable_errors() {
435        assert!(CoreError::quota_exceeded("mem", 0, 0).is_recoverable());
436        assert!(CoreError::resource_not_found(0, "x").is_recoverable());
437        assert!(CoreError::resource_already_exists(0, "x").is_recoverable());
438
439        assert!(!CoreError::resource_poisoned(0, "x").is_recoverable());
440        assert!(!CoreError::ownership_violation("a", "b").is_recoverable());
441        assert!(!CoreError::invalid_address_range(0, 0, 0).is_recoverable());
442        assert!(!CoreError::arithmetic_overflow("add", 0, 0).is_recoverable());
443        assert!(!CoreError::state_conflict("a", "b").is_recoverable());
444        assert!(!CoreError::internal("x").is_recoverable());
445        assert!(!CoreError::unknown("x").is_recoverable());
446    }
447
448    #[test]
449    fn test_all_security_related_errors() {
450        assert!(CoreError::ownership_violation("a", "b").is_security_related());
451        assert!(CoreError::invalid_address_range(0, 0, 0).is_security_related());
452
453        assert!(!CoreError::quota_exceeded("mem", 0, 0).is_security_related());
454        assert!(!CoreError::resource_not_found(0, "x").is_security_related());
455        assert!(!CoreError::resource_already_exists(0, "x").is_security_related());
456        assert!(!CoreError::resource_poisoned(0, "x").is_security_related());
457        assert!(!CoreError::arithmetic_overflow("add", 0, 0).is_security_related());
458        assert!(!CoreError::state_conflict("a", "b").is_security_related());
459        assert!(!CoreError::internal("x").is_security_related());
460        assert!(!CoreError::unknown("x").is_security_related());
461    }
462
463    // ===== LoggableError trait 对象安全测试 =====
464
465    #[test]
466    fn test_loggable_error_trait_object() {
467        let err: Box<dyn LoggableError> = Box::new(CoreError::internal("test"));
468        assert!(!err.is_recoverable());
469        assert!(!err.is_security_related());
470        assert_eq!(err.severity(), ErrorSeverity::Error);
471        assert!(err.to_string().contains("internal error"));
472    }
473}