webgates_core/errors_core.rs
1//! Core error interfaces shared across the crate.
2//!
3//! This module defines:
4//! - `ErrorSeverity`: common severity levels
5//! - `UserFriendlyError`: trait for multi-level messaging
6//! - `Result<T, E = Box<dyn std::error::Error + Send + Sync>>`: lightweight alias for pure/core layers
7//!
8//! The goal is to keep this module free of downstream dependencies so other
9//! modules can depend on it without creating cycles. Integration-specific
10//! mappings (e.g., HTTP/serde/logging) should live in higher layers.
11
12use std::fmt;
13
14/// Error severity levels for proper categorization and handling.
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub enum ErrorSeverity {
17 /// Critical system error requiring immediate attention.
18 Critical,
19 /// Error that prevents normal operation.
20 Error,
21 /// Warning that may indicate a problem.
22 Warning,
23 /// Informational message about an expected condition.
24 Info,
25}
26
27/// Trait providing user-friendly error messaging at multiple levels.
28///
29/// Implementors must supply user-safe text via `user_message` and richer context
30/// for logs and support via `developer_message` and `support_code`. The
31/// `severity`, `suggested_actions`, and `is_retryable` methods help downstream
32/// handling and UX.
33///
34/// This trait is intentionally minimal and does not depend on integration
35/// layers, keeping it usable in core/domain code.
36pub trait UserFriendlyError: fmt::Display + fmt::Debug {
37 /// User-facing message that is clear, actionable, and non-technical.
38 ///
39 /// Guidelines:
40 /// - Plain language any user can understand
41 /// - Actionable guidance when possible
42 /// - Never leak sensitive information
43 /// - Empathetic and helpful tone
44 fn user_message(&self) -> String;
45
46 /// Technical message with detailed information for developers and logs.
47 ///
48 /// Guidelines:
49 /// - Precise technical details and context for debugging
50 /// - Include relevant identifiers and parameters
51 /// - Structured for parsing by monitoring tools
52 fn developer_message(&self) -> String;
53
54 /// Unique support reference code for customer service and troubleshooting.
55 ///
56 /// Guidelines:
57 /// - Unique and easily communicable
58 /// - No sensitive information
59 /// - Consistent across error instances
60 fn support_code(&self) -> String;
61
62 /// Error severity level for proper handling and alerting.
63 fn severity(&self) -> ErrorSeverity;
64
65 /// Suggested user actions for resolving the error (optional).
66 fn suggested_actions(&self) -> Vec<String> {
67 Vec::new()
68 }
69
70 /// Whether this error should be retryable by the user (default: false).
71 fn is_retryable(&self) -> bool {
72 false
73 }
74}
75
76/// Lightweight result alias for core layers.
77///
78/// This keeps the alias free of crate-specific error enums to avoid creating
79/// dependency cycles. Higher layers can define richer aliases as needed.
80pub type Result<T, E = Box<dyn std::error::Error + Send + Sync>> = std::result::Result<T, E>;
81
82#[cfg(test)]
83mod tests {
84 use super::{ErrorSeverity, UserFriendlyError};
85
86 #[derive(Debug)]
87 struct DemoError;
88
89 impl std::fmt::Display for DemoError {
90 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
91 write!(f, "demo error")
92 }
93 }
94
95 impl UserFriendlyError for DemoError {
96 fn user_message(&self) -> String {
97 "Something went wrong. Please try again.".to_string()
98 }
99
100 fn developer_message(&self) -> String {
101 "DemoError: example developer message".to_string()
102 }
103
104 fn support_code(&self) -> String {
105 "DEMO-0001".to_string()
106 }
107
108 fn severity(&self) -> ErrorSeverity {
109 ErrorSeverity::Error
110 }
111
112 fn suggested_actions(&self) -> Vec<String> {
113 vec!["Retry the operation".to_string()]
114 }
115
116 fn is_retryable(&self) -> bool {
117 true
118 }
119 }
120
121 #[test]
122 fn demo_error_behaves() {
123 let err = DemoError;
124 assert_eq!(
125 err.user_message(),
126 "Something went wrong. Please try again."
127 );
128 assert!(err.developer_message().contains("DemoError"));
129 assert_eq!(err.support_code(), "DEMO-0001");
130 assert_eq!(err.severity(), ErrorSeverity::Error);
131 assert_eq!(
132 err.suggested_actions(),
133 vec!["Retry the operation".to_string()]
134 );
135 assert!(err.is_retryable());
136 }
137
138 #[test]
139 fn severity_variants() {
140 assert_ne!(ErrorSeverity::Critical, ErrorSeverity::Error);
141 assert_ne!(ErrorSeverity::Error, ErrorSeverity::Warning);
142 assert_ne!(ErrorSeverity::Warning, ErrorSeverity::Info);
143 }
144}