Skip to main content

legalis_diff/
plugins.rs

1//! Plugin system for extending diff analysis capabilities.
2//!
3//! This module provides a flexible plugin architecture for adding custom analyzers,
4//! validators, and transformers to the diff system.
5
6use crate::StatuteDiff;
7use serde::{Deserialize, Serialize};
8use std::any::Any;
9use std::collections::HashMap;
10
11/// Result type for plugin operations.
12pub type PluginResult<T> = Result<T, PluginError>;
13
14/// Errors that can occur in plugin operations.
15#[derive(Debug, Clone, thiserror::Error)]
16pub enum PluginError {
17    /// Plugin initialization failed
18    #[error("Plugin initialization failed: {0}")]
19    InitializationFailed(String),
20
21    /// Plugin execution failed
22    #[error("Plugin execution failed: {0}")]
23    ExecutionFailed(String),
24
25    /// Plugin configuration invalid
26    #[error("Invalid plugin configuration: {0}")]
27    InvalidConfiguration(String),
28
29    /// Plugin not found
30    #[error("Plugin not found: {0}")]
31    NotFound(String),
32
33    /// Plugin dependency missing
34    #[error("Missing plugin dependency: {0}")]
35    MissingDependency(String),
36}
37
38/// Metadata about a plugin.
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct PluginMetadata {
41    /// Plugin name
42    pub name: String,
43    /// Plugin version
44    pub version: String,
45    /// Plugin author
46    pub author: String,
47    /// Plugin description
48    pub description: String,
49    /// Plugin dependencies (names of other plugins)
50    pub dependencies: Vec<String>,
51}
52
53impl PluginMetadata {
54    /// Creates new plugin metadata.
55    #[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    /// Adds a dependency to the plugin.
72    #[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/// Analysis result from a custom analyzer plugin.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct AnalysisResult {
82    /// Plugin that generated this result
83    pub plugin_name: String,
84    /// Analysis findings
85    pub findings: Vec<Finding>,
86    /// Confidence score (0.0 to 1.0)
87    pub confidence: f64,
88    /// Additional metadata
89    pub metadata: HashMap<String, String>,
90}
91
92/// A finding from plugin analysis.
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub struct Finding {
95    /// Finding severity
96    pub severity: FindingSeverity,
97    /// Finding category
98    pub category: String,
99    /// Finding message
100    pub message: String,
101    /// Location in diff (optional)
102    pub location: Option<String>,
103    /// Suggested action
104    pub suggestion: Option<String>,
105}
106
107/// Severity level for findings.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
109pub enum FindingSeverity {
110    /// Informational finding
111    Info,
112    /// Low severity
113    Low,
114    /// Medium severity
115    Medium,
116    /// High severity
117    High,
118    /// Critical severity
119    Critical,
120}
121
122/// Trait for custom diff analyzer plugins.
123pub trait AnalyzerPlugin: Send + Sync {
124    /// Returns plugin metadata.
125    fn metadata(&self) -> &PluginMetadata;
126
127    /// Initializes the plugin with configuration.
128    fn initialize(&mut self, config: &HashMap<String, String>) -> PluginResult<()> {
129        let _ = config;
130        Ok(())
131    }
132
133    /// Analyzes a diff and returns findings.
134    fn analyze(&self, diff: &StatuteDiff) -> PluginResult<AnalysisResult>;
135
136    /// Returns the plugin as Any for downcasting.
137    fn as_any(&self) -> &dyn Any;
138}
139
140/// Trait for custom validator plugins.
141pub trait ValidatorPlugin: Send + Sync {
142    /// Returns plugin metadata.
143    fn metadata(&self) -> &PluginMetadata;
144
145    /// Initializes the plugin with configuration.
146    fn initialize(&mut self, config: &HashMap<String, String>) -> PluginResult<()> {
147        let _ = config;
148        Ok(())
149    }
150
151    /// Validates a diff.
152    fn validate(&self, diff: &StatuteDiff) -> PluginResult<ValidationResult>;
153
154    /// Returns the plugin as Any for downcasting.
155    fn as_any(&self) -> &dyn Any;
156}
157
158/// Result of validation.
159#[derive(Debug, Clone, Serialize, Deserialize)]
160pub struct ValidationResult {
161    /// Whether validation passed
162    pub passed: bool,
163    /// Validation score (0.0 to 1.0)
164    pub score: f64,
165    /// Validation messages
166    pub messages: Vec<String>,
167    /// Failed rules
168    pub failed_rules: Vec<String>,
169}
170
171/// Trait for custom transformer plugins.
172pub trait TransformerPlugin: Send + Sync {
173    /// Returns plugin metadata.
174    fn metadata(&self) -> &PluginMetadata;
175
176    /// Initializes the plugin with configuration.
177    fn initialize(&mut self, config: &HashMap<String, String>) -> PluginResult<()> {
178        let _ = config;
179        Ok(())
180    }
181
182    /// Transforms a diff.
183    fn transform(&self, diff: StatuteDiff) -> PluginResult<StatuteDiff>;
184
185    /// Returns the plugin as Any for downcasting.
186    fn as_any(&self) -> &dyn Any;
187}
188
189/// Plugin registry for managing all plugins.
190pub 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    /// Creates a new plugin registry.
204    #[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    /// Registers an analyzer plugin.
214    ///
215    /// # Errors
216    ///
217    /// Returns error if plugin with same name already exists.
218    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    /// Registers a validator plugin.
231    ///
232    /// # Errors
233    ///
234    /// Returns error if plugin with same name already exists.
235    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    /// Registers a transformer plugin.
248    ///
249    /// # Errors
250    ///
251    /// Returns error if plugin with same name already exists.
252    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    /// Gets an analyzer plugin by name.
265    #[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    /// Gets a validator plugin by name.
271    #[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    /// Gets a transformer plugin by name.
277    #[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    /// Lists all registered analyzer plugins.
283    #[must_use]
284    pub fn list_analyzers(&self) -> Vec<&PluginMetadata> {
285        self.analyzers.values().map(|p| p.metadata()).collect()
286    }
287
288    /// Lists all registered validator plugins.
289    #[must_use]
290    pub fn list_validators(&self) -> Vec<&PluginMetadata> {
291        self.validators.values().map(|p| p.metadata()).collect()
292    }
293
294    /// Lists all registered transformer plugins.
295    #[must_use]
296    pub fn list_transformers(&self) -> Vec<&PluginMetadata> {
297        self.transformers.values().map(|p| p.metadata()).collect()
298    }
299
300    /// Runs all analyzer plugins on a diff.
301    ///
302    /// # Errors
303    ///
304    /// Returns error if any plugin fails to execute.
305    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    /// Runs all validator plugins on a diff.
314    ///
315    /// # Errors
316    ///
317    /// Returns error if any plugin fails to execute.
318    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    /// Runs a specific analyzer plugin.
327    ///
328    /// # Errors
329    ///
330    /// Returns error if plugin not found or execution fails.
331    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    /// Runs a specific validator plugin.
339    ///
340    /// # Errors
341    ///
342    /// Returns error if plugin not found or execution fails.
343    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    /// Runs a specific transformer plugin.
351    ///
352    /// # Errors
353    ///
354    /// Returns error if plugin not found or execution fails.
355    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    /// Checks if all dependencies for plugins are satisfied.
363    ///
364    /// # Errors
365    ///
366    /// Returns error if any dependencies are missing.
367    pub fn check_dependencies(&self) -> PluginResult<()> {
368        // Check analyzer dependencies
369        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        // Check validator dependencies
385        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        // Check transformer dependencies
401        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    // Example analyzer plugin
427    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    // Example validator plugin
476    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        // Register plugins
560        registry
561            .register_analyzer(Box::new(ExampleAnalyzer::new()))
562            .unwrap();
563        registry
564            .register_validator(Box::new(ExampleValidator::new()))
565            .unwrap();
566
567        // Check plugins are registered
568        assert!(registry.get_analyzer("example-analyzer").is_some());
569        assert!(registry.get_validator("example-validator").is_some());
570
571        // List plugins
572        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}