Skip to main content

mockforge_core/
error.rs

1//! Error types for MockForge Core
2
3/// Result type alias for MockForge operations
4pub type Result<T> = std::result::Result<T, Error>;
5
6/// Core error types for MockForge operations
7#[derive(Debug, thiserror::Error)]
8pub enum Error {
9    /// Validation error (schema/format validation failed)
10    #[error("Validation error: {message}")]
11    Validation {
12        /// Validation error message
13        message: String,
14    },
15
16    /// Routing error (route not found or invalid)
17    #[error("Routing error: {message}")]
18    Routing {
19        /// Routing error message
20        message: String,
21    },
22
23    /// Proxy error (proxy request failed)
24    #[error("Proxy error: {message}")]
25    Proxy {
26        /// Proxy error message
27        message: String,
28    },
29
30    /// Latency simulation error (latency injection failed)
31    #[error("Latency simulation error: {message}")]
32    Latency {
33        /// Latency error message
34        message: String,
35    },
36
37    /// Configuration error (invalid config or missing required fields)
38    #[error("Configuration error: {message}")]
39    Config {
40        /// Configuration error message
41        message: String,
42    },
43
44    /// Protocol not found (requested protocol is not registered)
45    #[error("Protocol not found: {message}")]
46    ProtocolNotFound {
47        /// Protocol not found error message
48        message: String,
49    },
50
51    /// Protocol disabled (protocol exists but is disabled)
52    #[error("Protocol disabled: {message}")]
53    ProtocolDisabled {
54        /// Protocol disabled error message
55        message: String,
56    },
57
58    /// Protocol handler in use (handler already registered)
59    #[error("Protocol handler in use: {message}")]
60    ProtocolHandlerInUse {
61        /// Protocol handler conflict error message
62        message: String,
63    },
64
65    /// Protocol validation error (protocol-specific validation failed)
66    #[error("Protocol validation error: {message}")]
67    ProtocolValidationError {
68        /// Protocol name that failed validation
69        protocol: String,
70        /// Validation error message
71        message: String,
72    },
73
74    /// I/O error (file read/write operations)
75    #[error("IO error: {0}")]
76    Io(#[from] std::io::Error),
77
78    /// JSON serialization/deserialization error
79    #[error("JSON error: {0}")]
80    Json(#[from] serde_json::Error),
81
82    /// YAML serialization/deserialization error
83    #[error("YAML error: {0}")]
84    Yaml(#[from] serde_yaml::Error),
85
86    /// HTTP client request error
87    #[error("HTTP error: {0}")]
88    Http(#[from] reqwest::Error),
89
90    /// URL parsing error
91    #[error("URL parse error: {0}")]
92    UrlParse(#[from] url::ParseError),
93
94    /// Regular expression compilation error
95    #[error("Regex error: {0}")]
96    Regex(#[from] regex::Error),
97
98    /// Route not found error with method and path context
99    #[error("Route not found: {method} {path}")]
100    RouteNotFound {
101        /// HTTP method
102        method: String,
103        /// Request path
104        path: String,
105    },
106
107    /// Schema validation failed with structured context
108    #[error("Schema validation failed at '{path}': expected {expected}, got {actual}")]
109    SchemaValidationFailed {
110        /// JSON path where validation failed
111        path: String,
112        /// Expected type or value
113        expected: String,
114        /// Actual type or value encountered
115        actual: String,
116    },
117
118    /// Configuration error with source
119    #[error("Configuration error: {message}")]
120    ConfigWithSource {
121        /// Configuration error message
122        message: String,
123        /// Underlying cause
124        #[source]
125        source: Box<dyn std::error::Error + Send + Sync>,
126    },
127
128    /// Entity not found
129    #[error("{entity} not found: {id}")]
130    NotFound {
131        /// Entity type that was not found
132        entity: String,
133        /// Identifier that was looked up
134        id: String,
135    },
136
137    /// Feature is disabled
138    #[error("Feature disabled: {feature}")]
139    FeatureDisabled {
140        /// Feature name that is disabled
141        feature: String,
142    },
143
144    /// Global component already initialized
145    #[error("Already initialized: {component}")]
146    AlreadyInitialized {
147        /// Component name that was already initialized
148        component: String,
149    },
150
151    /// Invalid state for the requested operation
152    #[error("Invalid state: {message}")]
153    InvalidState {
154        /// Description of the invalid state
155        message: String,
156    },
157
158    /// SIEM transport error
159    #[error("SIEM transport error: {message}")]
160    SiemTransport {
161        /// SIEM transport error message
162        message: String,
163    },
164
165    /// I/O error with additional context
166    #[error("I/O error ({context}): {message}")]
167    IoWithContext {
168        /// Context describing what operation failed
169        context: String,
170        /// The underlying error message
171        message: String,
172    },
173
174    /// Internal error wrapping an underlying cause
175    #[error("Internal error: {message}")]
176    Internal {
177        /// Internal error message
178        message: String,
179    },
180
181    /// Generic error with message string
182    #[error("Generic error: {0}")]
183    Generic(String),
184
185    /// Encryption/decryption operation error
186    #[error("Encryption error: {0}")]
187    Encryption(#[from] crate::encryption::EncryptionError),
188
189    /// JavaScript evaluation error (template engine, etc.)
190    #[cfg(feature = "scripting")]
191    #[error("JavaScript error: {0}")]
192    JavaScript(#[from] rquickjs::Error),
193}
194
195impl From<String> for Error {
196    fn from(message: String) -> Self {
197        Self::Generic(message)
198    }
199}
200
201impl Error {
202    /// Create a validation error
203    pub fn validation<S: Into<String>>(message: S) -> Self {
204        Self::Validation {
205            message: message.into(),
206        }
207    }
208
209    /// Create a routing error
210    pub fn routing<S: Into<String>>(message: S) -> Self {
211        Self::Routing {
212            message: message.into(),
213        }
214    }
215
216    /// Create a proxy error
217    pub fn proxy<S: Into<String>>(message: S) -> Self {
218        Self::Proxy {
219            message: message.into(),
220        }
221    }
222
223    /// Create a latency error
224    pub fn latency<S: Into<String>>(message: S) -> Self {
225        Self::Latency {
226            message: message.into(),
227        }
228    }
229
230    /// Create a config error
231    pub fn config<S: Into<String>>(message: S) -> Self {
232        Self::Config {
233            message: message.into(),
234        }
235    }
236
237    /// Create a protocol not found error
238    pub fn protocol_not_found<S: Into<String>>(message: S) -> Self {
239        Self::ProtocolNotFound {
240            message: message.into(),
241        }
242    }
243
244    /// Create a protocol disabled error
245    pub fn protocol_disabled<S: Into<String>>(message: S) -> Self {
246        Self::ProtocolDisabled {
247            message: message.into(),
248        }
249    }
250
251    /// Create a protocol handler in use error
252    pub fn protocol_handler_in_use<S: Into<String>>(message: S) -> Self {
253        Self::ProtocolHandlerInUse {
254            message: message.into(),
255        }
256    }
257
258    /// Create a protocol validation error
259    pub fn protocol_validation_error<S: Into<String>>(protocol: S, message: S) -> Self {
260        Self::ProtocolValidationError {
261            protocol: protocol.into(),
262            message: message.into(),
263        }
264    }
265
266    /// Create a route-not-found error with method and path context
267    pub fn route_not_found<S: Into<String>>(method: S, path: S) -> Self {
268        Self::RouteNotFound {
269            method: method.into(),
270            path: path.into(),
271        }
272    }
273
274    /// Create a schema validation error with path, expected, and actual context
275    pub fn schema_validation_failed<S: Into<String>>(path: S, expected: S, actual: S) -> Self {
276        Self::SchemaValidationFailed {
277            path: path.into(),
278            expected: expected.into(),
279            actual: actual.into(),
280        }
281    }
282
283    /// Create a configuration error with an underlying source error
284    pub fn config_with_source<S: Into<String>>(
285        message: S,
286        source: impl std::error::Error + Send + Sync + 'static,
287    ) -> Self {
288        Self::ConfigWithSource {
289            message: message.into(),
290            source: Box::new(source),
291        }
292    }
293
294    /// Create a not-found error
295    pub fn not_found<S1: Into<String>, S2: Into<String>>(entity: S1, id: S2) -> Self {
296        Self::NotFound {
297            entity: entity.into(),
298            id: id.into(),
299        }
300    }
301
302    /// Create a feature-disabled error
303    pub fn feature_disabled<S: Into<String>>(feature: S) -> Self {
304        Self::FeatureDisabled {
305            feature: feature.into(),
306        }
307    }
308
309    /// Create an already-initialized error
310    pub fn already_initialized<S: Into<String>>(component: S) -> Self {
311        Self::AlreadyInitialized {
312            component: component.into(),
313        }
314    }
315
316    /// Create an invalid-state error
317    pub fn invalid_state<S: Into<String>>(message: S) -> Self {
318        Self::InvalidState {
319            message: message.into(),
320        }
321    }
322
323    /// Create a SIEM transport error
324    pub fn siem_transport<S: Into<String>>(message: S) -> Self {
325        Self::SiemTransport {
326            message: message.into(),
327        }
328    }
329
330    /// Create an I/O error with context
331    pub fn io_with_context<S1: Into<String>, S2: Into<String>>(context: S1, message: S2) -> Self {
332        Self::IoWithContext {
333            context: context.into(),
334            message: message.into(),
335        }
336    }
337
338    /// Create an internal error
339    pub fn internal<S: Into<String>>(message: S) -> Self {
340        Self::Internal {
341            message: message.into(),
342        }
343    }
344
345    /// Create a generic error
346    #[deprecated(note = "Use a specific error variant instead of Generic")]
347    pub fn generic<S: Into<String>>(message: S) -> Self {
348        Self::Generic(message.into())
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use super::*;
355
356    #[test]
357    fn test_validation_error() {
358        let err = Error::validation("test validation");
359        assert!(err.to_string().contains("Validation error"));
360        assert!(err.to_string().contains("test validation"));
361    }
362
363    #[test]
364    fn test_routing_error() {
365        let err = Error::routing("test routing");
366        assert!(err.to_string().contains("Routing error"));
367        assert!(err.to_string().contains("test routing"));
368    }
369
370    #[test]
371    fn test_proxy_error() {
372        let err = Error::proxy("test proxy");
373        assert!(err.to_string().contains("Proxy error"));
374        assert!(err.to_string().contains("test proxy"));
375    }
376
377    #[test]
378    fn test_latency_error() {
379        let err = Error::latency("test latency");
380        assert!(err.to_string().contains("Latency simulation error"));
381        assert!(err.to_string().contains("test latency"));
382    }
383
384    #[test]
385    fn test_config_error() {
386        let err = Error::config("test config");
387        assert!(err.to_string().contains("Configuration error"));
388        assert!(err.to_string().contains("test config"));
389    }
390
391    #[test]
392    fn test_internal_error() {
393        let err = Error::internal("test internal");
394        assert!(err.to_string().contains("Internal error"));
395        assert!(err.to_string().contains("test internal"));
396    }
397
398    #[test]
399    #[allow(deprecated)]
400    fn test_generic_error() {
401        let err = Error::generic("test generic");
402        assert!(err.to_string().contains("Generic error"));
403        assert!(err.to_string().contains("test generic"));
404    }
405
406    #[test]
407    fn test_not_found_error() {
408        let err = Error::not_found("User", "user-123");
409        assert_eq!(err.to_string(), "User not found: user-123");
410    }
411
412    #[test]
413    fn test_feature_disabled_error() {
414        let err = Error::feature_disabled("access review");
415        assert_eq!(err.to_string(), "Feature disabled: access review");
416    }
417
418    #[test]
419    fn test_already_initialized_error() {
420        let err = Error::already_initialized("SIEM emitter");
421        assert_eq!(err.to_string(), "Already initialized: SIEM emitter");
422    }
423
424    #[test]
425    fn test_invalid_state_error() {
426        let err = Error::invalid_state("Request is not pending approval");
427        assert_eq!(err.to_string(), "Invalid state: Request is not pending approval");
428    }
429
430    #[test]
431    fn test_siem_transport_error() {
432        let err = Error::siem_transport("Connection refused");
433        assert_eq!(err.to_string(), "SIEM transport error: Connection refused");
434    }
435
436    #[test]
437    fn test_io_with_context_error() {
438        let err = Error::io_with_context("reading risk register", "file not found");
439        assert_eq!(err.to_string(), "I/O error (reading risk register): file not found");
440    }
441
442    #[test]
443    fn test_from_string() {
444        let err: Error = "test message".to_string().into();
445        assert!(matches!(err, Error::Generic(_)));
446        assert!(err.to_string().contains("test message"));
447    }
448
449    #[test]
450    fn test_json_error_conversion() {
451        let json_err = serde_json::from_str::<serde_json::Value>("invalid json");
452        assert!(json_err.is_err());
453        let err: Error = json_err.unwrap_err().into();
454        assert!(matches!(err, Error::Json(_)));
455    }
456
457    #[test]
458    fn test_url_parse_error_conversion() {
459        let url_err = url::Url::parse("not a url");
460        assert!(url_err.is_err());
461        let err: Error = url_err.unwrap_err().into();
462        assert!(matches!(err, Error::UrlParse(_)));
463    }
464
465    #[test]
466    #[allow(clippy::invalid_regex)]
467    fn test_regex_error_conversion() {
468        let regex_err = regex::Regex::new("[invalid(");
469        assert!(regex_err.is_err());
470        let err: Error = regex_err.unwrap_err().into();
471        assert!(matches!(err, Error::Regex(_)));
472    }
473
474    #[test]
475    #[allow(deprecated)]
476    fn test_error_display() {
477        let errors = vec![
478            (Error::validation("msg"), "Validation error: msg"),
479            (Error::routing("msg"), "Routing error: msg"),
480            (Error::proxy("msg"), "Proxy error: msg"),
481            (Error::latency("msg"), "Latency simulation error: msg"),
482            (Error::config("msg"), "Configuration error: msg"),
483            (Error::internal("msg"), "Internal error: msg"),
484        ];
485
486        for (err, expected) in errors {
487            assert_eq!(err.to_string(), expected);
488        }
489    }
490}