Skip to main content

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}