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}