mockforge_plugin_core/
error.rs

1//! Plugin error types and result handling
2
3/// Plugin system result type
4pub type Result<T> = std::result::Result<T, PluginError>;
5
6/// Comprehensive error types for the plugin system
7#[derive(Debug, thiserror::Error)]
8pub enum PluginError {
9    /// Plugin loading or validation failed
10    #[error("Plugin loading error: {message}")]
11    LoadError {
12        /// Error message describing what went wrong
13        message: String,
14    },
15
16    /// Plugin execution failed
17    #[error("Plugin execution error: {message}")]
18    ExecutionError {
19        /// Error message describing the execution failure
20        message: String,
21    },
22
23    /// Plugin violated security constraints
24    #[error("Security violation: {violation}")]
25    SecurityViolation {
26        /// Description of the security violation
27        violation: String,
28    },
29
30    /// Plugin exceeded resource limits
31    #[error("Resource limit exceeded: {resource} limit={limit}, used={used}")]
32    ResourceLimitExceeded {
33        /// The resource that exceeded its limit
34        resource: String,
35        /// The configured limit
36        limit: String,
37        /// The amount used
38        used: String,
39    },
40
41    /// Plugin configuration is invalid
42    #[error("Invalid plugin configuration: {field} - {message}")]
43    InvalidConfiguration {
44        /// The configuration field that is invalid
45        field: String,
46        /// Error message describing the configuration issue
47        message: String,
48    },
49
50    /// Plugin is incompatible with current system
51    #[error("Plugin compatibility error: {reason}")]
52    CompatibilityError {
53        /// Reason for the compatibility error
54        reason: String,
55    },
56
57    /// Plugin communication failed
58    #[error("Plugin communication error: {message}")]
59    CommunicationError {
60        /// Error message describing the communication failure
61        message: String,
62    },
63
64    /// Plugin timed out
65    #[error("Plugin execution timeout: {timeout_ms}ms exceeded")]
66    TimeoutError {
67        /// Timeout duration in milliseconds
68        timeout_ms: u64,
69    },
70
71    /// WebAssembly runtime error
72    #[error("WebAssembly runtime error: {message}")]
73    WasmError {
74        /// Error message from the WASM runtime
75        message: String,
76    },
77
78    /// Plugin manifest is invalid
79    #[error("Invalid plugin manifest: {message}")]
80    InvalidManifest {
81        /// Error message describing the manifest issue
82        message: String,
83    },
84
85    /// Plugin dependency not found or incompatible
86    #[error("Plugin dependency error: {dependency} - {message}")]
87    DependencyError {
88        /// The dependency that caused the error
89        dependency: String,
90        /// Error message describing the dependency issue
91        message: String,
92    },
93
94    /// Generic plugin system error
95    #[error("Plugin system error: {message}")]
96    SystemError {
97        /// Error message describing the system error
98        message: String,
99    },
100}
101
102impl PluginError {
103    /// Create a load error
104    pub fn load<S: Into<String>>(message: S) -> Self {
105        Self::LoadError {
106            message: message.into(),
107        }
108    }
109
110    /// Create an execution error
111    pub fn execution<S: Into<String>>(message: S) -> Self {
112        Self::ExecutionError {
113            message: message.into(),
114        }
115    }
116
117    /// Create a security violation error
118    pub fn security<S: Into<String>>(violation: S) -> Self {
119        Self::SecurityViolation {
120            violation: violation.into(),
121        }
122    }
123
124    /// Create a resource limit error
125    pub fn resource_limit<S: Into<String>>(resource: S, limit: S, used: S) -> Self {
126        Self::ResourceLimitExceeded {
127            resource: resource.into(),
128            limit: limit.into(),
129            used: used.into(),
130        }
131    }
132
133    /// Create a configuration error
134    pub fn config<S: Into<String>>(field: S, message: S) -> Self {
135        Self::InvalidConfiguration {
136            field: field.into(),
137            message: message.into(),
138        }
139    }
140
141    /// Create a compatibility error
142    pub fn compatibility<S: Into<String>>(reason: S) -> Self {
143        Self::CompatibilityError {
144            reason: reason.into(),
145        }
146    }
147
148    /// Create a communication error
149    pub fn communication<S: Into<String>>(message: S) -> Self {
150        Self::CommunicationError {
151            message: message.into(),
152        }
153    }
154
155    /// Create a timeout error
156    pub fn timeout(timeout_ms: u64) -> Self {
157        Self::TimeoutError { timeout_ms }
158    }
159
160    /// Create a WASM error
161    pub fn wasm<S: Into<String>>(message: S) -> Self {
162        Self::WasmError {
163            message: message.into(),
164        }
165    }
166
167    /// Create a manifest error
168    pub fn manifest<S: Into<String>>(message: S) -> Self {
169        Self::InvalidManifest {
170            message: message.into(),
171        }
172    }
173
174    /// Create a dependency error
175    pub fn dependency<S: Into<String>>(dependency: S, message: S) -> Self {
176        Self::DependencyError {
177            dependency: dependency.into(),
178            message: message.into(),
179        }
180    }
181
182    /// Create a system error
183    pub fn system<S: Into<String>>(message: S) -> Self {
184        Self::SystemError {
185            message: message.into(),
186        }
187    }
188
189    /// Check if this is a security-related error
190    pub fn is_security_error(&self) -> bool {
191        matches!(self, PluginError::SecurityViolation { .. })
192    }
193
194    /// Check if this is a resource-related error
195    pub fn is_resource_error(&self) -> bool {
196        matches!(self, PluginError::ResourceLimitExceeded { .. })
197    }
198
199    /// Check if this is a timeout error
200    pub fn is_timeout_error(&self) -> bool {
201        matches!(self, PluginError::TimeoutError { .. })
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use super::*;
208
209    // Factory method tests
210    #[test]
211    fn test_load_error() {
212        let err = PluginError::load("failed to load plugin");
213        assert!(matches!(err, PluginError::LoadError { .. }));
214        assert!(err.to_string().contains("failed to load plugin"));
215    }
216
217    #[test]
218    fn test_execution_error() {
219        let err = PluginError::execution("execution failed");
220        assert!(matches!(err, PluginError::ExecutionError { .. }));
221        assert!(err.to_string().contains("execution failed"));
222    }
223
224    #[test]
225    fn test_security_error() {
226        let err = PluginError::security("unauthorized access");
227        assert!(matches!(err, PluginError::SecurityViolation { .. }));
228        assert!(err.to_string().contains("unauthorized access"));
229    }
230
231    #[test]
232    fn test_resource_limit_error() {
233        let err = PluginError::resource_limit("memory", "100MB", "150MB");
234        assert!(matches!(err, PluginError::ResourceLimitExceeded { .. }));
235        let msg = err.to_string();
236        assert!(msg.contains("memory"));
237        assert!(msg.contains("100MB"));
238        assert!(msg.contains("150MB"));
239    }
240
241    #[test]
242    fn test_config_error() {
243        let err = PluginError::config("timeout", "must be positive");
244        assert!(matches!(err, PluginError::InvalidConfiguration { .. }));
245        let msg = err.to_string();
246        assert!(msg.contains("timeout"));
247        assert!(msg.contains("must be positive"));
248    }
249
250    #[test]
251    fn test_compatibility_error() {
252        let err = PluginError::compatibility("API version mismatch");
253        assert!(matches!(err, PluginError::CompatibilityError { .. }));
254        assert!(err.to_string().contains("API version mismatch"));
255    }
256
257    #[test]
258    fn test_communication_error() {
259        let err = PluginError::communication("connection refused");
260        assert!(matches!(err, PluginError::CommunicationError { .. }));
261        assert!(err.to_string().contains("connection refused"));
262    }
263
264    #[test]
265    fn test_timeout_error() {
266        let err = PluginError::timeout(5000);
267        assert!(matches!(err, PluginError::TimeoutError { timeout_ms: 5000 }));
268        assert!(err.to_string().contains("5000"));
269    }
270
271    #[test]
272    fn test_wasm_error() {
273        let err = PluginError::wasm("invalid wasm module");
274        assert!(matches!(err, PluginError::WasmError { .. }));
275        assert!(err.to_string().contains("invalid wasm module"));
276    }
277
278    #[test]
279    fn test_manifest_error() {
280        let err = PluginError::manifest("missing required field");
281        assert!(matches!(err, PluginError::InvalidManifest { .. }));
282        assert!(err.to_string().contains("missing required field"));
283    }
284
285    #[test]
286    fn test_dependency_error() {
287        let err = PluginError::dependency("plugin-foo", "version not found");
288        assert!(matches!(err, PluginError::DependencyError { .. }));
289        let msg = err.to_string();
290        assert!(msg.contains("plugin-foo"));
291        assert!(msg.contains("version not found"));
292    }
293
294    #[test]
295    fn test_system_error() {
296        let err = PluginError::system("internal failure");
297        assert!(matches!(err, PluginError::SystemError { .. }));
298        assert!(err.to_string().contains("internal failure"));
299    }
300
301    // Check methods tests
302    #[test]
303    fn test_is_security_error() {
304        let err = PluginError::security("test");
305        assert!(err.is_security_error());
306
307        let err = PluginError::load("test");
308        assert!(!err.is_security_error());
309    }
310
311    #[test]
312    fn test_is_resource_error() {
313        let err = PluginError::resource_limit("memory", "100MB", "150MB");
314        assert!(err.is_resource_error());
315
316        let err = PluginError::execution("test");
317        assert!(!err.is_resource_error());
318    }
319
320    #[test]
321    fn test_is_timeout_error() {
322        let err = PluginError::timeout(1000);
323        assert!(err.is_timeout_error());
324
325        let err = PluginError::wasm("test");
326        assert!(!err.is_timeout_error());
327    }
328
329    // Error Display tests
330    #[test]
331    fn test_load_error_display() {
332        let err = PluginError::LoadError {
333            message: "test message".to_string(),
334        };
335        assert_eq!(err.to_string(), "Plugin loading error: test message");
336    }
337
338    #[test]
339    fn test_execution_error_display() {
340        let err = PluginError::ExecutionError {
341            message: "runtime failure".to_string(),
342        };
343        assert_eq!(err.to_string(), "Plugin execution error: runtime failure");
344    }
345
346    #[test]
347    fn test_security_violation_display() {
348        let err = PluginError::SecurityViolation {
349            violation: "access denied".to_string(),
350        };
351        assert_eq!(err.to_string(), "Security violation: access denied");
352    }
353
354    #[test]
355    fn test_resource_limit_display() {
356        let err = PluginError::ResourceLimitExceeded {
357            resource: "CPU".to_string(),
358            limit: "50%".to_string(),
359            used: "75%".to_string(),
360        };
361        assert_eq!(err.to_string(), "Resource limit exceeded: CPU limit=50%, used=75%");
362    }
363
364    #[test]
365    fn test_invalid_config_display() {
366        let err = PluginError::InvalidConfiguration {
367            field: "port".to_string(),
368            message: "invalid value".to_string(),
369        };
370        assert_eq!(err.to_string(), "Invalid plugin configuration: port - invalid value");
371    }
372
373    #[test]
374    fn test_timeout_display() {
375        let err = PluginError::TimeoutError { timeout_ms: 3000 };
376        assert_eq!(err.to_string(), "Plugin execution timeout: 3000ms exceeded");
377    }
378
379    #[test]
380    fn test_dependency_error_display() {
381        let err = PluginError::DependencyError {
382            dependency: "core-lib".to_string(),
383            message: "not found".to_string(),
384        };
385        assert_eq!(err.to_string(), "Plugin dependency error: core-lib - not found");
386    }
387
388    // Debug trait tests
389    #[test]
390    fn test_error_debug() {
391        let err = PluginError::load("test");
392        let debug = format!("{:?}", err);
393        assert!(debug.contains("LoadError"));
394    }
395
396    // String conversion tests
397    #[test]
398    fn test_load_error_from_string() {
399        let err = PluginError::load(String::from("dynamic message"));
400        assert!(err.to_string().contains("dynamic message"));
401    }
402
403    #[test]
404    fn test_load_error_from_str() {
405        let err = PluginError::load("static message");
406        assert!(err.to_string().contains("static message"));
407    }
408}