clnrm_core/
error.rs

1//! Error types for cleanroom testing framework
2//!
3//! This module provides comprehensive error handling following core team best practices:
4//! - Structured error types with context
5//! - Error chaining and propagation
6//! - User-friendly error messages
7//! - Debug information for troubleshooting
8
9use serde::{Deserialize, Serialize};
10use std::error::Error as StdError;
11use std::fmt;
12
13/// Result type alias for cleanroom operations
14pub type Result<T> = std::result::Result<T, CleanroomError>;
15
16/// Comprehensive error type for cleanroom operations
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CleanroomError {
19    /// Error kind
20    pub kind: ErrorKind,
21    /// Error message
22    pub message: String,
23    /// Additional context
24    pub context: Option<String>,
25    /// Source error (if any)
26    pub source: Option<String>,
27    /// Timestamp when error occurred
28    pub timestamp: chrono::DateTime<chrono::Utc>,
29}
30
31/// Error kinds for different failure scenarios
32#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub enum ErrorKind {
34    /// Container-related errors
35    ContainerError,
36    /// Network-related errors
37    NetworkError,
38    /// Resource limit exceeded
39    ResourceLimitExceeded,
40    /// Timeout errors
41    Timeout,
42    /// Configuration errors
43    ConfigurationError,
44    /// Policy violation
45    PolicyViolation,
46    /// Deterministic execution error
47    DeterministicError,
48    /// Coverage tracking error
49    CoverageError,
50    /// Snapshot error
51    SnapshotError,
52    /// Tracing error
53    TracingError,
54    /// Redaction error
55    RedactionError,
56    /// Report generation error
57    ReportError,
58    /// IO error
59    IoError,
60    /// Serialization error
61    SerializationError,
62    /// Validation error
63    ValidationError,
64    /// Service error
65    ServiceError,
66    /// Internal error
67    InternalError,
68}
69
70impl CleanroomError {
71    /// Create a new cleanroom error
72    pub fn new(kind: ErrorKind, message: impl Into<String>) -> Self {
73        Self {
74            kind,
75            message: message.into(),
76            context: None,
77            source: None,
78            timestamp: chrono::Utc::now(),
79        }
80    }
81
82    /// Create a new cleanroom error with context
83    pub fn with_context(mut self, context: impl Into<String>) -> Self {
84        self.context = Some(context.into());
85        self
86    }
87
88    /// Create a new cleanroom error with source
89    pub fn with_source(mut self, source: impl Into<String>) -> Self {
90        self.source = Some(source.into());
91        self
92    }
93
94    /// Create a container error
95    pub fn container_error(message: impl Into<String>) -> Self {
96        Self::new(ErrorKind::ContainerError, message)
97    }
98
99    /// Create a network error
100    pub fn network_error(message: impl Into<String>) -> Self {
101        Self::new(ErrorKind::NetworkError, message)
102    }
103
104    /// Create a resource limit exceeded error
105    pub fn resource_limit_exceeded(message: impl Into<String>) -> Self {
106        Self::new(ErrorKind::ResourceLimitExceeded, message)
107    }
108
109    /// Create a timeout error
110    pub fn timeout_error(message: impl Into<String>) -> Self {
111        Self::new(ErrorKind::Timeout, message)
112    }
113
114    /// Create a configuration error
115    pub fn configuration_error(message: impl Into<String>) -> Self {
116        Self::new(ErrorKind::ConfigurationError, message)
117    }
118
119    /// Create a policy violation error
120    pub fn policy_violation_error(message: impl Into<String>) -> Self {
121        Self::new(ErrorKind::PolicyViolation, message)
122    }
123
124    /// Create a deterministic execution error
125    pub fn deterministic_error(message: impl Into<String>) -> Self {
126        Self::new(ErrorKind::DeterministicError, message)
127    }
128
129    /// Create a coverage tracking error
130    pub fn coverage_error(message: impl Into<String>) -> Self {
131        Self::new(ErrorKind::CoverageError, message)
132    }
133
134    /// Create a snapshot error
135    pub fn snapshot_error(message: impl Into<String>) -> Self {
136        Self::new(ErrorKind::SnapshotError, message)
137    }
138
139    /// Create a tracing error
140    pub fn tracing_error(message: impl Into<String>) -> Self {
141        Self::new(ErrorKind::TracingError, message)
142    }
143
144    /// Create a redaction error
145    pub fn redaction_error(message: impl Into<String>) -> Self {
146        Self::new(ErrorKind::RedactionError, message)
147    }
148
149    /// Create a report generation error
150    pub fn report_error(message: impl Into<String>) -> Self {
151        Self::new(ErrorKind::ReportError, message)
152    }
153
154    /// Create a connection failed error
155    pub fn connection_failed(message: impl Into<String>) -> Self {
156        Self::new(ErrorKind::NetworkError, message)
157    }
158
159    /// Create a service error
160    pub fn service_error(message: impl Into<String>) -> Self {
161        Self::new(ErrorKind::ServiceError, message)
162    }
163
164    /// Create an IO error
165    pub fn io_error(message: impl Into<String>) -> Self {
166        Self::new(ErrorKind::IoError, message)
167    }
168
169    /// Create a serialization error
170    pub fn serialization_error(message: impl Into<String>) -> Self {
171        Self::new(ErrorKind::SerializationError, message)
172    }
173
174    /// Create a validation error
175    pub fn validation_error(message: impl Into<String>) -> Self {
176        Self::new(ErrorKind::ValidationError, message)
177    }
178
179    /// Create an internal error
180    pub fn internal_error(message: impl Into<String>) -> Self {
181        Self::new(ErrorKind::InternalError, message)
182    }
183
184    /// Create a configuration error (alias for configuration_error)
185    pub fn config_error(message: impl Into<String>) -> Self {
186        Self::configuration_error(message)
187    }
188
189    /// Create an execution error (alias for internal_error)
190    pub fn execution_error(message: impl Into<String>) -> Self {
191        Self::internal_error(message)
192    }
193}
194
195impl fmt::Display for CleanroomError {
196    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197        write!(f, "{:?}: {}", self.kind, self.message)?;
198        if let Some(context) = &self.context {
199            write!(f, " (Context: {})", context)?;
200        }
201        if let Some(source) = &self.source {
202            write!(f, " (Source: {})", source)?;
203        }
204        Ok(())
205    }
206}
207
208impl StdError for CleanroomError {
209    fn source(&self) -> Option<&(dyn StdError + 'static)> {
210        // We store source as String, so we can't return it as a trait object directly
211        None
212    }
213}
214
215// Implement From for common error types to convert them to CleanroomError
216impl From<std::io::Error> for CleanroomError {
217    fn from(err: std::io::Error) -> Self {
218        CleanroomError::io_error(err.to_string())
219    }
220}
221
222impl From<serde_json::Error> for CleanroomError {
223    fn from(err: serde_json::Error) -> Self {
224        CleanroomError::serialization_error(err.to_string())
225    }
226}
227
228impl From<testcontainers::TestcontainersError> for CleanroomError {
229    fn from(err: testcontainers::TestcontainersError) -> Self {
230        CleanroomError::container_error(err.to_string())
231    }
232}
233
234impl From<BackendError> for CleanroomError {
235    fn from(err: BackendError) -> Self {
236        match err {
237            BackendError::Runtime(msg) => CleanroomError::internal_error(msg),
238            BackendError::CommandExecution(msg) => CleanroomError::internal_error(msg),
239            BackendError::ContainerStartup(msg) => CleanroomError::container_error(msg),
240            BackendError::ContainerCommunication(msg) => CleanroomError::container_error(msg),
241            BackendError::ImagePull(msg) => CleanroomError::container_error(msg),
242            BackendError::ImageBuild(msg) => CleanroomError::container_error(msg),
243            BackendError::UnsupportedFeature(msg) => CleanroomError::internal_error(msg),
244        }
245    }
246}
247
248// Define BackendError, PolicyError, etc. as separate enums if needed,
249// or directly use ErrorKind for more granular error reporting.
250// For now, we'll keep them as separate enums for clarity and potential future expansion.
251
252/// Backend-specific errors
253#[derive(Debug, Clone, Serialize, Deserialize)]
254pub enum BackendError {
255    /// Runtime execution error
256    Runtime(String),
257    /// Command execution error
258    CommandExecution(String),
259    /// Container startup error
260    ContainerStartup(String),
261    /// Container communication error
262    ContainerCommunication(String),
263    /// Image pull error
264    ImagePull(String),
265    /// Image build error
266    ImageBuild(String),
267    /// Unsupported feature
268    UnsupportedFeature(String),
269}
270
271impl fmt::Display for BackendError {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        write!(f, "{:?}", self)
274    }
275}
276
277impl StdError for BackendError {}
278
279/// Policy-specific errors
280#[derive(Debug, Clone, Serialize, Deserialize)]
281pub enum PolicyError {
282    /// Invalid policy configuration
283    InvalidPolicy(String),
284    /// Policy violation detected
285    PolicyViolation(String),
286    /// Unsupported policy feature
287    UnsupportedFeature(String),
288}
289
290impl fmt::Display for PolicyError {
291    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
292        write!(f, "{:?}", self)
293    }
294}
295
296impl StdError for PolicyError {}
297
298/// Scenario-specific errors
299#[derive(Debug, Clone, Serialize, Deserialize)]
300pub enum ScenarioError {
301    /// Invalid scenario definition
302    InvalidScenario(String),
303    /// Step execution failed
304    StepExecutionFailed(String),
305    /// Scenario timeout
306    ScenarioTimeout(String),
307    /// Concurrent execution error
308    ConcurrentExecution(String),
309}
310
311impl fmt::Display for ScenarioError {
312    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
313        write!(f, "{:?}", self)
314    }
315}
316
317impl StdError for ScenarioError {}
318
319/// Service-specific errors
320#[derive(Debug, Clone, Serialize, Deserialize)]
321pub enum ServiceError {
322    /// Service connection failed
323    ConnectionFailed(String),
324    /// Service startup failed
325    StartupFailed(String),
326    /// Service health check failed
327    HealthCheckFailed(String),
328    /// Service configuration error
329    Configuration(String),
330    /// Unsupported service operation
331    UnsupportedOperation(String),
332}
333
334impl fmt::Display for ServiceError {
335    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
336        write!(f, "{:?}", self)
337    }
338}
339
340impl StdError for ServiceError {}
341
342/// Configuration errors
343#[derive(Debug, Clone, Serialize, Deserialize)]
344pub enum ConfigError {
345    /// Invalid configuration file
346    InvalidFile(String),
347    /// Missing configuration value
348    MissingValue(String),
349    /// Invalid configuration value
350    InvalidValue(String),
351    /// Invalid pattern in configuration
352    InvalidPattern(String, String),
353}
354
355impl fmt::Display for ConfigError {
356    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
357        write!(f, "{:?}", self)
358    }
359}
360
361impl StdError for ConfigError {}
362
363#[cfg(test)]
364mod tests {
365    #![allow(clippy::unwrap_used, clippy::expect_used, clippy::indexing_slicing, clippy::panic)]
366    
367    use super::*;
368
369    #[test]
370    fn test_error_creation() {
371        let error = CleanroomError::new(ErrorKind::ConfigurationError, "test message");
372        assert_eq!(error.message, "test message");
373    }
374
375    #[test]
376    fn test_error_with_source() {
377        let error = CleanroomError::new(ErrorKind::ContainerError, "test message")
378            .with_source("test source");
379        assert_eq!(error.message, "test message");
380        assert_eq!(error.source, Some("test source".to_string()));
381    }
382
383    #[test]
384    fn test_error_display() {
385        let error = CleanroomError::new(ErrorKind::Timeout, "test message");
386        let display = format!("{}", error);
387        assert!(display.contains("Timeout"));
388        assert!(display.contains("test message"));
389    }
390
391    #[test]
392    fn test_error_from_io() {
393        let io_error = std::io::Error::new(std::io::ErrorKind::NotFound, "test");
394        let error: CleanroomError = io_error.into();
395        assert!(matches!(error.kind, ErrorKind::IoError));
396    }
397
398    #[test]
399    fn test_error_from_json() {
400        let json_error = serde_json::from_str::<serde_json::Value>("invalid json");
401        let error: CleanroomError = json_error.unwrap_err().into();
402        assert!(matches!(error.kind, ErrorKind::SerializationError));
403    }
404
405    #[test]
406    fn test_helper_functions() {
407        let container_error = CleanroomError::container_error("container failed");
408        assert!(matches!(container_error.kind, ErrorKind::ContainerError));
409
410        let network_error = CleanroomError::network_error("network failed");
411        assert!(matches!(network_error.kind, ErrorKind::NetworkError));
412
413        let timeout_error = CleanroomError::timeout_error("timeout occurred");
414        assert!(matches!(timeout_error.kind, ErrorKind::Timeout));
415    }
416}