rust_rule_engine/engine/
module.rs

1//! Module System (CLIPS-inspired defmodule)
2//!
3//! Provides namespace isolation and visibility control for large knowledge bases.
4//! Similar to CLIPS defmodule, Drools rule units, and package systems.
5//!
6//! # Features
7//!
8//! - **Module Isolation**: Separate namespaces for rules, templates, and facts
9//! - **Import/Export Control**: Fine-grained visibility management
10//! - **Module Focus**: Control execution flow across modules
11//! - **Pattern Matching**: Export/import with wildcards (e.g., "sensor-*")
12//!
13//! # Example
14//!
15//! ```rust
16//! use rust_rule_engine::engine::module::{ModuleManager, ExportList, ImportType};
17//!
18//! let mut manager = ModuleManager::new();
19//!
20//! // Create modules
21//! manager.create_module("SENSORS").unwrap();
22//! manager.create_module("CONTROL").unwrap();
23//!
24//! // Configure exports
25//! manager.export_all_from("SENSORS", ExportList::All).unwrap();
26//!
27//! // Configure imports
28//! manager.import_from("CONTROL", "SENSORS", ImportType::AllTemplates, "*").unwrap();
29//!
30//! // Set focus
31//! manager.set_focus("CONTROL").unwrap();
32//! ```
33
34use std::collections::{HashMap, HashSet, VecDeque};
35use crate::errors::{Result, RuleEngineError};
36
37/// Type of item that can be exported/imported
38#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum ItemType {
40    /// Rule
41    Rule,
42    /// Template (deftemplate)
43    Template,
44    /// Fact
45    Fact,
46    /// All items
47    All,
48}
49
50/// Export list specification
51#[derive(Debug, Clone, PartialEq)]
52pub enum ExportList {
53    /// Export everything (default for MAIN module)
54    All,
55    /// Export nothing (default for user modules)
56    None,
57    /// Export specific items matching patterns
58    Specific(Vec<ExportItem>),
59}
60
61/// Single export item
62#[derive(Debug, Clone, PartialEq)]
63pub struct ExportItem {
64    /// Type of item to export
65    pub item_type: ItemType,
66    /// Name or pattern (supports wildcards like "sensor-*")
67    pub pattern: String,
68}
69
70/// Import type specification
71#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum ImportType {
73    /// Import all rules
74    AllRules,
75    /// Import all templates
76    AllTemplates,
77    /// Import specific rules
78    Rules,
79    /// Import specific templates
80    Templates,
81    /// Import everything
82    All,
83}
84
85/// Import declaration
86#[derive(Debug, Clone, PartialEq)]
87pub struct ImportDecl {
88    /// Module to import from
89    pub from_module: String,
90    /// Type of items to import
91    pub import_type: ImportType,
92    /// Pattern to match (supports wildcards)
93    pub pattern: String,
94}
95
96/// A module in the knowledge base
97#[derive(Debug, Clone)]
98pub struct Module {
99    /// Module name
100    pub name: String,
101    /// Rules owned by this module
102    rules: HashSet<String>,
103    /// Templates owned by this module
104    templates: HashSet<String>,
105    /// Facts owned by this module (by type)
106    fact_types: HashSet<String>,
107    /// Export specification
108    exports: ExportList,
109    /// Import declarations
110    imports: Vec<ImportDecl>,
111    /// Module documentation
112    pub doc: Option<String>,
113}
114
115impl Module {
116    /// Create a new module
117    pub fn new(name: impl Into<String>) -> Self {
118        let name = name.into();
119        let exports = if name == "MAIN" {
120            ExportList::All
121        } else {
122            ExportList::None
123        };
124
125        Self {
126            name,
127            rules: HashSet::new(),
128            templates: HashSet::new(),
129            fact_types: HashSet::new(),
130            exports,
131            imports: Vec::new(),
132            doc: None,
133        }
134    }
135
136    /// Add documentation
137    pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
138        self.doc = Some(doc.into());
139        self
140    }
141
142    /// Add a rule to this module
143    pub fn add_rule(&mut self, rule_name: impl Into<String>) {
144        self.rules.insert(rule_name.into());
145    }
146
147    /// Add a template to this module
148    pub fn add_template(&mut self, template_name: impl Into<String>) {
149        self.templates.insert(template_name.into());
150    }
151
152    /// Add a fact type to this module
153    pub fn add_fact_type(&mut self, fact_type: impl Into<String>) {
154        self.fact_types.insert(fact_type.into());
155    }
156
157    /// Set export specification
158    pub fn set_exports(&mut self, exports: ExportList) {
159        self.exports = exports;
160    }
161
162    /// Get export specification
163    pub fn get_exports(&self) -> &ExportList {
164        &self.exports
165    }
166
167    /// Add an import declaration
168    pub fn add_import(&mut self, import: ImportDecl) {
169        self.imports.push(import);
170    }
171
172    /// Check if this module exports a rule
173    pub fn exports_rule(&self, rule_name: &str) -> bool {
174        match &self.exports {
175            ExportList::All => self.rules.contains(rule_name),
176            ExportList::None => false,
177            ExportList::Specific(items) => {
178                items.iter().any(|item| {
179                    matches!(item.item_type, ItemType::Rule | ItemType::All)
180                        && self.rules.contains(rule_name)
181                        && pattern_matches(&item.pattern, rule_name)
182                })
183            }
184        }
185    }
186
187    /// Check if this module exports a template
188    pub fn exports_template(&self, template_name: &str) -> bool {
189        match &self.exports {
190            ExportList::All => self.templates.contains(template_name),
191            ExportList::None => false,
192            ExportList::Specific(items) => {
193                items.iter().any(|item| {
194                    matches!(item.item_type, ItemType::Template | ItemType::All)
195                        && self.templates.contains(template_name)
196                        && pattern_matches(&item.pattern, template_name)
197                })
198            }
199        }
200    }
201
202    /// Get all rules in this module
203    pub fn get_rules(&self) -> &HashSet<String> {
204        &self.rules
205    }
206
207    /// Get all templates in this module
208    pub fn get_templates(&self) -> &HashSet<String> {
209        &self.templates
210    }
211
212    /// Get all imports
213    pub fn get_imports(&self) -> &[ImportDecl] {
214        &self.imports
215    }
216}
217
218/// Error type for cyclic import detection
219#[derive(Debug, Clone)]
220pub struct CycleError {
221    /// The cycle that was detected (list of module names forming the cycle)
222    pub cycle_path: Vec<String>,
223}
224
225impl std::fmt::Display for CycleError {
226    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
227        write!(f, "Cyclic import detected: {}", self.cycle_path.join(" -> "))
228    }
229}
230
231/// Module manager for organizing knowledge bases
232#[derive(Debug, Clone)]
233pub struct ModuleManager {
234    /// All modules
235    modules: HashMap<String, Module>,
236    /// Current focus module (for execution)
237    current_focus: String,
238    /// Default module name
239    default_module: String,
240    /// Track import graph for cycle detection
241    import_graph: HashMap<String, HashSet<String>>,
242}
243
244impl ModuleManager {
245    /// Create a new module manager
246    pub fn new() -> Self {
247        let mut modules = HashMap::new();
248        modules.insert("MAIN".to_string(), Module::new("MAIN"));
249
250        Self {
251            modules,
252            current_focus: "MAIN".to_string(),
253            default_module: "MAIN".to_string(),
254            import_graph: HashMap::new(),
255        }
256    }
257
258    /// Create a new module
259    pub fn create_module(&mut self, name: impl Into<String>) -> Result<&mut Module> {
260        let name = name.into();
261
262        if self.modules.contains_key(&name) {
263            return Err(RuleEngineError::ModuleError {
264                message: format!("Module '{}' already exists", name),
265            });
266        }
267
268        self.modules.insert(name.clone(), Module::new(&name));
269        Ok(self.modules.get_mut(&name).unwrap())
270    }
271
272    /// Get a module (mutable)
273    pub fn get_module_mut(&mut self, name: &str) -> Result<&mut Module> {
274        self.modules.get_mut(name).ok_or_else(|| RuleEngineError::ModuleError {
275            message: format!("Module '{}' not found", name),
276        })
277    }
278
279    /// Get a module (immutable)
280    pub fn get_module(&self, name: &str) -> Result<&Module> {
281        self.modules.get(name).ok_or_else(|| RuleEngineError::ModuleError {
282            message: format!("Module '{}' not found", name),
283        })
284    }
285
286    /// Delete a module
287    pub fn delete_module(&mut self, name: &str) -> Result<()> {
288        if name == self.default_module {
289            return Err(RuleEngineError::ModuleError {
290                message: "Cannot delete default module".to_string(),
291            });
292        }
293
294        if name == self.current_focus {
295            self.current_focus = self.default_module.clone();
296        }
297
298        self.modules.remove(name).ok_or_else(|| RuleEngineError::ModuleError {
299            message: format!("Module '{}' not found", name),
300        })?;
301
302        // Clean up import graph
303        self.import_graph.remove(name);
304        for (_, imports) in self.import_graph.iter_mut() {
305            imports.remove(name);
306        }
307
308        Ok(())
309    }
310
311    /// Set current focus module
312    pub fn set_focus(&mut self, module_name: impl Into<String>) -> Result<()> {
313        let module_name = module_name.into();
314
315        if !self.modules.contains_key(&module_name) {
316            return Err(RuleEngineError::ModuleError {
317                message: format!("Module '{}' not found", module_name),
318            });
319        }
320
321        self.current_focus = module_name;
322        Ok(())
323    }
324
325    /// Get current focus module name
326    pub fn get_focus(&self) -> &str {
327        &self.current_focus
328    }
329
330    /// Get all module names
331    pub fn list_modules(&self) -> Vec<String> {
332        self.modules.keys().cloned().collect()
333    }
334
335    /// Configure exports for a module
336    pub fn export_all_from(&mut self, module_name: &str, export_list: ExportList) -> Result<()> {
337        let module = self.get_module_mut(module_name)?;
338        module.set_exports(export_list);
339        Ok(())
340    }
341
342    /// Detect if adding an import would create a cycle
343    /// 
344    /// Uses BFS (Breadth-First Search) to traverse the import graph from `from_module`
345    /// and check if we can reach `to_module`. If we can, adding `to_module -> from_module`
346    /// would create a cycle.
347    ///
348    /// Returns `Ok(())` if no cycle would be created.
349    /// Returns `Err(RuleEngineError)` with detailed cycle path if cycle would be created.
350    fn detect_cycle(&self, to_module: &str, from_module: &str) -> Result<()> {
351        // Cycle with self
352        if to_module == from_module {
353            return Err(RuleEngineError::ModuleError {
354                message: format!(
355                    "Cyclic import detected: {} cannot import from itself",
356                    to_module
357                ),
358            });
359        }
360
361        // BFS from from_module to see if we can reach to_module
362        // If we can, then adding to_module -> from_module creates a cycle
363        let mut queue = VecDeque::new();
364        let mut visited = HashSet::new();
365        let mut parent_map: HashMap<String, String> = HashMap::new();
366
367        queue.push_back(from_module.to_string());
368        visited.insert(from_module.to_string());
369
370        while let Some(current) = queue.pop_front() {
371            // Get all modules that current imports from
372            if let Some(imports) = self.import_graph.get(&current) {
373                for imported in imports {
374                    if imported == to_module {
375                        // Found a cycle! Reconstruct the path
376                        let mut cycle_path = vec![to_module.to_string()];
377                        let mut node = current.clone();
378
379                        while let Some(parent) = parent_map.get(&node) {
380                            cycle_path.push(node.clone());
381                            node = parent.clone();
382                        }
383
384                        cycle_path.push(node);
385                        cycle_path.reverse();
386
387                        return Err(RuleEngineError::ModuleError {
388                            message: format!(
389                                "Cyclic import detected: {}",
390                                cycle_path.join(" -> ")
391                            ),
392                        });
393                    }
394
395                    if !visited.contains(imported) {
396                        visited.insert(imported.clone());
397                        parent_map.insert(imported.clone(), current.clone());
398                        queue.push_back(imported.clone());
399                    }
400                }
401            }
402        }
403
404        Ok(())
405    }
406
407    /// Get the import graph for inspection/debugging
408    pub fn get_import_graph(&self) -> &HashMap<String, HashSet<String>> {
409        &self.import_graph
410    }
411
412    /// Export the import graph for visualization or analysis
413    pub fn get_import_graph_debug(&self) -> Vec<(String, Vec<String>)> {
414        self.import_graph
415            .iter()
416            .map(|(module, imports)| {
417                (module.clone(), imports.iter().cloned().collect())
418            })
419            .collect()
420    }
421
422    /// Add an import to a module
423    pub fn import_from(
424        &mut self,
425        to_module: &str,
426        from_module: &str,
427        import_type: ImportType,
428        pattern: impl Into<String>,
429    ) -> Result<()> {
430        // Validate from_module exists
431        if !self.modules.contains_key(from_module) {
432            return Err(RuleEngineError::ModuleError {
433                message: format!("Source module '{}' not found", from_module),
434            });
435        }
436
437        // Check for cycles BEFORE adding the import
438        self.detect_cycle(to_module, from_module)?;
439
440        let module = self.get_module_mut(to_module)?;
441        module.add_import(ImportDecl {
442            from_module: from_module.to_string(),
443            import_type,
444            pattern: pattern.into(),
445        });
446
447        // Update import graph
448        self.import_graph
449            .entry(to_module.to_string())
450            .or_insert_with(HashSet::new)
451            .insert(from_module.to_string());
452
453        Ok(())
454    }
455
456    /// Check if a rule is visible to a module
457    pub fn is_rule_visible(&self, rule_name: &str, to_module: &str) -> Result<bool> {
458        let module = self.get_module(to_module)?;
459
460        // Own rules are always visible
461        if module.get_rules().contains(rule_name) {
462            return Ok(true);
463        }
464
465        // Check imports
466        for import in module.get_imports() {
467            if !matches!(import.import_type, ImportType::AllRules | ImportType::Rules | ImportType::All) {
468                continue;
469            }
470
471            let from_module = self.get_module(&import.from_module)?;
472
473            if from_module.exports_rule(rule_name) && pattern_matches(&import.pattern, rule_name) {
474                return Ok(true);
475            }
476        }
477
478        Ok(false)
479    }
480
481    /// Check if a template is visible to a module
482    pub fn is_template_visible(&self, template_name: &str, to_module: &str) -> Result<bool> {
483        let module = self.get_module(to_module)?;
484
485        // Own templates are always visible
486        if module.get_templates().contains(template_name) {
487            return Ok(true);
488        }
489
490        // Check imports
491        for import in module.get_imports() {
492            if !matches!(import.import_type, ImportType::AllTemplates | ImportType::Templates | ImportType::All) {
493                continue;
494            }
495
496            let from_module = self.get_module(&import.from_module)?;
497
498            if from_module.exports_template(template_name) && pattern_matches(&import.pattern, template_name) {
499                return Ok(true);
500            }
501        }
502
503        Ok(false)
504    }
505
506    /// Get all rules visible to a module
507    pub fn get_visible_rules(&self, module_name: &str) -> Result<Vec<String>> {
508        let module = self.get_module(module_name)?;
509        let mut visible = HashSet::new();
510
511        // Add own rules
512        visible.extend(module.get_rules().iter().cloned());
513
514        // Add imported rules
515        for import in module.get_imports() {
516            if !matches!(import.import_type, ImportType::AllRules | ImportType::Rules | ImportType::All) {
517                continue;
518            }
519
520            let from_module = self.get_module(&import.from_module)?;
521
522            for rule in from_module.get_rules() {
523                if from_module.exports_rule(rule) && pattern_matches(&import.pattern, rule) {
524                    visible.insert(rule.clone());
525                }
526            }
527        }
528
529        Ok(visible.into_iter().collect())
530    }
531
532    /// Get module statistics
533    pub fn get_stats(&self) -> ModuleStats {
534        ModuleStats {
535            total_modules: self.modules.len(),
536            current_focus: self.current_focus.clone(),
537            modules: self.modules.iter().map(|(name, module)| {
538                (name.clone(), ModuleInfo {
539                    name: name.clone(),
540                    rules_count: module.rules.len(),
541                    templates_count: module.templates.len(),
542                    imports_count: module.imports.len(),
543                    exports_type: match &module.exports {
544                        ExportList::All => "All".to_string(),
545                        ExportList::None => "None".to_string(),
546                        ExportList::Specific(items) => format!("Specific({})", items.len()),
547                    },
548                })
549            }).collect(),
550        }
551    }
552}
553
554impl Default for ModuleManager {
555    fn default() -> Self {
556        Self::new()
557    }
558}
559
560/// Module statistics
561#[derive(Debug, Clone)]
562pub struct ModuleStats {
563    /// Total number of modules
564    pub total_modules: usize,
565    /// Current focus module
566    pub current_focus: String,
567    /// Information about each module
568    pub modules: HashMap<String, ModuleInfo>,
569}
570
571/// Information about a single module
572#[derive(Debug, Clone)]
573pub struct ModuleInfo {
574    /// Module name
575    pub name: String,
576    /// Number of rules
577    pub rules_count: usize,
578    /// Number of templates
579    pub templates_count: usize,
580    /// Number of imports
581    pub imports_count: usize,
582    /// Export type description
583    pub exports_type: String,
584}
585
586/// Check if a name matches a pattern (supports wildcards)
587fn pattern_matches(pattern: &str, name: &str) -> bool {
588    if pattern == "*" || pattern == "?ALL" {
589        return true;
590    }
591
592    // Simple wildcard matching
593    if pattern.ends_with('*') {
594        let prefix = &pattern[..pattern.len() - 1];
595        name.starts_with(prefix)
596    } else if pattern.starts_with('*') {
597        let suffix = &pattern[1..];
598        name.ends_with(suffix)
599    } else {
600        pattern == name
601    }
602}
603
604#[cfg(test)]
605mod tests {
606    use super::*;
607
608    #[test]
609    fn test_module_creation() {
610        let mut manager = ModuleManager::new();
611
612        assert!(manager.create_module("TEST").is_ok());
613        assert!(manager.create_module("TEST").is_err()); // Duplicate
614
615        assert_eq!(manager.list_modules().len(), 2); // MAIN + TEST
616    }
617
618    #[test]
619    fn test_module_focus() {
620        let mut manager = ModuleManager::new();
621        manager.create_module("SENSORS").unwrap();
622
623        assert_eq!(manager.get_focus(), "MAIN");
624
625        manager.set_focus("SENSORS").unwrap();
626        assert_eq!(manager.get_focus(), "SENSORS");
627
628        assert!(manager.set_focus("NONEXISTENT").is_err());
629    }
630
631    #[test]
632    fn test_export_import() {
633        let mut manager = ModuleManager::new();
634        manager.create_module("SENSORS").unwrap();
635        manager.create_module("CONTROL").unwrap();
636
637        // Add rules to SENSORS
638        let sensors = manager.get_module_mut("SENSORS").unwrap();
639        sensors.add_rule("sensor-temp");
640        sensors.add_rule("sensor-pressure");
641        sensors.set_exports(ExportList::Specific(vec![
642            ExportItem {
643                item_type: ItemType::Rule,
644                pattern: "sensor-*".to_string(),
645            },
646        ]));
647
648        // Import in CONTROL
649        manager.import_from("CONTROL", "SENSORS", ImportType::AllRules, "*").unwrap();
650
651        // Check visibility
652        assert!(manager.is_rule_visible("sensor-temp", "CONTROL").unwrap());
653        assert!(manager.is_rule_visible("sensor-pressure", "CONTROL").unwrap());
654    }
655
656    #[test]
657    fn test_pattern_matching() {
658        assert!(pattern_matches("*", "anything"));
659        assert!(pattern_matches("sensor-*", "sensor-temp"));
660        assert!(pattern_matches("sensor-*", "sensor-pressure"));
661        assert!(!pattern_matches("sensor-*", "control-temp"));
662        assert!(pattern_matches("*-temp", "sensor-temp"));
663        assert!(pattern_matches("exact", "exact"));
664        assert!(!pattern_matches("exact", "not-exact"));
665    }
666
667    #[test]
668    fn test_main_module_default_export() {
669        let manager = ModuleManager::new();
670        let main_module = manager.get_module("MAIN").unwrap();
671
672        // MAIN module should export all by default
673        assert!(matches!(main_module.exports, ExportList::All));
674    }
675
676    #[test]
677    fn test_user_module_default_export() {
678        let mut manager = ModuleManager::new();
679        manager.create_module("USER").unwrap();
680        let user_module = manager.get_module("USER").unwrap();
681
682        // User modules should export none by default
683        assert!(matches!(user_module.exports, ExportList::None));
684    }
685
686    #[test]
687    fn test_visibility_own_rules() {
688        let mut manager = ModuleManager::new();
689        manager.create_module("TEST").unwrap();
690
691        let test_module = manager.get_module_mut("TEST").unwrap();
692        test_module.add_rule("my-rule");
693
694        // Own rules are always visible
695        assert!(manager.is_rule_visible("my-rule", "TEST").unwrap());
696    }
697
698    #[test]
699    fn test_get_visible_rules() {
700        let mut manager = ModuleManager::new();
701        manager.create_module("MOD1").unwrap();
702        manager.create_module("MOD2").unwrap();
703
704        // Add rules to MOD1
705        let mod1 = manager.get_module_mut("MOD1").unwrap();
706        mod1.add_rule("rule1");
707        mod1.add_rule("rule2");
708        mod1.set_exports(ExportList::All);
709
710        // Add rule to MOD2
711        let mod2 = manager.get_module_mut("MOD2").unwrap();
712        mod2.add_rule("rule3");
713
714        // Import from MOD1 to MOD2
715        manager.import_from("MOD2", "MOD1", ImportType::AllRules, "*").unwrap();
716
717        let visible = manager.get_visible_rules("MOD2").unwrap();
718        assert!(visible.contains(&"rule1".to_string()));
719        assert!(visible.contains(&"rule2".to_string()));
720        assert!(visible.contains(&"rule3".to_string()));
721        assert_eq!(visible.len(), 3);
722    }
723
724    #[test]
725    fn test_module_stats() {
726        let mut manager = ModuleManager::new();
727        manager.create_module("TEST").unwrap();
728
729        let test_module = manager.get_module_mut("TEST").unwrap();
730        test_module.add_rule("rule1");
731        test_module.add_template("template1");
732
733        let stats = manager.get_stats();
734        assert_eq!(stats.total_modules, 2); // MAIN + TEST
735        assert_eq!(stats.current_focus, "MAIN");
736
737        let test_info = stats.modules.get("TEST").unwrap();
738        assert_eq!(test_info.rules_count, 1);
739        assert_eq!(test_info.templates_count, 1);
740    }
741}