1use serde::{Deserialize, Serialize};
10use std::error::Error as StdError;
11use std::fmt;
12
13pub type Result<T> = std::result::Result<T, CleanroomError>;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct CleanroomError {
19 pub kind: ErrorKind,
21 pub message: String,
23 pub context: Option<String>,
25 pub source: Option<String>,
27 pub timestamp: chrono::DateTime<chrono::Utc>,
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
33pub enum ErrorKind {
34 ContainerError,
36 NetworkError,
38 ResourceLimitExceeded,
40 Timeout,
42 ConfigurationError,
44 PolicyViolation,
46 DeterministicError,
48 CoverageError,
50 SnapshotError,
52 TracingError,
54 RedactionError,
56 ReportError,
58 IoError,
60 SerializationError,
62 ValidationError,
64 ServiceError,
66 InternalError,
68 TemplateError,
70}
71
72impl CleanroomError {
73 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 pub fn with_context(mut self, context: impl Into<String>) -> Self {
86 self.context = Some(context.into());
87 self
88 }
89
90 pub fn with_source(mut self, source: impl Into<String>) -> Self {
92 self.source = Some(source.into());
93 self
94 }
95
96 pub fn container_error(message: impl Into<String>) -> Self {
98 Self::new(ErrorKind::ContainerError, message)
99 }
100
101 pub fn network_error(message: impl Into<String>) -> Self {
103 Self::new(ErrorKind::NetworkError, message)
104 }
105
106 pub fn resource_limit_exceeded(message: impl Into<String>) -> Self {
108 Self::new(ErrorKind::ResourceLimitExceeded, message)
109 }
110
111 pub fn timeout_error(message: impl Into<String>) -> Self {
113 Self::new(ErrorKind::Timeout, message)
114 }
115
116 pub fn configuration_error(message: impl Into<String>) -> Self {
118 Self::new(ErrorKind::ConfigurationError, message)
119 }
120
121 pub fn policy_violation_error(message: impl Into<String>) -> Self {
123 Self::new(ErrorKind::PolicyViolation, message)
124 }
125
126 pub fn deterministic_error(message: impl Into<String>) -> Self {
128 Self::new(ErrorKind::DeterministicError, message)
129 }
130
131 pub fn coverage_error(message: impl Into<String>) -> Self {
133 Self::new(ErrorKind::CoverageError, message)
134 }
135
136 pub fn snapshot_error(message: impl Into<String>) -> Self {
138 Self::new(ErrorKind::SnapshotError, message)
139 }
140
141 pub fn tracing_error(message: impl Into<String>) -> Self {
143 Self::new(ErrorKind::TracingError, message)
144 }
145
146 pub fn redaction_error(message: impl Into<String>) -> Self {
148 Self::new(ErrorKind::RedactionError, message)
149 }
150
151 pub fn report_error(message: impl Into<String>) -> Self {
153 Self::new(ErrorKind::ReportError, message)
154 }
155
156 pub fn connection_failed(message: impl Into<String>) -> Self {
158 Self::new(ErrorKind::NetworkError, message)
159 }
160
161 pub fn service_error(message: impl Into<String>) -> Self {
163 Self::new(ErrorKind::ServiceError, message)
164 }
165
166 pub fn io_error(message: impl Into<String>) -> Self {
168 Self::new(ErrorKind::IoError, message)
169 }
170
171 pub fn serialization_error(message: impl Into<String>) -> Self {
173 Self::new(ErrorKind::SerializationError, message)
174 }
175
176 pub fn validation_error(message: impl Into<String>) -> Self {
178 Self::new(ErrorKind::ValidationError, message)
179 }
180
181 pub fn internal_error(message: impl Into<String>) -> Self {
183 Self::new(ErrorKind::InternalError, message)
184 }
185
186 pub fn config_error(message: impl Into<String>) -> Self {
188 Self::configuration_error(message)
189 }
190
191 pub fn execution_error(message: impl Into<String>) -> Self {
193 Self::internal_error(message)
194 }
195
196 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 None
219 }
220}
221
222impl 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#[derive(Debug, Clone, Serialize, Deserialize)]
261pub enum BackendError {
262 Runtime(String),
264 CommandExecution(String),
266 ContainerStartup(String),
268 ContainerCommunication(String),
270 ImagePull(String),
272 ImageBuild(String),
274 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#[derive(Debug, Clone, Serialize, Deserialize)]
288pub enum PolicyError {
289 InvalidPolicy(String),
291 PolicyViolation(String),
293 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#[derive(Debug, Clone, Serialize, Deserialize)]
307pub enum ScenarioError {
308 InvalidScenario(String),
310 StepExecutionFailed(String),
312 ScenarioTimeout(String),
314 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#[derive(Debug, Clone, Serialize, Deserialize)]
328pub enum ServiceError {
329 ConnectionFailed(String),
331 StartupFailed(String),
333 HealthCheckFailed(String),
335 Configuration(String),
337 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#[derive(Debug, Clone, Serialize, Deserialize)]
351pub enum ConfigError {
352 InvalidFile(String),
354 MissingValue(String),
356 InvalidValue(String),
358 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}