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