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};
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/// Module manager for organizing knowledge bases
219#[derive(Debug, Clone)]
220pub struct ModuleManager {
221    /// All modules
222    modules: HashMap<String, Module>,
223    /// Current focus module (for execution)
224    current_focus: String,
225    /// Default module name
226    default_module: String,
227}
228
229impl ModuleManager {
230    /// Create a new module manager
231    pub fn new() -> Self {
232        let mut modules = HashMap::new();
233        modules.insert("MAIN".to_string(), Module::new("MAIN"));
234
235        Self {
236            modules,
237            current_focus: "MAIN".to_string(),
238            default_module: "MAIN".to_string(),
239        }
240    }
241
242    /// Create a new module
243    pub fn create_module(&mut self, name: impl Into<String>) -> Result<&mut Module> {
244        let name = name.into();
245
246        if self.modules.contains_key(&name) {
247            return Err(RuleEngineError::ModuleError {
248                message: format!("Module '{}' already exists", name),
249            });
250        }
251
252        self.modules.insert(name.clone(), Module::new(&name));
253        Ok(self.modules.get_mut(&name).unwrap())
254    }
255
256    /// Get a module (mutable)
257    pub fn get_module_mut(&mut self, name: &str) -> Result<&mut Module> {
258        self.modules.get_mut(name).ok_or_else(|| RuleEngineError::ModuleError {
259            message: format!("Module '{}' not found", name),
260        })
261    }
262
263    /// Get a module (immutable)
264    pub fn get_module(&self, name: &str) -> Result<&Module> {
265        self.modules.get(name).ok_or_else(|| RuleEngineError::ModuleError {
266            message: format!("Module '{}' not found", name),
267        })
268    }
269
270    /// Delete a module
271    pub fn delete_module(&mut self, name: &str) -> Result<()> {
272        if name == self.default_module {
273            return Err(RuleEngineError::ModuleError {
274                message: "Cannot delete default module".to_string(),
275            });
276        }
277
278        if name == self.current_focus {
279            self.current_focus = self.default_module.clone();
280        }
281
282        self.modules.remove(name).ok_or_else(|| RuleEngineError::ModuleError {
283            message: format!("Module '{}' not found", name),
284        })?;
285
286        Ok(())
287    }
288
289    /// Set current focus module
290    pub fn set_focus(&mut self, module_name: impl Into<String>) -> Result<()> {
291        let module_name = module_name.into();
292
293        if !self.modules.contains_key(&module_name) {
294            return Err(RuleEngineError::ModuleError {
295                message: format!("Module '{}' not found", module_name),
296            });
297        }
298
299        self.current_focus = module_name;
300        Ok(())
301    }
302
303    /// Get current focus module name
304    pub fn get_focus(&self) -> &str {
305        &self.current_focus
306    }
307
308    /// Get all module names
309    pub fn list_modules(&self) -> Vec<String> {
310        self.modules.keys().cloned().collect()
311    }
312
313    /// Configure exports for a module
314    pub fn export_all_from(&mut self, module_name: &str, export_list: ExportList) -> Result<()> {
315        let module = self.get_module_mut(module_name)?;
316        module.set_exports(export_list);
317        Ok(())
318    }
319
320    /// Add an import to a module
321    pub fn import_from(
322        &mut self,
323        to_module: &str,
324        from_module: &str,
325        import_type: ImportType,
326        pattern: impl Into<String>,
327    ) -> Result<()> {
328        // Validate from_module exists
329        if !self.modules.contains_key(from_module) {
330            return Err(RuleEngineError::ModuleError {
331                message: format!("Source module '{}' not found", from_module),
332            });
333        }
334
335        let module = self.get_module_mut(to_module)?;
336        module.add_import(ImportDecl {
337            from_module: from_module.to_string(),
338            import_type,
339            pattern: pattern.into(),
340        });
341
342        Ok(())
343    }
344
345    /// Check if a rule is visible to a module
346    pub fn is_rule_visible(&self, rule_name: &str, to_module: &str) -> Result<bool> {
347        let module = self.get_module(to_module)?;
348
349        // Own rules are always visible
350        if module.get_rules().contains(rule_name) {
351            return Ok(true);
352        }
353
354        // Check imports
355        for import in module.get_imports() {
356            if !matches!(import.import_type, ImportType::AllRules | ImportType::Rules | ImportType::All) {
357                continue;
358            }
359
360            let from_module = self.get_module(&import.from_module)?;
361
362            if from_module.exports_rule(rule_name) && pattern_matches(&import.pattern, rule_name) {
363                return Ok(true);
364            }
365        }
366
367        Ok(false)
368    }
369
370    /// Check if a template is visible to a module
371    pub fn is_template_visible(&self, template_name: &str, to_module: &str) -> Result<bool> {
372        let module = self.get_module(to_module)?;
373
374        // Own templates are always visible
375        if module.get_templates().contains(template_name) {
376            return Ok(true);
377        }
378
379        // Check imports
380        for import in module.get_imports() {
381            if !matches!(import.import_type, ImportType::AllTemplates | ImportType::Templates | ImportType::All) {
382                continue;
383            }
384
385            let from_module = self.get_module(&import.from_module)?;
386
387            if from_module.exports_template(template_name) && pattern_matches(&import.pattern, template_name) {
388                return Ok(true);
389            }
390        }
391
392        Ok(false)
393    }
394
395    /// Get all rules visible to a module
396    pub fn get_visible_rules(&self, module_name: &str) -> Result<Vec<String>> {
397        let module = self.get_module(module_name)?;
398        let mut visible = HashSet::new();
399
400        // Add own rules
401        visible.extend(module.get_rules().iter().cloned());
402
403        // Add imported rules
404        for import in module.get_imports() {
405            if !matches!(import.import_type, ImportType::AllRules | ImportType::Rules | ImportType::All) {
406                continue;
407            }
408
409            let from_module = self.get_module(&import.from_module)?;
410
411            for rule in from_module.get_rules() {
412                if from_module.exports_rule(rule) && pattern_matches(&import.pattern, rule) {
413                    visible.insert(rule.clone());
414                }
415            }
416        }
417
418        Ok(visible.into_iter().collect())
419    }
420
421    /// Get module statistics
422    pub fn get_stats(&self) -> ModuleStats {
423        ModuleStats {
424            total_modules: self.modules.len(),
425            current_focus: self.current_focus.clone(),
426            modules: self.modules.iter().map(|(name, module)| {
427                (name.clone(), ModuleInfo {
428                    name: name.clone(),
429                    rules_count: module.rules.len(),
430                    templates_count: module.templates.len(),
431                    imports_count: module.imports.len(),
432                    exports_type: match &module.exports {
433                        ExportList::All => "All".to_string(),
434                        ExportList::None => "None".to_string(),
435                        ExportList::Specific(items) => format!("Specific({})", items.len()),
436                    },
437                })
438            }).collect(),
439        }
440    }
441}
442
443impl Default for ModuleManager {
444    fn default() -> Self {
445        Self::new()
446    }
447}
448
449/// Module statistics
450#[derive(Debug, Clone)]
451pub struct ModuleStats {
452    /// Total number of modules
453    pub total_modules: usize,
454    /// Current focus module
455    pub current_focus: String,
456    /// Information about each module
457    pub modules: HashMap<String, ModuleInfo>,
458}
459
460/// Information about a single module
461#[derive(Debug, Clone)]
462pub struct ModuleInfo {
463    /// Module name
464    pub name: String,
465    /// Number of rules
466    pub rules_count: usize,
467    /// Number of templates
468    pub templates_count: usize,
469    /// Number of imports
470    pub imports_count: usize,
471    /// Export type description
472    pub exports_type: String,
473}
474
475/// Check if a name matches a pattern (supports wildcards)
476fn pattern_matches(pattern: &str, name: &str) -> bool {
477    if pattern == "*" || pattern == "?ALL" {
478        return true;
479    }
480
481    // Simple wildcard matching
482    if pattern.ends_with('*') {
483        let prefix = &pattern[..pattern.len() - 1];
484        name.starts_with(prefix)
485    } else if pattern.starts_with('*') {
486        let suffix = &pattern[1..];
487        name.ends_with(suffix)
488    } else {
489        pattern == name
490    }
491}
492
493#[cfg(test)]
494mod tests {
495    use super::*;
496
497    #[test]
498    fn test_module_creation() {
499        let mut manager = ModuleManager::new();
500
501        assert!(manager.create_module("TEST").is_ok());
502        assert!(manager.create_module("TEST").is_err()); // Duplicate
503
504        assert_eq!(manager.list_modules().len(), 2); // MAIN + TEST
505    }
506
507    #[test]
508    fn test_module_focus() {
509        let mut manager = ModuleManager::new();
510        manager.create_module("SENSORS").unwrap();
511
512        assert_eq!(manager.get_focus(), "MAIN");
513
514        manager.set_focus("SENSORS").unwrap();
515        assert_eq!(manager.get_focus(), "SENSORS");
516
517        assert!(manager.set_focus("NONEXISTENT").is_err());
518    }
519
520    #[test]
521    fn test_export_import() {
522        let mut manager = ModuleManager::new();
523        manager.create_module("SENSORS").unwrap();
524        manager.create_module("CONTROL").unwrap();
525
526        // Add rules to SENSORS
527        let sensors = manager.get_module_mut("SENSORS").unwrap();
528        sensors.add_rule("sensor-temp");
529        sensors.add_rule("sensor-pressure");
530        sensors.set_exports(ExportList::Specific(vec![
531            ExportItem {
532                item_type: ItemType::Rule,
533                pattern: "sensor-*".to_string(),
534            },
535        ]));
536
537        // Import in CONTROL
538        manager.import_from("CONTROL", "SENSORS", ImportType::AllRules, "*").unwrap();
539
540        // Check visibility
541        assert!(manager.is_rule_visible("sensor-temp", "CONTROL").unwrap());
542        assert!(manager.is_rule_visible("sensor-pressure", "CONTROL").unwrap());
543    }
544
545    #[test]
546    fn test_pattern_matching() {
547        assert!(pattern_matches("*", "anything"));
548        assert!(pattern_matches("sensor-*", "sensor-temp"));
549        assert!(pattern_matches("sensor-*", "sensor-pressure"));
550        assert!(!pattern_matches("sensor-*", "control-temp"));
551        assert!(pattern_matches("*-temp", "sensor-temp"));
552        assert!(pattern_matches("exact", "exact"));
553        assert!(!pattern_matches("exact", "not-exact"));
554    }
555
556    #[test]
557    fn test_main_module_default_export() {
558        let manager = ModuleManager::new();
559        let main_module = manager.get_module("MAIN").unwrap();
560
561        // MAIN module should export all by default
562        assert!(matches!(main_module.exports, ExportList::All));
563    }
564
565    #[test]
566    fn test_user_module_default_export() {
567        let mut manager = ModuleManager::new();
568        manager.create_module("USER").unwrap();
569        let user_module = manager.get_module("USER").unwrap();
570
571        // User modules should export none by default
572        assert!(matches!(user_module.exports, ExportList::None));
573    }
574
575    #[test]
576    fn test_visibility_own_rules() {
577        let mut manager = ModuleManager::new();
578        manager.create_module("TEST").unwrap();
579
580        let test_module = manager.get_module_mut("TEST").unwrap();
581        test_module.add_rule("my-rule");
582
583        // Own rules are always visible
584        assert!(manager.is_rule_visible("my-rule", "TEST").unwrap());
585    }
586
587    #[test]
588    fn test_get_visible_rules() {
589        let mut manager = ModuleManager::new();
590        manager.create_module("MOD1").unwrap();
591        manager.create_module("MOD2").unwrap();
592
593        // Add rules to MOD1
594        let mod1 = manager.get_module_mut("MOD1").unwrap();
595        mod1.add_rule("rule1");
596        mod1.add_rule("rule2");
597        mod1.set_exports(ExportList::All);
598
599        // Add rule to MOD2
600        let mod2 = manager.get_module_mut("MOD2").unwrap();
601        mod2.add_rule("rule3");
602
603        // Import from MOD1 to MOD2
604        manager.import_from("MOD2", "MOD1", ImportType::AllRules, "*").unwrap();
605
606        let visible = manager.get_visible_rules("MOD2").unwrap();
607        assert!(visible.contains(&"rule1".to_string()));
608        assert!(visible.contains(&"rule2".to_string()));
609        assert!(visible.contains(&"rule3".to_string()));
610        assert_eq!(visible.len(), 3);
611    }
612
613    #[test]
614    fn test_module_stats() {
615        let mut manager = ModuleManager::new();
616        manager.create_module("TEST").unwrap();
617
618        let test_module = manager.get_module_mut("TEST").unwrap();
619        test_module.add_rule("rule1");
620        test_module.add_template("template1");
621
622        let stats = manager.get_stats();
623        assert_eq!(stats.total_modules, 2); // MAIN + TEST
624        assert_eq!(stats.current_focus, "MAIN");
625
626        let test_info = stats.modules.get("TEST").unwrap();
627        assert_eq!(test_info.rules_count, 1);
628        assert_eq!(test_info.templates_count, 1);
629    }
630}