1use crate::StatuteDiff;
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9use std::collections::HashMap;
10
11pub type PluginResult<T> = Result<T, PluginError>;
13
14#[derive(Debug, Clone, thiserror::Error)]
16pub enum PluginError {
17 #[error("Plugin initialization failed: {0}")]
19 InitializationFailed(String),
20
21 #[error("Plugin execution failed: {0}")]
23 ExecutionFailed(String),
24
25 #[error("Invalid plugin configuration: {0}")]
27 InvalidConfiguration(String),
28
29 #[error("Plugin not found: {0}")]
31 NotFound(String),
32
33 #[error("Missing plugin dependency: {0}")]
35 MissingDependency(String),
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PluginMetadata {
41 pub name: String,
43 pub version: String,
45 pub author: String,
47 pub description: String,
49 pub dependencies: Vec<String>,
51}
52
53impl PluginMetadata {
54 #[must_use]
56 pub fn new(
57 name: impl Into<String>,
58 version: impl Into<String>,
59 author: impl Into<String>,
60 description: impl Into<String>,
61 ) -> Self {
62 Self {
63 name: name.into(),
64 version: version.into(),
65 author: author.into(),
66 description: description.into(),
67 dependencies: Vec::new(),
68 }
69 }
70
71 #[must_use]
73 pub fn with_dependency(mut self, dep: impl Into<String>) -> Self {
74 self.dependencies.push(dep.into());
75 self
76 }
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AnalysisResult {
82 pub plugin_name: String,
84 pub findings: Vec<Finding>,
86 pub confidence: f64,
88 pub metadata: HashMap<String, String>,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct Finding {
95 pub severity: FindingSeverity,
97 pub category: String,
99 pub message: String,
101 pub location: Option<String>,
103 pub suggestion: Option<String>,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
109pub enum FindingSeverity {
110 Info,
112 Low,
114 Medium,
116 High,
118 Critical,
120}
121
122pub trait AnalyzerPlugin: Send + Sync {
124 fn metadata(&self) -> &PluginMetadata;
126
127 fn initialize(&mut self, config: &HashMap<String, String>) -> PluginResult<()> {
129 let _ = config;
130 Ok(())
131 }
132
133 fn analyze(&self, diff: &StatuteDiff) -> PluginResult<AnalysisResult>;
135
136 fn as_any(&self) -> &dyn Any;
138}
139
140pub trait ValidatorPlugin: Send + Sync {
142 fn metadata(&self) -> &PluginMetadata;
144
145 fn initialize(&mut self, config: &HashMap<String, String>) -> PluginResult<()> {
147 let _ = config;
148 Ok(())
149 }
150
151 fn validate(&self, diff: &StatuteDiff) -> PluginResult<ValidationResult>;
153
154 fn as_any(&self) -> &dyn Any;
156}
157
158#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ValidationResult {
161 pub passed: bool,
163 pub score: f64,
165 pub messages: Vec<String>,
167 pub failed_rules: Vec<String>,
169}
170
171pub trait TransformerPlugin: Send + Sync {
173 fn metadata(&self) -> &PluginMetadata;
175
176 fn initialize(&mut self, config: &HashMap<String, String>) -> PluginResult<()> {
178 let _ = config;
179 Ok(())
180 }
181
182 fn transform(&self, diff: StatuteDiff) -> PluginResult<StatuteDiff>;
184
185 fn as_any(&self) -> &dyn Any;
187}
188
189pub struct PluginRegistry {
191 analyzers: HashMap<String, Box<dyn AnalyzerPlugin>>,
192 validators: HashMap<String, Box<dyn ValidatorPlugin>>,
193 transformers: HashMap<String, Box<dyn TransformerPlugin>>,
194}
195
196impl Default for PluginRegistry {
197 fn default() -> Self {
198 Self::new()
199 }
200}
201
202impl PluginRegistry {
203 #[must_use]
205 pub fn new() -> Self {
206 Self {
207 analyzers: HashMap::new(),
208 validators: HashMap::new(),
209 transformers: HashMap::new(),
210 }
211 }
212
213 pub fn register_analyzer(&mut self, plugin: Box<dyn AnalyzerPlugin>) -> PluginResult<()> {
219 let name = plugin.metadata().name.clone();
220 if self.analyzers.contains_key(&name) {
221 return Err(PluginError::InitializationFailed(format!(
222 "Analyzer plugin '{}' already registered",
223 name
224 )));
225 }
226 self.analyzers.insert(name, plugin);
227 Ok(())
228 }
229
230 pub fn register_validator(&mut self, plugin: Box<dyn ValidatorPlugin>) -> PluginResult<()> {
236 let name = plugin.metadata().name.clone();
237 if self.validators.contains_key(&name) {
238 return Err(PluginError::InitializationFailed(format!(
239 "Validator plugin '{}' already registered",
240 name
241 )));
242 }
243 self.validators.insert(name, plugin);
244 Ok(())
245 }
246
247 pub fn register_transformer(&mut self, plugin: Box<dyn TransformerPlugin>) -> PluginResult<()> {
253 let name = plugin.metadata().name.clone();
254 if self.transformers.contains_key(&name) {
255 return Err(PluginError::InitializationFailed(format!(
256 "Transformer plugin '{}' already registered",
257 name
258 )));
259 }
260 self.transformers.insert(name, plugin);
261 Ok(())
262 }
263
264 #[must_use]
266 pub fn get_analyzer(&self, name: &str) -> Option<&dyn AnalyzerPlugin> {
267 self.analyzers.get(name).map(|p| p.as_ref())
268 }
269
270 #[must_use]
272 pub fn get_validator(&self, name: &str) -> Option<&dyn ValidatorPlugin> {
273 self.validators.get(name).map(|p| p.as_ref())
274 }
275
276 #[must_use]
278 pub fn get_transformer(&self, name: &str) -> Option<&dyn TransformerPlugin> {
279 self.transformers.get(name).map(|p| p.as_ref())
280 }
281
282 #[must_use]
284 pub fn list_analyzers(&self) -> Vec<&PluginMetadata> {
285 self.analyzers.values().map(|p| p.metadata()).collect()
286 }
287
288 #[must_use]
290 pub fn list_validators(&self) -> Vec<&PluginMetadata> {
291 self.validators.values().map(|p| p.metadata()).collect()
292 }
293
294 #[must_use]
296 pub fn list_transformers(&self) -> Vec<&PluginMetadata> {
297 self.transformers.values().map(|p| p.metadata()).collect()
298 }
299
300 pub fn analyze_all(&self, diff: &StatuteDiff) -> PluginResult<Vec<AnalysisResult>> {
306 let mut results = Vec::new();
307 for plugin in self.analyzers.values() {
308 results.push(plugin.analyze(diff)?);
309 }
310 Ok(results)
311 }
312
313 pub fn validate_all(&self, diff: &StatuteDiff) -> PluginResult<Vec<ValidationResult>> {
319 let mut results = Vec::new();
320 for plugin in self.validators.values() {
321 results.push(plugin.validate(diff)?);
322 }
323 Ok(results)
324 }
325
326 pub fn run_analyzer(&self, name: &str, diff: &StatuteDiff) -> PluginResult<AnalysisResult> {
332 let plugin = self
333 .get_analyzer(name)
334 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
335 plugin.analyze(diff)
336 }
337
338 pub fn run_validator(&self, name: &str, diff: &StatuteDiff) -> PluginResult<ValidationResult> {
344 let plugin = self
345 .get_validator(name)
346 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
347 plugin.validate(diff)
348 }
349
350 pub fn run_transformer(&self, name: &str, diff: StatuteDiff) -> PluginResult<StatuteDiff> {
356 let plugin = self
357 .get_transformer(name)
358 .ok_or_else(|| PluginError::NotFound(name.to_string()))?;
359 plugin.transform(diff)
360 }
361
362 pub fn check_dependencies(&self) -> PluginResult<()> {
368 for plugin in self.analyzers.values() {
370 for dep in &plugin.metadata().dependencies {
371 if !self.analyzers.contains_key(dep)
372 && !self.validators.contains_key(dep)
373 && !self.transformers.contains_key(dep)
374 {
375 return Err(PluginError::MissingDependency(format!(
376 "Plugin '{}' requires '{}'",
377 plugin.metadata().name,
378 dep
379 )));
380 }
381 }
382 }
383
384 for plugin in self.validators.values() {
386 for dep in &plugin.metadata().dependencies {
387 if !self.analyzers.contains_key(dep)
388 && !self.validators.contains_key(dep)
389 && !self.transformers.contains_key(dep)
390 {
391 return Err(PluginError::MissingDependency(format!(
392 "Plugin '{}' requires '{}'",
393 plugin.metadata().name,
394 dep
395 )));
396 }
397 }
398 }
399
400 for plugin in self.transformers.values() {
402 for dep in &plugin.metadata().dependencies {
403 if !self.analyzers.contains_key(dep)
404 && !self.validators.contains_key(dep)
405 && !self.transformers.contains_key(dep)
406 {
407 return Err(PluginError::MissingDependency(format!(
408 "Plugin '{}' requires '{}'",
409 plugin.metadata().name,
410 dep
411 )));
412 }
413 }
414 }
415
416 Ok(())
417 }
418}
419
420#[cfg(test)]
421mod tests {
422 use super::*;
423 use crate::{Severity, diff};
424 use legalis_core::{Effect, EffectType, Statute};
425
426 struct ExampleAnalyzer {
428 metadata: PluginMetadata,
429 }
430
431 impl ExampleAnalyzer {
432 fn new() -> Self {
433 Self {
434 metadata: PluginMetadata::new(
435 "example-analyzer",
436 "1.0.0",
437 "Test Author",
438 "Example analyzer plugin",
439 ),
440 }
441 }
442 }
443
444 impl AnalyzerPlugin for ExampleAnalyzer {
445 fn metadata(&self) -> &PluginMetadata {
446 &self.metadata
447 }
448
449 fn analyze(&self, diff: &StatuteDiff) -> PluginResult<AnalysisResult> {
450 let findings = if diff.changes.is_empty() {
451 vec![]
452 } else {
453 vec![Finding {
454 severity: FindingSeverity::Info,
455 category: "change-detection".to_string(),
456 message: format!("Detected {} changes", diff.changes.len()),
457 location: None,
458 suggestion: Some("Review changes carefully".to_string()),
459 }]
460 };
461
462 Ok(AnalysisResult {
463 plugin_name: self.metadata.name.clone(),
464 findings,
465 confidence: 1.0,
466 metadata: HashMap::new(),
467 })
468 }
469
470 fn as_any(&self) -> &dyn Any {
471 self
472 }
473 }
474
475 struct ExampleValidator {
477 metadata: PluginMetadata,
478 }
479
480 impl ExampleValidator {
481 fn new() -> Self {
482 Self {
483 metadata: PluginMetadata::new(
484 "example-validator",
485 "1.0.0",
486 "Test Author",
487 "Example validator plugin",
488 ),
489 }
490 }
491 }
492
493 impl ValidatorPlugin for ExampleValidator {
494 fn metadata(&self) -> &PluginMetadata {
495 &self.metadata
496 }
497
498 fn validate(&self, diff: &StatuteDiff) -> PluginResult<ValidationResult> {
499 let passed = diff.impact.severity != Severity::Breaking;
500 Ok(ValidationResult {
501 passed,
502 score: if passed { 1.0 } else { 0.0 },
503 messages: vec!["Validation complete".to_string()],
504 failed_rules: if passed {
505 vec![]
506 } else {
507 vec!["no-breaking-changes".to_string()]
508 },
509 })
510 }
511
512 fn as_any(&self) -> &dyn Any {
513 self
514 }
515 }
516
517 #[test]
518 fn test_plugin_metadata_creation() {
519 let metadata = PluginMetadata::new("test", "1.0.0", "Author", "Description")
520 .with_dependency("dep1")
521 .with_dependency("dep2");
522
523 assert_eq!(metadata.name, "test");
524 assert_eq!(metadata.version, "1.0.0");
525 assert_eq!(metadata.dependencies.len(), 2);
526 }
527
528 #[test]
529 fn test_analyzer_plugin() {
530 let statute1 = Statute::new("test", "Old", Effect::new(EffectType::Grant, "Benefit"));
531 let statute2 = Statute::new("test", "New", Effect::new(EffectType::Grant, "Benefit"));
532 let diff_result = diff(&statute1, &statute2).unwrap();
533
534 let plugin = ExampleAnalyzer::new();
535 let result = plugin.analyze(&diff_result).unwrap();
536
537 assert_eq!(result.plugin_name, "example-analyzer");
538 assert!(!result.findings.is_empty());
539 assert_eq!(result.confidence, 1.0);
540 }
541
542 #[test]
543 fn test_validator_plugin() {
544 let statute1 = Statute::new("test", "Old", Effect::new(EffectType::Grant, "Benefit"));
545 let statute2 = Statute::new("test", "New", Effect::new(EffectType::Grant, "Benefit"));
546 let diff_result = diff(&statute1, &statute2).unwrap();
547
548 let plugin = ExampleValidator::new();
549 let result = plugin.validate(&diff_result).unwrap();
550
551 assert!(result.passed);
552 assert_eq!(result.score, 1.0);
553 }
554
555 #[test]
556 fn test_plugin_registry() {
557 let mut registry = PluginRegistry::new();
558
559 registry
561 .register_analyzer(Box::new(ExampleAnalyzer::new()))
562 .unwrap();
563 registry
564 .register_validator(Box::new(ExampleValidator::new()))
565 .unwrap();
566
567 assert!(registry.get_analyzer("example-analyzer").is_some());
569 assert!(registry.get_validator("example-validator").is_some());
570
571 assert_eq!(registry.list_analyzers().len(), 1);
573 assert_eq!(registry.list_validators().len(), 1);
574 }
575
576 #[test]
577 fn test_run_specific_plugin() {
578 let mut registry = PluginRegistry::new();
579 registry
580 .register_analyzer(Box::new(ExampleAnalyzer::new()))
581 .unwrap();
582
583 let statute1 = Statute::new("test", "Old", Effect::new(EffectType::Grant, "Benefit"));
584 let statute2 = Statute::new("test", "New", Effect::new(EffectType::Grant, "Benefit"));
585 let diff_result = diff(&statute1, &statute2).unwrap();
586
587 let result = registry
588 .run_analyzer("example-analyzer", &diff_result)
589 .unwrap();
590 assert_eq!(result.plugin_name, "example-analyzer");
591 }
592
593 #[test]
594 fn test_plugin_not_found() {
595 let registry = PluginRegistry::new();
596 let statute1 = Statute::new("test", "Old", Effect::new(EffectType::Grant, "Benefit"));
597 let statute2 = Statute::new("test", "New", Effect::new(EffectType::Grant, "Benefit"));
598 let diff_result = diff(&statute1, &statute2).unwrap();
599
600 let result = registry.run_analyzer("nonexistent", &diff_result);
601 assert!(result.is_err());
602 }
603
604 #[test]
605 fn test_duplicate_registration() {
606 let mut registry = PluginRegistry::new();
607 registry
608 .register_analyzer(Box::new(ExampleAnalyzer::new()))
609 .unwrap();
610
611 let result = registry.register_analyzer(Box::new(ExampleAnalyzer::new()));
612 assert!(result.is_err());
613 }
614
615 #[test]
616 fn test_dependency_checking() {
617 let mut registry = PluginRegistry::new();
618
619 let mut analyzer = ExampleAnalyzer::new();
620 analyzer
621 .metadata
622 .dependencies
623 .push("nonexistent".to_string());
624
625 registry.register_analyzer(Box::new(analyzer)).unwrap();
626
627 let result = registry.check_dependencies();
628 assert!(result.is_err());
629 }
630}