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 {
60 message: String,
62 },
63
64 #[error("Plugin validation error: {message}")]
66 ValidationError {
67 message: String,
69 },
70
71 #[error("Security violation: {violation}")]
73 SecurityViolation {
74 violation: String,
76 },
77
78 #[error("Plugin manifest error: {message}")]
80 ManifestError {
81 message: String,
83 },
84
85 #[error("WebAssembly module error: {message}")]
87 WasmError {
88 message: String,
90 },
91
92 #[error("File system error: {message}")]
94 FsError {
95 message: String,
97 },
98
99 #[error("Plugin already loaded: {plugin_id}")]
101 AlreadyLoaded {
102 plugin_id: PluginId,
104 },
105
106 #[error("Plugin not found: {plugin_id}")]
108 NotFound {
109 plugin_id: PluginId,
111 },
112
113 #[error("Plugin dependency error: {message}")]
115 DependencyError {
116 message: String,
118 },
119
120 #[error("Resource limit exceeded: {message}")]
122 ResourceLimit {
123 message: String,
125 },
126
127 #[error("Plugin execution error: {message}")]
129 ExecutionError {
130 message: String,
132 },
133}
134
135impl PluginLoaderError {
136 pub fn load<S: Into<String>>(message: S) -> Self {
138 Self::LoadError {
139 message: message.into(),
140 }
141 }
142
143 pub fn validation<S: Into<String>>(message: S) -> Self {
145 Self::ValidationError {
146 message: message.into(),
147 }
148 }
149
150 pub fn security<S: Into<String>>(violation: S) -> Self {
152 Self::SecurityViolation {
153 violation: violation.into(),
154 }
155 }
156
157 pub fn manifest<S: Into<String>>(message: S) -> Self {
159 Self::ManifestError {
160 message: message.into(),
161 }
162 }
163
164 pub fn wasm<S: Into<String>>(message: S) -> Self {
166 Self::WasmError {
167 message: message.into(),
168 }
169 }
170
171 pub fn fs<S: Into<String>>(message: S) -> Self {
173 Self::FsError {
174 message: message.into(),
175 }
176 }
177
178 pub fn already_loaded(plugin_id: PluginId) -> Self {
180 Self::AlreadyLoaded { plugin_id }
181 }
182
183 pub fn not_found(plugin_id: PluginId) -> Self {
185 Self::NotFound { plugin_id }
186 }
187
188 pub fn dependency<S: Into<String>>(message: S) -> Self {
190 Self::DependencyError {
191 message: message.into(),
192 }
193 }
194
195 pub fn resource_limit<S: Into<String>>(message: S) -> Self {
197 Self::ResourceLimit {
198 message: message.into(),
199 }
200 }
201
202 pub fn execution<S: Into<String>>(message: S) -> Self {
204 Self::ExecutionError {
205 message: message.into(),
206 }
207 }
208
209 pub fn is_security_error(&self) -> bool {
211 matches!(self, PluginLoaderError::SecurityViolation { .. })
212 }
213}
214
215#[derive(Debug, Clone)]
217pub struct PluginLoaderConfig {
218 pub plugin_dirs: Vec<String>,
220 pub allow_unsigned: bool,
222 pub trusted_keys: Vec<String>,
224 pub key_data: std::collections::HashMap<String, Vec<u8>>,
226 pub max_plugins: usize,
228 pub load_timeout_secs: u64,
230 pub debug_logging: bool,
232 pub skip_wasm_validation: bool,
234}
235
236impl Default for PluginLoaderConfig {
237 fn default() -> Self {
238 Self {
239 plugin_dirs: vec!["~/.mockforge/plugins".to_string(), "./plugins".to_string()],
240 allow_unsigned: false,
241 trusted_keys: vec!["trusted-dev-key".to_string()],
242 key_data: std::collections::HashMap::new(),
243 max_plugins: 100,
244 load_timeout_secs: 30,
245 debug_logging: false,
246 skip_wasm_validation: false,
247 }
248 }
249}
250
251#[derive(Debug, Clone)]
253pub struct PluginLoadContext {
254 pub plugin_id: PluginId,
256 pub manifest: PluginManifest,
258 pub plugin_path: String,
260 pub load_time: chrono::DateTime<chrono::Utc>,
262 pub config: PluginLoaderConfig,
264}
265
266impl PluginLoadContext {
267 pub fn new(
269 plugin_id: PluginId,
270 manifest: PluginManifest,
271 plugin_path: String,
272 config: PluginLoaderConfig,
273 ) -> Self {
274 Self {
275 plugin_id,
276 manifest,
277 plugin_path,
278 load_time: chrono::Utc::now(),
279 config,
280 }
281 }
282}
283
284#[derive(Debug, Clone, Default)]
286pub struct PluginLoadStats {
287 pub discovered: usize,
289 pub loaded: usize,
291 pub failed: usize,
293 pub skipped: usize,
295 pub start_time: Option<chrono::DateTime<chrono::Utc>>,
297 pub end_time: Option<chrono::DateTime<chrono::Utc>>,
299}
300
301impl PluginLoadStats {
302 pub fn start_loading(&mut self) {
304 self.start_time = Some(chrono::Utc::now());
305 }
306
307 pub fn finish_loading(&mut self) {
309 self.end_time = Some(chrono::Utc::now());
310 }
311
312 pub fn record_success(&mut self) {
314 self.loaded += 1;
315 self.discovered += 1;
316 }
317
318 pub fn record_failure(&mut self) {
320 self.failed += 1;
321 self.discovered += 1;
322 }
323
324 pub fn record_skipped(&mut self) {
326 self.skipped += 1;
327 self.discovered += 1;
328 }
329
330 pub fn duration(&self) -> Option<chrono::Duration> {
332 match (self.start_time, self.end_time) {
333 (Some(start), Some(end)) => Some(end - start),
334 _ => None,
335 }
336 }
337
338 pub fn success_rate(&self) -> f64 {
340 if self.discovered == 0 {
341 1.0 } else {
343 (self.loaded as f64 / self.discovered as f64) * 100.0
344 }
345 }
346
347 pub fn total_plugins(&self) -> usize {
349 self.loaded + self.failed + self.skipped
350 }
351}
352
353#[derive(Debug, Clone)]
355pub struct PluginDiscovery {
356 pub plugin_id: PluginId,
358 pub manifest: PluginManifest,
360 pub path: String,
362 pub is_valid: bool,
364 pub errors: Vec<String>,
366}
367
368impl PluginDiscovery {
369 pub fn success(plugin_id: PluginId, manifest: PluginManifest, path: String) -> Self {
371 Self {
372 plugin_id,
373 manifest,
374 path,
375 is_valid: true,
376 errors: Vec::new(),
377 }
378 }
379
380 pub fn failure(plugin_id: PluginId, path: String, errors: Vec<String>) -> Self {
382 let plugin_id_clone = PluginId(plugin_id.0.clone());
383 Self {
384 plugin_id,
385 manifest: PluginManifest::new(PluginInfo::new(
386 plugin_id_clone,
387 PluginVersion::new(0, 0, 0),
388 "Unknown",
389 "Plugin failed to load",
390 PluginAuthor::new("unknown"),
391 )),
392 path,
393 is_valid: false,
394 errors,
395 }
396 }
397
398 pub fn is_success(&self) -> bool {
400 self.is_valid
401 }
402
403 pub fn first_error(&self) -> Option<&str> {
405 self.errors.first().map(|s| s.as_str())
406 }
407}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 #[test]
414 fn test_plugin_loader_error_types() {
415 let load_error = PluginLoaderError::LoadError {
416 message: "test error".to_string(),
417 };
418 assert!(matches!(load_error, PluginLoaderError::LoadError { .. }));
419
420 let validation_error = PluginLoaderError::ValidationError {
421 message: "validation failed".to_string(),
422 };
423 assert!(matches!(validation_error, PluginLoaderError::ValidationError { .. }));
424 }
425
426 #[test]
427 fn test_plugin_discovery_success() {
428 let plugin_id = PluginId("test-plugin".to_string());
429 let manifest = PluginManifest::new(PluginInfo::new(
430 plugin_id.clone(),
431 PluginVersion::new(1, 0, 0),
432 "Test Plugin",
433 "A test plugin",
434 PluginAuthor::new("test-author"),
435 ));
436
437 let result = PluginDiscovery::success(plugin_id, manifest, "/path/to/plugin".to_string());
438
439 assert!(result.is_success());
440 assert!(result.first_error().is_none());
441 }
442
443 #[test]
444 fn test_plugin_discovery_failure() {
445 let plugin_id = PluginId("failing-plugin".to_string());
446 let errors = vec!["Error 1".to_string(), "Error 2".to_string()];
447
448 let result =
449 PluginDiscovery::failure(plugin_id, "/path/to/plugin".to_string(), errors.clone());
450
451 assert!(!result.is_success());
452 assert_eq!(result.first_error(), Some("Error 1"));
453 assert_eq!(result.errors.len(), 2);
454 }
455
456 #[test]
457 fn test_module_exports() {
458 let _ = std::marker::PhantomData::<PluginLoader>;
460 let _ = std::marker::PhantomData::<PluginRegistry>;
461 let _ = std::marker::PhantomData::<PluginValidator>;
462 }
464}