1use std::path::Path;
19use std::path::PathBuf;
20
21use mockforge_plugin_core::{
23    PluginAuthor, PluginId, PluginInfo, PluginInstance, PluginManifest, PluginVersion,
24};
25
26pub mod git;
27pub mod installer;
28pub mod loader;
29pub mod metadata;
30pub mod registry;
31pub mod remote;
32pub mod runtime_adapter;
33pub mod sandbox;
34pub mod signature;
35pub mod signature_gen;
36pub mod validator;
37
38pub use git::*;
40pub use installer::*;
41pub use loader::*;
42pub use metadata::*;
43pub use registry::*;
44pub use remote::*;
45pub use runtime_adapter::*;
46pub use sandbox::*;
47pub use signature::*;
48pub use signature_gen::*;
49pub use validator::*;
50
51pub type LoaderResult<T> = std::result::Result<T, PluginLoaderError>;
53
54#[derive(Debug, thiserror::Error)]
56pub enum PluginLoaderError {
57    #[error("Plugin loading error: {message}")]
59    LoadError { message: String },
60
61    #[error("Plugin validation error: {message}")]
63    ValidationError { message: String },
64
65    #[error("Security violation: {violation}")]
67    SecurityViolation { violation: String },
68
69    #[error("Plugin manifest error: {message}")]
71    ManifestError { message: String },
72
73    #[error("WebAssembly module error: {message}")]
75    WasmError { message: String },
76
77    #[error("File system error: {message}")]
79    FsError { message: String },
80
81    #[error("Plugin already loaded: {plugin_id}")]
83    AlreadyLoaded { plugin_id: PluginId },
84
85    #[error("Plugin not found: {plugin_id}")]
87    NotFound { plugin_id: PluginId },
88
89    #[error("Plugin dependency error: {message}")]
91    DependencyError { message: String },
92
93    #[error("Resource limit exceeded: {message}")]
95    ResourceLimit { message: String },
96
97    #[error("Plugin execution error: {message}")]
99    ExecutionError { message: String },
100}
101
102impl PluginLoaderError {
103    pub fn load<S: Into<String>>(message: S) -> Self {
105        Self::LoadError {
106            message: message.into(),
107        }
108    }
109
110    pub fn validation<S: Into<String>>(message: S) -> Self {
112        Self::ValidationError {
113            message: message.into(),
114        }
115    }
116
117    pub fn security<S: Into<String>>(violation: S) -> Self {
119        Self::SecurityViolation {
120            violation: violation.into(),
121        }
122    }
123
124    pub fn manifest<S: Into<String>>(message: S) -> Self {
126        Self::ManifestError {
127            message: message.into(),
128        }
129    }
130
131    pub fn wasm<S: Into<String>>(message: S) -> Self {
133        Self::WasmError {
134            message: message.into(),
135        }
136    }
137
138    pub fn fs<S: Into<String>>(message: S) -> Self {
140        Self::FsError {
141            message: message.into(),
142        }
143    }
144
145    pub fn already_loaded(plugin_id: PluginId) -> Self {
147        Self::AlreadyLoaded { plugin_id }
148    }
149
150    pub fn not_found(plugin_id: PluginId) -> Self {
152        Self::NotFound { plugin_id }
153    }
154
155    pub fn dependency<S: Into<String>>(message: S) -> Self {
157        Self::DependencyError {
158            message: message.into(),
159        }
160    }
161
162    pub fn resource_limit<S: Into<String>>(message: S) -> Self {
164        Self::ResourceLimit {
165            message: message.into(),
166        }
167    }
168
169    pub fn execution<S: Into<String>>(message: S) -> Self {
171        Self::ExecutionError {
172            message: message.into(),
173        }
174    }
175
176    pub fn is_security_error(&self) -> bool {
178        matches!(self, PluginLoaderError::SecurityViolation { .. })
179    }
180}
181
182#[derive(Debug, Clone)]
184pub struct PluginLoaderConfig {
185    pub plugin_dirs: Vec<String>,
187    pub allow_unsigned: bool,
189    pub trusted_keys: Vec<String>,
191    pub key_data: std::collections::HashMap<String, Vec<u8>>,
193    pub max_plugins: usize,
195    pub load_timeout_secs: u64,
197    pub debug_logging: bool,
199    pub skip_wasm_validation: bool,
201}
202
203impl Default for PluginLoaderConfig {
204    fn default() -> Self {
205        Self {
206            plugin_dirs: vec!["~/.mockforge/plugins".to_string(), "./plugins".to_string()],
207            allow_unsigned: false,
208            trusted_keys: vec!["trusted-dev-key".to_string()],
209            key_data: std::collections::HashMap::new(),
210            max_plugins: 100,
211            load_timeout_secs: 30,
212            debug_logging: false,
213            skip_wasm_validation: false,
214        }
215    }
216}
217
218#[derive(Debug, Clone)]
220pub struct PluginLoadContext {
221    pub plugin_id: PluginId,
223    pub manifest: PluginManifest,
225    pub plugin_path: String,
227    pub load_time: chrono::DateTime<chrono::Utc>,
229    pub config: PluginLoaderConfig,
231}
232
233impl PluginLoadContext {
234    pub fn new(
236        plugin_id: PluginId,
237        manifest: PluginManifest,
238        plugin_path: String,
239        config: PluginLoaderConfig,
240    ) -> Self {
241        Self {
242            plugin_id,
243            manifest,
244            plugin_path,
245            load_time: chrono::Utc::now(),
246            config,
247        }
248    }
249}
250
251#[derive(Debug, Clone, Default)]
253pub struct PluginLoadStats {
254    pub discovered: usize,
256    pub loaded: usize,
258    pub failed: usize,
260    pub skipped: usize,
262    pub start_time: Option<chrono::DateTime<chrono::Utc>>,
264    pub end_time: Option<chrono::DateTime<chrono::Utc>>,
266}
267
268impl PluginLoadStats {
269    pub fn start_loading(&mut self) {
271        self.start_time = Some(chrono::Utc::now());
272    }
273
274    pub fn finish_loading(&mut self) {
276        self.end_time = Some(chrono::Utc::now());
277    }
278
279    pub fn record_success(&mut self) {
281        self.loaded += 1;
282        self.discovered += 1;
283    }
284
285    pub fn record_failure(&mut self) {
287        self.failed += 1;
288        self.discovered += 1;
289    }
290
291    pub fn record_skipped(&mut self) {
293        self.skipped += 1;
294        self.discovered += 1;
295    }
296
297    pub fn duration(&self) -> Option<chrono::Duration> {
299        match (self.start_time, self.end_time) {
300            (Some(start), Some(end)) => Some(end - start),
301            _ => None,
302        }
303    }
304
305    pub fn success_rate(&self) -> f64 {
307        if self.discovered == 0 {
308            1.0 } else {
310            (self.loaded as f64 / self.discovered as f64) * 100.0
311        }
312    }
313
314    pub fn total_plugins(&self) -> usize {
316        self.loaded + self.failed + self.skipped
317    }
318}
319
320#[derive(Debug, Clone)]
322pub struct PluginDiscovery {
323    pub plugin_id: PluginId,
325    pub manifest: PluginManifest,
327    pub path: String,
329    pub is_valid: bool,
331    pub errors: Vec<String>,
333}
334
335impl PluginDiscovery {
336    pub fn success(plugin_id: PluginId, manifest: PluginManifest, path: String) -> Self {
338        Self {
339            plugin_id,
340            manifest,
341            path,
342            is_valid: true,
343            errors: Vec::new(),
344        }
345    }
346
347    pub fn failure(plugin_id: PluginId, path: String, errors: Vec<String>) -> Self {
349        let plugin_id_clone = PluginId(plugin_id.0.clone());
350        Self {
351            plugin_id,
352            manifest: PluginManifest::new(PluginInfo::new(
353                plugin_id_clone,
354                PluginVersion::new(0, 0, 0),
355                "Unknown",
356                "Plugin failed to load",
357                PluginAuthor::new("unknown"),
358            )),
359            path,
360            is_valid: false,
361            errors,
362        }
363    }
364
365    pub fn is_success(&self) -> bool {
367        self.is_valid
368    }
369
370    pub fn first_error(&self) -> Option<&str> {
372        self.errors.first().map(|s| s.as_str())
373    }
374}
375
376#[cfg(test)]
377mod tests {
378    use super::*;
379
380    #[test]
381    fn test_plugin_loader_error_types() {
382        let load_error = PluginLoaderError::LoadError {
383            message: "test error".to_string(),
384        };
385        assert!(matches!(load_error, PluginLoaderError::LoadError { .. }));
386
387        let validation_error = PluginLoaderError::ValidationError {
388            message: "validation failed".to_string(),
389        };
390        assert!(matches!(validation_error, PluginLoaderError::ValidationError { .. }));
391    }
392
393    #[test]
394    fn test_plugin_discovery_success() {
395        let plugin_id = PluginId("test-plugin".to_string());
396        let manifest = PluginManifest::new(PluginInfo::new(
397            plugin_id.clone(),
398            PluginVersion::new(1, 0, 0),
399            "Test Plugin",
400            "A test plugin",
401            PluginAuthor::new("test-author"),
402        ));
403
404        let result = PluginDiscovery::success(plugin_id, manifest, "/path/to/plugin".to_string());
405
406        assert!(result.is_success());
407        assert!(result.first_error().is_none());
408    }
409
410    #[test]
411    fn test_plugin_discovery_failure() {
412        let plugin_id = PluginId("failing-plugin".to_string());
413        let errors = vec!["Error 1".to_string(), "Error 2".to_string()];
414
415        let result =
416            PluginDiscovery::failure(plugin_id, "/path/to/plugin".to_string(), errors.clone());
417
418        assert!(!result.is_success());
419        assert_eq!(result.first_error(), Some("Error 1"));
420        assert_eq!(result.errors.len(), 2);
421    }
422
423    #[test]
424    fn test_module_exports() {
425        let _ = std::marker::PhantomData::<PluginLoader>;
427        let _ = std::marker::PhantomData::<PluginRegistry>;
428        let _ = std::marker::PhantomData::<PluginValidator>;
429        }
431}