1use std::collections::{HashMap, HashSet, VecDeque};
35use crate::errors::{Result, RuleEngineError};
36
37#[derive(Debug, Clone, PartialEq, Eq, Hash)]
39pub enum ItemType {
40 Rule,
42 Template,
44 Fact,
46 All,
48}
49
50#[derive(Debug, Clone, PartialEq)]
52pub enum ExportList {
53 All,
55 None,
57 Specific(Vec<ExportItem>),
59}
60
61#[derive(Debug, Clone, PartialEq)]
63pub struct ExportItem {
64 pub item_type: ItemType,
66 pub pattern: String,
68}
69
70#[derive(Debug, Clone, PartialEq, Eq)]
72pub enum ImportType {
73 AllRules,
75 AllTemplates,
77 Rules,
79 Templates,
81 All,
83}
84
85#[derive(Debug, Clone, PartialEq)]
87pub struct ReExport {
88 pub patterns: Vec<String>,
90 pub transitive: bool,
92}
93
94#[derive(Debug, Clone, PartialEq)]
96pub struct ImportDecl {
97 pub from_module: String,
99 pub import_type: ImportType,
101 pub pattern: String,
103 pub re_export: Option<ReExport>,
105}
106
107#[derive(Debug, Clone)]
109pub struct Module {
110 pub name: String,
112 rules: HashSet<String>,
114 templates: HashSet<String>,
116 fact_types: HashSet<String>,
118 exports: ExportList,
120 imports: Vec<ImportDecl>,
122 pub doc: Option<String>,
124 salience: i32,
126}
127
128impl Module {
129 pub fn new(name: impl Into<String>) -> Self {
131 let name = name.into();
132 let exports = if name == "MAIN" {
133 ExportList::All
134 } else {
135 ExportList::None
136 };
137
138 Self {
139 name,
140 rules: HashSet::new(),
141 templates: HashSet::new(),
142 fact_types: HashSet::new(),
143 exports,
144 imports: Vec::new(),
145 doc: None,
146 salience: 0,
147 }
148 }
149
150 pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
152 self.doc = Some(doc.into());
153 self
154 }
155
156 pub fn add_rule(&mut self, rule_name: impl Into<String>) {
158 self.rules.insert(rule_name.into());
159 }
160
161 pub fn add_template(&mut self, template_name: impl Into<String>) {
163 self.templates.insert(template_name.into());
164 }
165
166 pub fn add_fact_type(&mut self, fact_type: impl Into<String>) {
168 self.fact_types.insert(fact_type.into());
169 }
170
171 pub fn set_exports(&mut self, exports: ExportList) {
173 self.exports = exports;
174 }
175
176 pub fn get_exports(&self) -> &ExportList {
178 &self.exports
179 }
180
181 pub fn add_import(&mut self, import: ImportDecl) {
183 self.imports.push(import);
184 }
185
186 pub fn exports_rule(&self, rule_name: &str) -> bool {
188 let is_owned = self.rules.contains(rule_name);
190
191 let exports_owned = match &self.exports {
193 ExportList::All => is_owned,
194 ExportList::None => false,
195 ExportList::Specific(items) => {
196 is_owned && items.iter().any(|item| {
197 matches!(item.item_type, ItemType::Rule | ItemType::All)
198 && pattern_matches(&item.pattern, rule_name)
199 })
200 }
201 };
202
203 exports_owned || self.should_re_export_rule(rule_name)
205 }
206
207 pub fn exports_template(&self, template_name: &str) -> bool {
209 let is_owned = self.templates.contains(template_name);
211
212 let exports_owned = match &self.exports {
214 ExportList::All => is_owned,
215 ExportList::None => false,
216 ExportList::Specific(items) => {
217 is_owned && items.iter().any(|item| {
218 matches!(item.item_type, ItemType::Template | ItemType::All)
219 && pattern_matches(&item.pattern, template_name)
220 })
221 }
222 };
223
224 exports_owned || self.should_re_export_template(template_name)
226 }
227
228 pub fn get_rules(&self) -> &HashSet<String> {
230 &self.rules
231 }
232
233 pub fn get_templates(&self) -> &HashSet<String> {
235 &self.templates
236 }
237
238 pub fn get_imports(&self) -> &[ImportDecl] {
240 &self.imports
241 }
242
243 pub fn set_salience(&mut self, salience: i32) {
245 self.salience = salience;
246 }
247
248 pub fn get_salience(&self) -> i32 {
250 self.salience
251 }
252
253 pub fn should_re_export_rule(&self, rule_name: &str) -> bool {
255 for import in &self.imports {
256 if let Some(re_export) = &import.re_export {
257 if re_export.patterns.iter().any(|p| pattern_matches(p, rule_name)) {
258 return true;
259 }
260 }
261 }
262 false
263 }
264
265 pub fn should_re_export_template(&self, template_name: &str) -> bool {
267 for import in &self.imports {
268 if let Some(re_export) = &import.re_export {
269 if re_export.patterns.iter().any(|p| pattern_matches(p, template_name)) {
270 return true;
271 }
272 }
273 }
274 false
275 }
276}
277
278#[derive(Debug, Clone)]
280pub struct CycleError {
281 pub cycle_path: Vec<String>,
283}
284
285impl std::fmt::Display for CycleError {
286 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
287 write!(f, "Cyclic import detected: {}", self.cycle_path.join(" -> "))
288 }
289}
290
291#[derive(Debug, Clone)]
293pub struct ModuleManager {
294 modules: HashMap<String, Module>,
296 current_focus: String,
298 default_module: String,
300 import_graph: HashMap<String, HashSet<String>>,
302}
303
304impl ModuleManager {
305 pub fn new() -> Self {
307 let mut modules = HashMap::new();
308 modules.insert("MAIN".to_string(), Module::new("MAIN"));
309
310 Self {
311 modules,
312 current_focus: "MAIN".to_string(),
313 default_module: "MAIN".to_string(),
314 import_graph: HashMap::new(),
315 }
316 }
317
318 pub fn create_module(&mut self, name: impl Into<String>) -> Result<&mut Module> {
320 let name = name.into();
321
322 if self.modules.contains_key(&name) {
323 return Err(RuleEngineError::ModuleError {
324 message: format!("Module '{}' already exists", name),
325 });
326 }
327
328 self.modules.insert(name.clone(), Module::new(&name));
329 Ok(self.modules.get_mut(&name).unwrap())
330 }
331
332 pub fn get_module_mut(&mut self, name: &str) -> Result<&mut Module> {
334 self.modules.get_mut(name).ok_or_else(|| RuleEngineError::ModuleError {
335 message: format!("Module '{}' not found", name),
336 })
337 }
338
339 pub fn get_module(&self, name: &str) -> Result<&Module> {
341 self.modules.get(name).ok_or_else(|| RuleEngineError::ModuleError {
342 message: format!("Module '{}' not found", name),
343 })
344 }
345
346 pub fn delete_module(&mut self, name: &str) -> Result<()> {
348 if name == self.default_module {
349 return Err(RuleEngineError::ModuleError {
350 message: "Cannot delete default module".to_string(),
351 });
352 }
353
354 if name == self.current_focus {
355 self.current_focus = self.default_module.clone();
356 }
357
358 self.modules.remove(name).ok_or_else(|| RuleEngineError::ModuleError {
359 message: format!("Module '{}' not found", name),
360 })?;
361
362 self.import_graph.remove(name);
364 for (_, imports) in self.import_graph.iter_mut() {
365 imports.remove(name);
366 }
367
368 Ok(())
369 }
370
371 pub fn set_focus(&mut self, module_name: impl Into<String>) -> Result<()> {
373 let module_name = module_name.into();
374
375 if !self.modules.contains_key(&module_name) {
376 return Err(RuleEngineError::ModuleError {
377 message: format!("Module '{}' not found", module_name),
378 });
379 }
380
381 self.current_focus = module_name;
382 Ok(())
383 }
384
385 pub fn get_focus(&self) -> &str {
387 &self.current_focus
388 }
389
390 pub fn list_modules(&self) -> Vec<String> {
392 self.modules.keys().cloned().collect()
393 }
394
395 pub fn export_all_from(&mut self, module_name: &str, export_list: ExportList) -> Result<()> {
397 let module = self.get_module_mut(module_name)?;
398 module.set_exports(export_list);
399 Ok(())
400 }
401
402 fn detect_cycle(&self, to_module: &str, from_module: &str) -> Result<()> {
411 if to_module == from_module {
413 return Err(RuleEngineError::ModuleError {
414 message: format!(
415 "Cyclic import detected: {} cannot import from itself",
416 to_module
417 ),
418 });
419 }
420
421 let mut queue = VecDeque::new();
424 let mut visited = HashSet::new();
425 let mut parent_map: HashMap<String, String> = HashMap::new();
426
427 queue.push_back(from_module.to_string());
428 visited.insert(from_module.to_string());
429
430 while let Some(current) = queue.pop_front() {
431 if let Some(imports) = self.import_graph.get(¤t) {
433 for imported in imports {
434 if imported == to_module {
435 let mut cycle_path = vec![to_module.to_string()];
437 let mut node = current.clone();
438
439 while let Some(parent) = parent_map.get(&node) {
440 cycle_path.push(node.clone());
441 node = parent.clone();
442 }
443
444 cycle_path.push(node);
445 cycle_path.reverse();
446
447 return Err(RuleEngineError::ModuleError {
448 message: format!(
449 "Cyclic import detected: {}",
450 cycle_path.join(" -> ")
451 ),
452 });
453 }
454
455 if !visited.contains(imported) {
456 visited.insert(imported.clone());
457 parent_map.insert(imported.clone(), current.clone());
458 queue.push_back(imported.clone());
459 }
460 }
461 }
462 }
463
464 Ok(())
465 }
466
467 pub fn get_import_graph(&self) -> &HashMap<String, HashSet<String>> {
469 &self.import_graph
470 }
471
472 pub fn get_import_graph_debug(&self) -> Vec<(String, Vec<String>)> {
474 self.import_graph
475 .iter()
476 .map(|(module, imports)| {
477 (module.clone(), imports.iter().cloned().collect())
478 })
479 .collect()
480 }
481
482 pub fn import_from(
484 &mut self,
485 to_module: &str,
486 from_module: &str,
487 import_type: ImportType,
488 pattern: impl Into<String>,
489 ) -> Result<()> {
490 self.import_from_with_reexport(to_module, from_module, import_type, pattern, None)
491 }
492
493 pub fn import_from_with_reexport(
495 &mut self,
496 to_module: &str,
497 from_module: &str,
498 import_type: ImportType,
499 pattern: impl Into<String>,
500 re_export: Option<ReExport>,
501 ) -> Result<()> {
502 if !self.modules.contains_key(from_module) {
504 return Err(RuleEngineError::ModuleError {
505 message: format!("Source module '{}' not found", from_module),
506 });
507 }
508
509 self.detect_cycle(to_module, from_module)?;
511
512 let module = self.get_module_mut(to_module)?;
513 module.add_import(ImportDecl {
514 from_module: from_module.to_string(),
515 import_type,
516 pattern: pattern.into(),
517 re_export,
518 });
519
520 self.import_graph
522 .entry(to_module.to_string())
523 .or_insert_with(HashSet::new)
524 .insert(from_module.to_string());
525
526 Ok(())
527 }
528
529 pub fn is_rule_visible(&self, rule_name: &str, to_module: &str) -> Result<bool> {
531 let module = self.get_module(to_module)?;
532
533 if module.get_rules().contains(rule_name) {
535 return Ok(true);
536 }
537
538 for import in module.get_imports() {
540 if !matches!(import.import_type, ImportType::AllRules | ImportType::Rules | ImportType::All) {
541 continue;
542 }
543
544 let from_module = self.get_module(&import.from_module)?;
545
546 if from_module.exports_rule(rule_name) && pattern_matches(&import.pattern, rule_name) {
547 return Ok(true);
548 }
549 }
550
551 Ok(false)
552 }
553
554 pub fn is_template_visible(&self, template_name: &str, to_module: &str) -> Result<bool> {
556 let module = self.get_module(to_module)?;
557
558 if module.get_templates().contains(template_name) {
560 return Ok(true);
561 }
562
563 for import in module.get_imports() {
565 if !matches!(import.import_type, ImportType::AllTemplates | ImportType::Templates | ImportType::All) {
566 continue;
567 }
568
569 let from_module = self.get_module(&import.from_module)?;
570
571 if from_module.exports_template(template_name) && pattern_matches(&import.pattern, template_name) {
572 return Ok(true);
573 }
574 }
575
576 Ok(false)
577 }
578
579 pub fn get_visible_rules(&self, module_name: &str) -> Result<Vec<String>> {
581 let module = self.get_module(module_name)?;
582 let mut visible = HashSet::new();
583
584 visible.extend(module.get_rules().iter().cloned());
586
587 for import in module.get_imports() {
589 if !matches!(import.import_type, ImportType::AllRules | ImportType::Rules | ImportType::All) {
590 continue;
591 }
592
593 let from_module = self.get_module(&import.from_module)?;
594
595 for rule in from_module.get_rules() {
596 if from_module.exports_rule(rule) && pattern_matches(&import.pattern, rule) {
597 visible.insert(rule.clone());
598 }
599 }
600 }
601
602 Ok(visible.into_iter().collect())
603 }
604
605 pub fn get_stats(&self) -> ModuleStats {
607 ModuleStats {
608 total_modules: self.modules.len(),
609 current_focus: self.current_focus.clone(),
610 modules: self.modules.iter().map(|(name, module)| {
611 (name.clone(), ModuleInfo {
612 name: name.clone(),
613 rules_count: module.rules.len(),
614 templates_count: module.templates.len(),
615 imports_count: module.imports.len(),
616 exports_type: match &module.exports {
617 ExportList::All => "All".to_string(),
618 ExportList::None => "None".to_string(),
619 ExportList::Specific(items) => format!("Specific({})", items.len()),
620 },
621 salience: module.salience,
622 })
623 }).collect(),
624 }
625 }
626
627 pub fn set_module_salience(&mut self, module_name: &str, salience: i32) -> Result<()> {
629 let module = self.get_module_mut(module_name)?;
630 module.set_salience(salience);
631 Ok(())
632 }
633
634 pub fn get_module_salience(&self, module_name: &str) -> Result<i32> {
636 let module = self.get_module(module_name)?;
637 Ok(module.get_salience())
638 }
639
640 pub fn get_transitive_dependencies(&self, module_name: &str) -> Result<Vec<String>> {
642 let mut visited = HashSet::new();
643 let mut queue = VecDeque::new();
644 let mut result = Vec::new();
645
646 queue.push_back(module_name.to_string());
647 visited.insert(module_name.to_string());
648
649 while let Some(current) = queue.pop_front() {
650 if let Some(imports) = self.import_graph.get(¤t) {
651 for imported in imports {
652 if !visited.contains(imported) {
653 visited.insert(imported.clone());
654 result.push(imported.clone());
655 queue.push_back(imported.clone());
656 }
657 }
658 }
659 }
660
661 Ok(result)
662 }
663
664 pub fn validate_module(&self, module_name: &str) -> Result<ModuleValidation> {
666 let module = self.get_module(module_name)?;
667 let mut warnings = Vec::new();
668 let mut errors = Vec::new();
669
670 for import in module.get_imports() {
672 if !self.modules.contains_key(&import.from_module) {
673 errors.push(format!("Import references non-existent module: {}", import.from_module));
674 }
675 }
676
677 for import in module.get_imports() {
679 if let Ok(from_module) = self.get_module(&import.from_module) {
680 let mut has_visible = false;
681
682 match import.import_type {
684 ImportType::AllRules | ImportType::Rules | ImportType::All => {
685 for rule in from_module.get_rules() {
686 if from_module.exports_rule(rule) && pattern_matches(&import.pattern, rule) {
687 has_visible = true;
688 break;
689 }
690 }
691 }
692 ImportType::AllTemplates | ImportType::Templates => {
693 for template in from_module.get_templates() {
694 if from_module.exports_template(template) && pattern_matches(&import.pattern, template) {
695 has_visible = true;
696 break;
697 }
698 }
699 }
700 }
701
702 if !has_visible {
703 warnings.push(format!(
704 "Import from '{}' with pattern '{}' doesn't match any exported items",
705 import.from_module, import.pattern
706 ));
707 }
708 }
709 }
710
711 for import in module.get_imports() {
713 if let Some(re_export) = &import.re_export {
714 for pattern in &re_export.patterns {
715 let mut matches_any = false;
716
717 if let Ok(from_module) = self.get_module(&import.from_module) {
718 for rule in from_module.get_rules() {
720 if from_module.exports_rule(rule) && pattern_matches(pattern, rule) {
721 matches_any = true;
722 break;
723 }
724 }
725
726 if !matches_any {
728 for template in from_module.get_templates() {
729 if from_module.exports_template(template) && pattern_matches(pattern, template) {
730 matches_any = true;
731 break;
732 }
733 }
734 }
735 }
736
737 if !matches_any {
738 warnings.push(format!(
739 "Re-export pattern '{}' from import '{}' doesn't match any items",
740 pattern, import.from_module
741 ));
742 }
743 }
744 }
745 }
746
747 if module.get_rules().is_empty() && module.get_templates().is_empty() && module.get_imports().is_empty() {
749 warnings.push("Module is empty (no rules, templates, or imports)".to_string());
750 }
751
752 Ok(ModuleValidation {
753 module_name: module_name.to_string(),
754 is_valid: errors.is_empty(),
755 errors,
756 warnings,
757 })
758 }
759
760 pub fn validate_all_modules(&self) -> HashMap<String, ModuleValidation> {
762 self.modules
763 .keys()
764 .filter_map(|name| {
765 self.validate_module(name).ok().map(|v| (name.clone(), v))
766 })
767 .collect()
768 }
769}
770
771impl Default for ModuleManager {
772 fn default() -> Self {
773 Self::new()
774 }
775}
776
777#[derive(Debug, Clone)]
779pub struct ModuleStats {
780 pub total_modules: usize,
782 pub current_focus: String,
784 pub modules: HashMap<String, ModuleInfo>,
786}
787
788#[derive(Debug, Clone)]
790pub struct ModuleInfo {
791 pub name: String,
793 pub rules_count: usize,
795 pub templates_count: usize,
797 pub imports_count: usize,
799 pub exports_type: String,
801 pub salience: i32,
803}
804
805#[derive(Debug, Clone)]
807pub struct ModuleValidation {
808 pub module_name: String,
810 pub is_valid: bool,
812 pub errors: Vec<String>,
814 pub warnings: Vec<String>,
816}
817
818fn pattern_matches(pattern: &str, name: &str) -> bool {
820 if pattern == "*" || pattern == "?ALL" {
821 return true;
822 }
823
824 if pattern.ends_with('*') {
826 let prefix = &pattern[..pattern.len() - 1];
827 name.starts_with(prefix)
828 } else if pattern.starts_with('*') {
829 let suffix = &pattern[1..];
830 name.ends_with(suffix)
831 } else {
832 pattern == name
833 }
834}
835
836#[cfg(test)]
837mod tests {
838 use super::*;
839
840 #[test]
841 fn test_module_creation() {
842 let mut manager = ModuleManager::new();
843
844 assert!(manager.create_module("TEST").is_ok());
845 assert!(manager.create_module("TEST").is_err()); assert_eq!(manager.list_modules().len(), 2); }
849
850 #[test]
851 fn test_module_focus() {
852 let mut manager = ModuleManager::new();
853 manager.create_module("SENSORS").unwrap();
854
855 assert_eq!(manager.get_focus(), "MAIN");
856
857 manager.set_focus("SENSORS").unwrap();
858 assert_eq!(manager.get_focus(), "SENSORS");
859
860 assert!(manager.set_focus("NONEXISTENT").is_err());
861 }
862
863 #[test]
864 fn test_export_import() {
865 let mut manager = ModuleManager::new();
866 manager.create_module("SENSORS").unwrap();
867 manager.create_module("CONTROL").unwrap();
868
869 let sensors = manager.get_module_mut("SENSORS").unwrap();
871 sensors.add_rule("sensor-temp");
872 sensors.add_rule("sensor-pressure");
873 sensors.set_exports(ExportList::Specific(vec![
874 ExportItem {
875 item_type: ItemType::Rule,
876 pattern: "sensor-*".to_string(),
877 },
878 ]));
879
880 manager.import_from("CONTROL", "SENSORS", ImportType::AllRules, "*").unwrap();
882
883 assert!(manager.is_rule_visible("sensor-temp", "CONTROL").unwrap());
885 assert!(manager.is_rule_visible("sensor-pressure", "CONTROL").unwrap());
886 }
887
888 #[test]
889 fn test_pattern_matching() {
890 assert!(pattern_matches("*", "anything"));
891 assert!(pattern_matches("sensor-*", "sensor-temp"));
892 assert!(pattern_matches("sensor-*", "sensor-pressure"));
893 assert!(!pattern_matches("sensor-*", "control-temp"));
894 assert!(pattern_matches("*-temp", "sensor-temp"));
895 assert!(pattern_matches("exact", "exact"));
896 assert!(!pattern_matches("exact", "not-exact"));
897 }
898
899 #[test]
900 fn test_main_module_default_export() {
901 let manager = ModuleManager::new();
902 let main_module = manager.get_module("MAIN").unwrap();
903
904 assert!(matches!(main_module.exports, ExportList::All));
906 }
907
908 #[test]
909 fn test_user_module_default_export() {
910 let mut manager = ModuleManager::new();
911 manager.create_module("USER").unwrap();
912 let user_module = manager.get_module("USER").unwrap();
913
914 assert!(matches!(user_module.exports, ExportList::None));
916 }
917
918 #[test]
919 fn test_visibility_own_rules() {
920 let mut manager = ModuleManager::new();
921 manager.create_module("TEST").unwrap();
922
923 let test_module = manager.get_module_mut("TEST").unwrap();
924 test_module.add_rule("my-rule");
925
926 assert!(manager.is_rule_visible("my-rule", "TEST").unwrap());
928 }
929
930 #[test]
931 fn test_get_visible_rules() {
932 let mut manager = ModuleManager::new();
933 manager.create_module("MOD1").unwrap();
934 manager.create_module("MOD2").unwrap();
935
936 let mod1 = manager.get_module_mut("MOD1").unwrap();
938 mod1.add_rule("rule1");
939 mod1.add_rule("rule2");
940 mod1.set_exports(ExportList::All);
941
942 let mod2 = manager.get_module_mut("MOD2").unwrap();
944 mod2.add_rule("rule3");
945
946 manager.import_from("MOD2", "MOD1", ImportType::AllRules, "*").unwrap();
948
949 let visible = manager.get_visible_rules("MOD2").unwrap();
950 assert!(visible.contains(&"rule1".to_string()));
951 assert!(visible.contains(&"rule2".to_string()));
952 assert!(visible.contains(&"rule3".to_string()));
953 assert_eq!(visible.len(), 3);
954 }
955
956 #[test]
957 fn test_module_stats() {
958 let mut manager = ModuleManager::new();
959 manager.create_module("TEST").unwrap();
960
961 let test_module = manager.get_module_mut("TEST").unwrap();
962 test_module.add_rule("rule1");
963 test_module.add_template("template1");
964
965 let stats = manager.get_stats();
966 assert_eq!(stats.total_modules, 2); assert_eq!(stats.current_focus, "MAIN");
968
969 let test_info = stats.modules.get("TEST").unwrap();
970 assert_eq!(test_info.rules_count, 1);
971 assert_eq!(test_info.templates_count, 1);
972 }
973
974 #[test]
977 fn test_transitive_reexport() {
978 let mut manager = ModuleManager::new();
979 manager.create_module("BASE").unwrap();
980 manager.create_module("MIDDLE").unwrap();
981 manager.create_module("TOP").unwrap();
982
983 let base = manager.get_module_mut("BASE").unwrap();
985 base.add_rule("base-rule1");
986 base.add_rule("base-rule2");
987 base.set_exports(ExportList::All);
988
989 manager.import_from_with_reexport(
991 "MIDDLE",
992 "BASE",
993 ImportType::AllRules,
994 "*",
995 Some(ReExport {
996 patterns: vec!["base-*".to_string()],
997 transitive: true,
998 }),
999 ).unwrap();
1000
1001 manager.import_from("TOP", "MIDDLE", ImportType::AllRules, "*").unwrap();
1003
1004 assert!(manager.is_rule_visible("base-rule1", "TOP").unwrap());
1006 assert!(manager.is_rule_visible("base-rule2", "TOP").unwrap());
1007 }
1008
1009 #[test]
1010 fn test_module_salience() {
1011 let mut manager = ModuleManager::new();
1012 manager.create_module("HIGH_PRIORITY").unwrap();
1013 manager.create_module("LOW_PRIORITY").unwrap();
1014
1015 manager.set_module_salience("HIGH_PRIORITY", 100).unwrap();
1017 manager.set_module_salience("LOW_PRIORITY", -50).unwrap();
1018
1019 assert_eq!(manager.get_module_salience("HIGH_PRIORITY").unwrap(), 100);
1021 assert_eq!(manager.get_module_salience("LOW_PRIORITY").unwrap(), -50);
1022 assert_eq!(manager.get_module_salience("MAIN").unwrap(), 0);
1023
1024 let stats = manager.get_stats();
1026 assert_eq!(stats.modules.get("HIGH_PRIORITY").unwrap().salience, 100);
1027 assert_eq!(stats.modules.get("LOW_PRIORITY").unwrap().salience, -50);
1028 }
1029
1030 #[test]
1031 fn test_transitive_dependencies() {
1032 let mut manager = ModuleManager::new();
1033 manager.create_module("A").unwrap();
1034 manager.create_module("B").unwrap();
1035 manager.create_module("C").unwrap();
1036 manager.create_module("D").unwrap();
1037
1038 manager.import_from("A", "B", ImportType::All, "*").unwrap();
1040 manager.import_from("B", "C", ImportType::All, "*").unwrap();
1041 manager.import_from("C", "D", ImportType::All, "*").unwrap();
1042
1043 let deps = manager.get_transitive_dependencies("A").unwrap();
1045 assert!(deps.contains(&"B".to_string()));
1046 assert!(deps.contains(&"C".to_string()));
1047 assert!(deps.contains(&"D".to_string()));
1048 assert_eq!(deps.len(), 3);
1049 }
1050
1051 #[test]
1052 fn test_module_validation_broken_import() {
1053 let mut manager = ModuleManager::new();
1054 manager.create_module("TEST").unwrap();
1055
1056 let test_module = manager.get_module_mut("TEST").unwrap();
1058 test_module.add_import(ImportDecl {
1059 from_module: "NONEXISTENT".to_string(),
1060 import_type: ImportType::All,
1061 pattern: "*".to_string(),
1062 re_export: None,
1063 });
1064
1065 let validation = manager.validate_module("TEST").unwrap();
1066 assert!(!validation.is_valid);
1067 assert!(validation.errors.iter().any(|e| e.contains("NONEXISTENT")));
1068 }
1069
1070 #[test]
1071 fn test_module_validation_unused_import() {
1072 let mut manager = ModuleManager::new();
1073 manager.create_module("SOURCE").unwrap();
1074 manager.create_module("TARGET").unwrap();
1075
1076 let source = manager.get_module_mut("SOURCE").unwrap();
1078 source.add_rule("my-rule");
1079 source.set_exports(ExportList::None); manager.import_from("TARGET", "SOURCE", ImportType::AllRules, "*").unwrap();
1083
1084 let validation = manager.validate_module("TARGET").unwrap();
1085 assert!(validation.is_valid); assert!(!validation.warnings.is_empty());
1087 assert!(validation.warnings.iter().any(|w| w.contains("doesn't match any exported items")));
1088 }
1089
1090 #[test]
1091 fn test_module_validation_empty_module() {
1092 let mut manager = ModuleManager::new();
1093 manager.create_module("EMPTY").unwrap();
1094
1095 let validation = manager.validate_module("EMPTY").unwrap();
1096 assert!(validation.is_valid);
1097 assert!(validation.warnings.iter().any(|w| w.contains("empty")));
1098 }
1099
1100 #[test]
1101 fn test_module_validation_reexport_pattern() {
1102 let mut manager = ModuleManager::new();
1103 manager.create_module("SOURCE").unwrap();
1104 manager.create_module("TARGET").unwrap();
1105
1106 let source = manager.get_module_mut("SOURCE").unwrap();
1108 source.add_rule("rule1");
1109 source.set_exports(ExportList::All);
1110
1111 manager.import_from_with_reexport(
1113 "TARGET",
1114 "SOURCE",
1115 ImportType::AllRules,
1116 "*",
1117 Some(ReExport {
1118 patterns: vec!["sensor-*".to_string()], transitive: false,
1120 }),
1121 ).unwrap();
1122
1123 let validation = manager.validate_module("TARGET").unwrap();
1124 assert!(validation.is_valid);
1125 assert!(validation.warnings.iter().any(|w| w.contains("Re-export pattern")));
1126 }
1127
1128 #[test]
1129 fn test_validate_all_modules() {
1130 let mut manager = ModuleManager::new();
1131 manager.create_module("MOD1").unwrap();
1132 manager.create_module("MOD2").unwrap();
1133
1134 let validations = manager.validate_all_modules();
1135 assert_eq!(validations.len(), 3); assert!(validations.contains_key("MAIN"));
1137 assert!(validations.contains_key("MOD1"));
1138 assert!(validations.contains_key("MOD2"));
1139 }
1140
1141 #[test]
1142 fn test_selective_reexport() {
1143 let mut manager = ModuleManager::new();
1144 manager.create_module("BASE").unwrap();
1145 manager.create_module("MIDDLE").unwrap();
1146 manager.create_module("TOP").unwrap();
1147
1148 let base = manager.get_module_mut("BASE").unwrap();
1150 base.add_rule("sensor-temp");
1151 base.add_rule("sensor-pressure");
1152 base.add_rule("control-valve");
1153 base.set_exports(ExportList::All);
1154
1155 manager.import_from_with_reexport(
1157 "MIDDLE",
1158 "BASE",
1159 ImportType::AllRules,
1160 "*",
1161 Some(ReExport {
1162 patterns: vec!["sensor-*".to_string()],
1163 transitive: true,
1164 }),
1165 ).unwrap();
1166
1167 assert!(manager.is_rule_visible("sensor-temp", "MIDDLE").unwrap());
1169 assert!(manager.is_rule_visible("sensor-pressure", "MIDDLE").unwrap());
1170 assert!(manager.is_rule_visible("control-valve", "MIDDLE").unwrap());
1171
1172 manager.import_from("TOP", "MIDDLE", ImportType::AllRules, "*").unwrap();
1174
1175 assert!(manager.is_rule_visible("sensor-temp", "TOP").unwrap());
1177 assert!(manager.is_rule_visible("sensor-pressure", "TOP").unwrap());
1178 }
1182}