duende_core/
error.rs

1//! Error types for duende-core.
2//!
3//! Per Iron Lotus Framework: All errors are explicit, no panics allowed.
4
5use std::time::Duration;
6
7/// Result type alias for daemon operations.
8pub type Result<T> = std::result::Result<T, DaemonError>;
9
10/// Comprehensive error type for daemon operations.
11///
12/// Following Iron Lotus principle of explicit error handling,
13/// this enum covers all failure modes without panics.
14#[derive(Debug, thiserror::Error)]
15pub enum DaemonError {
16    /// Configuration error during daemon initialization.
17    #[error("configuration error: {0}")]
18    Config(String),
19
20    /// Initialization failed.
21    #[error("initialization failed: {0}")]
22    Init(String),
23
24    /// Runtime error during daemon execution.
25    #[error("runtime error: {0}")]
26    Runtime(String),
27
28    /// Shutdown error.
29    #[error("shutdown error: {0}")]
30    Shutdown(String),
31
32    /// Shutdown timed out.
33    #[error("shutdown timed out after {0:?}")]
34    ShutdownTimeout(Duration),
35
36    /// Health check failed.
37    #[error("health check failed: {0}")]
38    HealthCheck(String),
39
40    /// Resource limit exceeded.
41    #[error("resource limit exceeded: {resource} (limit: {limit}, actual: {actual})")]
42    ResourceLimit {
43        /// The resource that was exceeded.
44        resource: String,
45        /// The configured limit.
46        limit: u64,
47        /// The actual value.
48        actual: u64,
49    },
50
51    /// Policy violation.
52    #[error("policy violation: {0}")]
53    PolicyViolation(String),
54
55    /// Signal handling error.
56    #[error("signal error: {0}")]
57    Signal(String),
58
59    /// Daemon not found.
60    #[error("daemon not found: {0}")]
61    NotFound(String),
62
63    /// Invalid state for operation.
64    #[error("invalid state: {0}")]
65    State(String),
66
67    /// I/O error.
68    #[error("I/O error: {0}")]
69    Io(#[from] std::io::Error),
70
71    /// Serialization error.
72    #[error("serialization error: {0}")]
73    Serialization(String),
74
75    /// Internal error (should not occur in production).
76    #[error("internal error: {0}")]
77    Internal(String),
78}
79
80impl DaemonError {
81    /// Creates a configuration error.
82    #[must_use]
83    pub fn config(msg: impl Into<String>) -> Self {
84        Self::Config(msg.into())
85    }
86
87    /// Creates an initialization error.
88    #[must_use]
89    pub fn init(msg: impl Into<String>) -> Self {
90        Self::Init(msg.into())
91    }
92
93    /// Creates a runtime error.
94    #[must_use]
95    pub fn runtime(msg: impl Into<String>) -> Self {
96        Self::Runtime(msg.into())
97    }
98
99    /// Creates a shutdown error.
100    #[must_use]
101    pub fn shutdown(msg: impl Into<String>) -> Self {
102        Self::Shutdown(msg.into())
103    }
104
105    /// Creates a health check error.
106    #[must_use]
107    pub fn health_check(msg: impl Into<String>) -> Self {
108        Self::HealthCheck(msg.into())
109    }
110
111    /// Creates a policy violation error.
112    #[must_use]
113    pub fn policy_violation(msg: impl Into<String>) -> Self {
114        Self::PolicyViolation(msg.into())
115    }
116
117    /// Returns true if this error is recoverable (daemon can continue).
118    #[must_use]
119    pub const fn is_recoverable(&self) -> bool {
120        matches!(
121            self,
122            Self::HealthCheck(_) | Self::ResourceLimit { .. } | Self::PolicyViolation(_)
123        )
124    }
125
126    /// Returns true if this error requires immediate shutdown.
127    #[must_use]
128    pub const fn is_fatal(&self) -> bool {
129        matches!(self, Self::Init(_) | Self::Internal(_))
130    }
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn test_error_display() {
139        let err = DaemonError::config("invalid port");
140        assert_eq!(err.to_string(), "configuration error: invalid port");
141    }
142
143    #[test]
144    fn test_error_recoverable() {
145        assert!(DaemonError::health_check("timeout").is_recoverable());
146        assert!(!DaemonError::init("failed").is_recoverable());
147    }
148
149    #[test]
150    fn test_error_fatal() {
151        assert!(DaemonError::init("failed").is_fatal());
152        assert!(!DaemonError::runtime("transient").is_fatal());
153    }
154
155    #[test]
156    fn test_resource_limit_error() {
157        let err = DaemonError::ResourceLimit {
158            resource: "memory".to_string(),
159            limit: 1024,
160            actual: 2048,
161        };
162        assert!(err.to_string().contains("memory"));
163        assert!(err.is_recoverable());
164    }
165}