1use crate::errors::{Result, RuleEngineError};
35use std::collections::{HashMap, HashSet, VecDeque};
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
197 && items.iter().any(|item| {
198 matches!(item.item_type, ItemType::Rule | ItemType::All)
199 && pattern_matches(&item.pattern, rule_name)
200 })
201 }
202 };
203
204 exports_owned || self.should_re_export_rule(rule_name)
206 }
207
208 pub fn exports_template(&self, template_name: &str) -> bool {
210 let is_owned = self.templates.contains(template_name);
212
213 let exports_owned = match &self.exports {
215 ExportList::All => is_owned,
216 ExportList::None => false,
217 ExportList::Specific(items) => {
218 is_owned
219 && items.iter().any(|item| {
220 matches!(item.item_type, ItemType::Template | ItemType::All)
221 && pattern_matches(&item.pattern, template_name)
222 })
223 }
224 };
225
226 exports_owned || self.should_re_export_template(template_name)
228 }
229
230 pub fn get_rules(&self) -> &HashSet<String> {
232 &self.rules
233 }
234
235 pub fn get_templates(&self) -> &HashSet<String> {
237 &self.templates
238 }
239
240 pub fn get_imports(&self) -> &[ImportDecl] {
242 &self.imports
243 }
244
245 pub fn set_salience(&mut self, salience: i32) {
247 self.salience = salience;
248 }
249
250 pub fn get_salience(&self) -> i32 {
252 self.salience
253 }
254
255 pub fn should_re_export_rule(&self, rule_name: &str) -> bool {
257 for import in &self.imports {
258 if let Some(re_export) = &import.re_export {
259 if re_export
260 .patterns
261 .iter()
262 .any(|p| pattern_matches(p, rule_name))
263 {
264 return true;
265 }
266 }
267 }
268 false
269 }
270
271 pub fn should_re_export_template(&self, template_name: &str) -> bool {
273 for import in &self.imports {
274 if let Some(re_export) = &import.re_export {
275 if re_export
276 .patterns
277 .iter()
278 .any(|p| pattern_matches(p, template_name))
279 {
280 return true;
281 }
282 }
283 }
284 false
285 }
286}
287
288#[derive(Debug, Clone)]
290pub struct CycleError {
291 pub cycle_path: Vec<String>,
293}
294
295impl std::fmt::Display for CycleError {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 write!(
298 f,
299 "Cyclic import detected: {}",
300 self.cycle_path.join(" -> ")
301 )
302 }
303}
304
305#[derive(Debug, Clone)]
307pub struct ModuleManager {
308 modules: HashMap<String, Module>,
310 current_focus: String,
312 default_module: String,
314 import_graph: HashMap<String, HashSet<String>>,
316}
317
318impl ModuleManager {
319 pub fn new() -> Self {
321 let mut modules = HashMap::new();
322 modules.insert("MAIN".to_string(), Module::new("MAIN"));
323
324 Self {
325 modules,
326 current_focus: "MAIN".to_string(),
327 default_module: "MAIN".to_string(),
328 import_graph: HashMap::new(),
329 }
330 }
331
332 pub fn create_module(&mut self, name: impl Into<String>) -> Result<&mut Module> {
334 let name = name.into();
335
336 if self.modules.contains_key(&name) {
337 return Err(RuleEngineError::ModuleError {
338 message: format!("Module '{}' already exists", name),
339 });
340 }
341
342 self.modules.insert(name.clone(), Module::new(&name));
343 Ok(self.modules.get_mut(&name).unwrap())
344 }
345
346 pub fn get_module_mut(&mut self, name: &str) -> Result<&mut Module> {
348 self.modules
349 .get_mut(name)
350 .ok_or_else(|| RuleEngineError::ModuleError {
351 message: format!("Module '{}' not found", name),
352 })
353 }
354
355 pub fn get_module(&self, name: &str) -> Result<&Module> {
357 self.modules
358 .get(name)
359 .ok_or_else(|| RuleEngineError::ModuleError {
360 message: format!("Module '{}' not found", name),
361 })
362 }
363
364 pub fn delete_module(&mut self, name: &str) -> Result<()> {
366 if name == self.default_module {
367 return Err(RuleEngineError::ModuleError {
368 message: "Cannot delete default module".to_string(),
369 });
370 }
371
372 if name == self.current_focus {
373 self.current_focus = self.default_module.clone();
374 }
375
376 self.modules
377 .remove(name)
378 .ok_or_else(|| RuleEngineError::ModuleError {
379 message: format!("Module '{}' not found", name),
380 })?;
381
382 self.import_graph.remove(name);
384 for (_, imports) in self.import_graph.iter_mut() {
385 imports.remove(name);
386 }
387
388 Ok(())
389 }
390
391 pub fn set_focus(&mut self, module_name: impl Into<String>) -> Result<()> {
393 let module_name = module_name.into();
394
395 if !self.modules.contains_key(&module_name) {
396 return Err(RuleEngineError::ModuleError {
397 message: format!("Module '{}' not found", module_name),
398 });
399 }
400
401 self.current_focus = module_name;
402 Ok(())
403 }
404
405 pub fn get_focus(&self) -> &str {
407 &self.current_focus
408 }
409
410 pub fn list_modules(&self) -> Vec<String> {
412 self.modules.keys().cloned().collect()
413 }
414
415 pub fn export_all_from(&mut self, module_name: &str, export_list: ExportList) -> Result<()> {
417 let module = self.get_module_mut(module_name)?;
418 module.set_exports(export_list);
419 Ok(())
420 }
421
422 fn detect_cycle(&self, to_module: &str, from_module: &str) -> Result<()> {
431 if to_module == from_module {
433 return Err(RuleEngineError::ModuleError {
434 message: format!(
435 "Cyclic import detected: {} cannot import from itself",
436 to_module
437 ),
438 });
439 }
440
441 let mut queue = VecDeque::new();
444 let mut visited = HashSet::new();
445 let mut parent_map: HashMap<String, String> = HashMap::new();
446
447 queue.push_back(from_module.to_string());
448 visited.insert(from_module.to_string());
449
450 while let Some(current) = queue.pop_front() {
451 if let Some(imports) = self.import_graph.get(¤t) {
453 for imported in imports {
454 if imported == to_module {
455 let mut cycle_path = vec![to_module.to_string()];
457 let mut node = current.clone();
458
459 while let Some(parent) = parent_map.get(&node) {
460 cycle_path.push(node.clone());
461 node = parent.clone();
462 }
463
464 cycle_path.push(node);
465 cycle_path.reverse();
466
467 return Err(RuleEngineError::ModuleError {
468 message: format!("Cyclic import detected: {}", cycle_path.join(" -> ")),
469 });
470 }
471
472 if !visited.contains(imported) {
473 visited.insert(imported.clone());
474 parent_map.insert(imported.clone(), current.clone());
475 queue.push_back(imported.clone());
476 }
477 }
478 }
479 }
480
481 Ok(())
482 }
483
484 pub fn get_import_graph(&self) -> &HashMap<String, HashSet<String>> {
486 &self.import_graph
487 }
488
489 pub fn get_import_graph_debug(&self) -> Vec<(String, Vec<String>)> {
491 self.import_graph
492 .iter()
493 .map(|(module, imports)| (module.clone(), imports.iter().cloned().collect()))
494 .collect()
495 }
496
497 pub fn import_from(
499 &mut self,
500 to_module: &str,
501 from_module: &str,
502 import_type: ImportType,
503 pattern: impl Into<String>,
504 ) -> Result<()> {
505 self.import_from_with_reexport(to_module, from_module, import_type, pattern, None)
506 }
507
508 pub fn import_from_with_reexport(
510 &mut self,
511 to_module: &str,
512 from_module: &str,
513 import_type: ImportType,
514 pattern: impl Into<String>,
515 re_export: Option<ReExport>,
516 ) -> Result<()> {
517 if !self.modules.contains_key(from_module) {
519 return Err(RuleEngineError::ModuleError {
520 message: format!("Source module '{}' not found", from_module),
521 });
522 }
523
524 self.detect_cycle(to_module, from_module)?;
526
527 let module = self.get_module_mut(to_module)?;
528 module.add_import(ImportDecl {
529 from_module: from_module.to_string(),
530 import_type,
531 pattern: pattern.into(),
532 re_export,
533 });
534
535 self.import_graph
537 .entry(to_module.to_string())
538 .or_default()
539 .insert(from_module.to_string());
540
541 Ok(())
542 }
543
544 pub fn is_rule_visible(&self, rule_name: &str, to_module: &str) -> Result<bool> {
546 let module = self.get_module(to_module)?;
547
548 if module.get_rules().contains(rule_name) {
550 return Ok(true);
551 }
552
553 for import in module.get_imports() {
555 if !matches!(
556 import.import_type,
557 ImportType::AllRules | ImportType::Rules | ImportType::All
558 ) {
559 continue;
560 }
561
562 let from_module = self.get_module(&import.from_module)?;
563
564 if from_module.exports_rule(rule_name) && pattern_matches(&import.pattern, rule_name) {
565 return Ok(true);
566 }
567 }
568
569 Ok(false)
570 }
571
572 pub fn is_template_visible(&self, template_name: &str, to_module: &str) -> Result<bool> {
574 let module = self.get_module(to_module)?;
575
576 if module.get_templates().contains(template_name) {
578 return Ok(true);
579 }
580
581 for import in module.get_imports() {
583 if !matches!(
584 import.import_type,
585 ImportType::AllTemplates | ImportType::Templates | ImportType::All
586 ) {
587 continue;
588 }
589
590 let from_module = self.get_module(&import.from_module)?;
591
592 if from_module.exports_template(template_name)
593 && pattern_matches(&import.pattern, template_name)
594 {
595 return Ok(true);
596 }
597 }
598
599 Ok(false)
600 }
601
602 pub fn get_visible_rules(&self, module_name: &str) -> Result<Vec<String>> {
604 let module = self.get_module(module_name)?;
605 let mut visible = HashSet::new();
606
607 visible.extend(module.get_rules().iter().cloned());
609
610 for import in module.get_imports() {
612 if !matches!(
613 import.import_type,
614 ImportType::AllRules | ImportType::Rules | ImportType::All
615 ) {
616 continue;
617 }
618
619 let from_module = self.get_module(&import.from_module)?;
620
621 for rule in from_module.get_rules() {
622 if from_module.exports_rule(rule) && pattern_matches(&import.pattern, rule) {
623 visible.insert(rule.clone());
624 }
625 }
626 }
627
628 Ok(visible.into_iter().collect())
629 }
630
631 pub fn get_stats(&self) -> ModuleStats {
633 ModuleStats {
634 total_modules: self.modules.len(),
635 current_focus: self.current_focus.clone(),
636 modules: self
637 .modules
638 .iter()
639 .map(|(name, module)| {
640 (
641 name.clone(),
642 ModuleInfo {
643 name: name.clone(),
644 rules_count: module.rules.len(),
645 templates_count: module.templates.len(),
646 imports_count: module.imports.len(),
647 exports_type: match &module.exports {
648 ExportList::All => "All".to_string(),
649 ExportList::None => "None".to_string(),
650 ExportList::Specific(items) => format!("Specific({})", items.len()),
651 },
652 salience: module.salience,
653 },
654 )
655 })
656 .collect(),
657 }
658 }
659
660 pub fn set_module_salience(&mut self, module_name: &str, salience: i32) -> Result<()> {
662 let module = self.get_module_mut(module_name)?;
663 module.set_salience(salience);
664 Ok(())
665 }
666
667 pub fn get_module_salience(&self, module_name: &str) -> Result<i32> {
669 let module = self.get_module(module_name)?;
670 Ok(module.get_salience())
671 }
672
673 pub fn get_transitive_dependencies(&self, module_name: &str) -> Result<Vec<String>> {
675 let mut visited = HashSet::new();
676 let mut queue = VecDeque::new();
677 let mut result = Vec::new();
678
679 queue.push_back(module_name.to_string());
680 visited.insert(module_name.to_string());
681
682 while let Some(current) = queue.pop_front() {
683 if let Some(imports) = self.import_graph.get(¤t) {
684 for imported in imports {
685 if !visited.contains(imported) {
686 visited.insert(imported.clone());
687 result.push(imported.clone());
688 queue.push_back(imported.clone());
689 }
690 }
691 }
692 }
693
694 Ok(result)
695 }
696
697 pub fn validate_module(&self, module_name: &str) -> Result<ModuleValidation> {
699 let module = self.get_module(module_name)?;
700 let mut warnings = Vec::new();
701 let mut errors = Vec::new();
702
703 for import in module.get_imports() {
705 if !self.modules.contains_key(&import.from_module) {
706 errors.push(format!(
707 "Import references non-existent module: {}",
708 import.from_module
709 ));
710 }
711 }
712
713 for import in module.get_imports() {
715 if let Ok(from_module) = self.get_module(&import.from_module) {
716 let mut has_visible = false;
717
718 match import.import_type {
720 ImportType::AllRules | ImportType::Rules | ImportType::All => {
721 for rule in from_module.get_rules() {
722 if from_module.exports_rule(rule)
723 && pattern_matches(&import.pattern, rule)
724 {
725 has_visible = true;
726 break;
727 }
728 }
729 }
730 ImportType::AllTemplates | ImportType::Templates => {
731 for template in from_module.get_templates() {
732 if from_module.exports_template(template)
733 && pattern_matches(&import.pattern, template)
734 {
735 has_visible = true;
736 break;
737 }
738 }
739 }
740 }
741
742 if !has_visible {
743 warnings.push(format!(
744 "Import from '{}' with pattern '{}' doesn't match any exported items",
745 import.from_module, import.pattern
746 ));
747 }
748 }
749 }
750
751 for import in module.get_imports() {
753 if let Some(re_export) = &import.re_export {
754 for pattern in &re_export.patterns {
755 let mut matches_any = false;
756
757 if let Ok(from_module) = self.get_module(&import.from_module) {
758 for rule in from_module.get_rules() {
760 if from_module.exports_rule(rule) && pattern_matches(pattern, rule) {
761 matches_any = true;
762 break;
763 }
764 }
765
766 if !matches_any {
768 for template in from_module.get_templates() {
769 if from_module.exports_template(template)
770 && pattern_matches(pattern, template)
771 {
772 matches_any = true;
773 break;
774 }
775 }
776 }
777 }
778
779 if !matches_any {
780 warnings.push(format!(
781 "Re-export pattern '{}' from import '{}' doesn't match any items",
782 pattern, import.from_module
783 ));
784 }
785 }
786 }
787 }
788
789 if module.get_rules().is_empty()
791 && module.get_templates().is_empty()
792 && module.get_imports().is_empty()
793 {
794 warnings.push("Module is empty (no rules, templates, or imports)".to_string());
795 }
796
797 Ok(ModuleValidation {
798 module_name: module_name.to_string(),
799 is_valid: errors.is_empty(),
800 errors,
801 warnings,
802 })
803 }
804
805 pub fn validate_all_modules(&self) -> HashMap<String, ModuleValidation> {
807 self.modules
808 .keys()
809 .filter_map(|name| self.validate_module(name).ok().map(|v| (name.clone(), v)))
810 .collect()
811 }
812}
813
814impl Default for ModuleManager {
815 fn default() -> Self {
816 Self::new()
817 }
818}
819
820#[derive(Debug, Clone)]
822pub struct ModuleStats {
823 pub total_modules: usize,
825 pub current_focus: String,
827 pub modules: HashMap<String, ModuleInfo>,
829}
830
831#[derive(Debug, Clone)]
833pub struct ModuleInfo {
834 pub name: String,
836 pub rules_count: usize,
838 pub templates_count: usize,
840 pub imports_count: usize,
842 pub exports_type: String,
844 pub salience: i32,
846}
847
848#[derive(Debug, Clone)]
850pub struct ModuleValidation {
851 pub module_name: String,
853 pub is_valid: bool,
855 pub errors: Vec<String>,
857 pub warnings: Vec<String>,
859}
860
861fn pattern_matches(pattern: &str, name: &str) -> bool {
863 if pattern == "*" || pattern == "?ALL" {
864 return true;
865 }
866
867 if let Some(prefix) = pattern.strip_suffix('*') {
869 name.starts_with(prefix)
870 } else if let Some(suffix) = pattern.strip_prefix('*') {
871 name.ends_with(suffix)
872 } else {
873 pattern == name
874 }
875}
876
877#[cfg(test)]
878mod tests {
879 use super::*;
880
881 #[test]
882 fn test_module_creation() {
883 let mut manager = ModuleManager::new();
884
885 assert!(manager.create_module("TEST").is_ok());
886 assert!(manager.create_module("TEST").is_err()); assert_eq!(manager.list_modules().len(), 2); }
890
891 #[test]
892 fn test_module_focus() {
893 let mut manager = ModuleManager::new();
894 manager.create_module("SENSORS").unwrap();
895
896 assert_eq!(manager.get_focus(), "MAIN");
897
898 manager.set_focus("SENSORS").unwrap();
899 assert_eq!(manager.get_focus(), "SENSORS");
900
901 assert!(manager.set_focus("NONEXISTENT").is_err());
902 }
903
904 #[test]
905 fn test_export_import() {
906 let mut manager = ModuleManager::new();
907 manager.create_module("SENSORS").unwrap();
908 manager.create_module("CONTROL").unwrap();
909
910 let sensors = manager.get_module_mut("SENSORS").unwrap();
912 sensors.add_rule("sensor-temp");
913 sensors.add_rule("sensor-pressure");
914 sensors.set_exports(ExportList::Specific(vec![ExportItem {
915 item_type: ItemType::Rule,
916 pattern: "sensor-*".to_string(),
917 }]));
918
919 manager
921 .import_from("CONTROL", "SENSORS", ImportType::AllRules, "*")
922 .unwrap();
923
924 assert!(manager.is_rule_visible("sensor-temp", "CONTROL").unwrap());
926 assert!(manager
927 .is_rule_visible("sensor-pressure", "CONTROL")
928 .unwrap());
929 }
930
931 #[test]
932 fn test_pattern_matching() {
933 assert!(pattern_matches("*", "anything"));
934 assert!(pattern_matches("sensor-*", "sensor-temp"));
935 assert!(pattern_matches("sensor-*", "sensor-pressure"));
936 assert!(!pattern_matches("sensor-*", "control-temp"));
937 assert!(pattern_matches("*-temp", "sensor-temp"));
938 assert!(pattern_matches("exact", "exact"));
939 assert!(!pattern_matches("exact", "not-exact"));
940 }
941
942 #[test]
943 fn test_main_module_default_export() {
944 let manager = ModuleManager::new();
945 let main_module = manager.get_module("MAIN").unwrap();
946
947 assert!(matches!(main_module.exports, ExportList::All));
949 }
950
951 #[test]
952 fn test_user_module_default_export() {
953 let mut manager = ModuleManager::new();
954 manager.create_module("USER").unwrap();
955 let user_module = manager.get_module("USER").unwrap();
956
957 assert!(matches!(user_module.exports, ExportList::None));
959 }
960
961 #[test]
962 fn test_visibility_own_rules() {
963 let mut manager = ModuleManager::new();
964 manager.create_module("TEST").unwrap();
965
966 let test_module = manager.get_module_mut("TEST").unwrap();
967 test_module.add_rule("my-rule");
968
969 assert!(manager.is_rule_visible("my-rule", "TEST").unwrap());
971 }
972
973 #[test]
974 fn test_get_visible_rules() {
975 let mut manager = ModuleManager::new();
976 manager.create_module("MOD1").unwrap();
977 manager.create_module("MOD2").unwrap();
978
979 let mod1 = manager.get_module_mut("MOD1").unwrap();
981 mod1.add_rule("rule1");
982 mod1.add_rule("rule2");
983 mod1.set_exports(ExportList::All);
984
985 let mod2 = manager.get_module_mut("MOD2").unwrap();
987 mod2.add_rule("rule3");
988
989 manager
991 .import_from("MOD2", "MOD1", ImportType::AllRules, "*")
992 .unwrap();
993
994 let visible = manager.get_visible_rules("MOD2").unwrap();
995 assert!(visible.contains(&"rule1".to_string()));
996 assert!(visible.contains(&"rule2".to_string()));
997 assert!(visible.contains(&"rule3".to_string()));
998 assert_eq!(visible.len(), 3);
999 }
1000
1001 #[test]
1002 fn test_module_stats() {
1003 let mut manager = ModuleManager::new();
1004 manager.create_module("TEST").unwrap();
1005
1006 let test_module = manager.get_module_mut("TEST").unwrap();
1007 test_module.add_rule("rule1");
1008 test_module.add_template("template1");
1009
1010 let stats = manager.get_stats();
1011 assert_eq!(stats.total_modules, 2); assert_eq!(stats.current_focus, "MAIN");
1013
1014 let test_info = stats.modules.get("TEST").unwrap();
1015 assert_eq!(test_info.rules_count, 1);
1016 assert_eq!(test_info.templates_count, 1);
1017 }
1018
1019 #[test]
1022 fn test_transitive_reexport() {
1023 let mut manager = ModuleManager::new();
1024 manager.create_module("BASE").unwrap();
1025 manager.create_module("MIDDLE").unwrap();
1026 manager.create_module("TOP").unwrap();
1027
1028 let base = manager.get_module_mut("BASE").unwrap();
1030 base.add_rule("base-rule1");
1031 base.add_rule("base-rule2");
1032 base.set_exports(ExportList::All);
1033
1034 manager
1036 .import_from_with_reexport(
1037 "MIDDLE",
1038 "BASE",
1039 ImportType::AllRules,
1040 "*",
1041 Some(ReExport {
1042 patterns: vec!["base-*".to_string()],
1043 transitive: true,
1044 }),
1045 )
1046 .unwrap();
1047
1048 manager
1050 .import_from("TOP", "MIDDLE", ImportType::AllRules, "*")
1051 .unwrap();
1052
1053 assert!(manager.is_rule_visible("base-rule1", "TOP").unwrap());
1055 assert!(manager.is_rule_visible("base-rule2", "TOP").unwrap());
1056 }
1057
1058 #[test]
1059 fn test_module_salience() {
1060 let mut manager = ModuleManager::new();
1061 manager.create_module("HIGH_PRIORITY").unwrap();
1062 manager.create_module("LOW_PRIORITY").unwrap();
1063
1064 manager.set_module_salience("HIGH_PRIORITY", 100).unwrap();
1066 manager.set_module_salience("LOW_PRIORITY", -50).unwrap();
1067
1068 assert_eq!(manager.get_module_salience("HIGH_PRIORITY").unwrap(), 100);
1070 assert_eq!(manager.get_module_salience("LOW_PRIORITY").unwrap(), -50);
1071 assert_eq!(manager.get_module_salience("MAIN").unwrap(), 0);
1072
1073 let stats = manager.get_stats();
1075 assert_eq!(stats.modules.get("HIGH_PRIORITY").unwrap().salience, 100);
1076 assert_eq!(stats.modules.get("LOW_PRIORITY").unwrap().salience, -50);
1077 }
1078
1079 #[test]
1080 fn test_transitive_dependencies() {
1081 let mut manager = ModuleManager::new();
1082 manager.create_module("A").unwrap();
1083 manager.create_module("B").unwrap();
1084 manager.create_module("C").unwrap();
1085 manager.create_module("D").unwrap();
1086
1087 manager.import_from("A", "B", ImportType::All, "*").unwrap();
1089 manager.import_from("B", "C", ImportType::All, "*").unwrap();
1090 manager.import_from("C", "D", ImportType::All, "*").unwrap();
1091
1092 let deps = manager.get_transitive_dependencies("A").unwrap();
1094 assert!(deps.contains(&"B".to_string()));
1095 assert!(deps.contains(&"C".to_string()));
1096 assert!(deps.contains(&"D".to_string()));
1097 assert_eq!(deps.len(), 3);
1098 }
1099
1100 #[test]
1101 fn test_module_validation_broken_import() {
1102 let mut manager = ModuleManager::new();
1103 manager.create_module("TEST").unwrap();
1104
1105 let test_module = manager.get_module_mut("TEST").unwrap();
1107 test_module.add_import(ImportDecl {
1108 from_module: "NONEXISTENT".to_string(),
1109 import_type: ImportType::All,
1110 pattern: "*".to_string(),
1111 re_export: None,
1112 });
1113
1114 let validation = manager.validate_module("TEST").unwrap();
1115 assert!(!validation.is_valid);
1116 assert!(validation.errors.iter().any(|e| e.contains("NONEXISTENT")));
1117 }
1118
1119 #[test]
1120 fn test_module_validation_unused_import() {
1121 let mut manager = ModuleManager::new();
1122 manager.create_module("SOURCE").unwrap();
1123 manager.create_module("TARGET").unwrap();
1124
1125 let source = manager.get_module_mut("SOURCE").unwrap();
1127 source.add_rule("my-rule");
1128 source.set_exports(ExportList::None); manager
1132 .import_from("TARGET", "SOURCE", ImportType::AllRules, "*")
1133 .unwrap();
1134
1135 let validation = manager.validate_module("TARGET").unwrap();
1136 assert!(validation.is_valid); assert!(!validation.warnings.is_empty());
1138 assert!(validation
1139 .warnings
1140 .iter()
1141 .any(|w| w.contains("doesn't match any exported items")));
1142 }
1143
1144 #[test]
1145 fn test_module_validation_empty_module() {
1146 let mut manager = ModuleManager::new();
1147 manager.create_module("EMPTY").unwrap();
1148
1149 let validation = manager.validate_module("EMPTY").unwrap();
1150 assert!(validation.is_valid);
1151 assert!(validation.warnings.iter().any(|w| w.contains("empty")));
1152 }
1153
1154 #[test]
1155 fn test_module_validation_reexport_pattern() {
1156 let mut manager = ModuleManager::new();
1157 manager.create_module("SOURCE").unwrap();
1158 manager.create_module("TARGET").unwrap();
1159
1160 let source = manager.get_module_mut("SOURCE").unwrap();
1162 source.add_rule("rule1");
1163 source.set_exports(ExportList::All);
1164
1165 manager
1167 .import_from_with_reexport(
1168 "TARGET",
1169 "SOURCE",
1170 ImportType::AllRules,
1171 "*",
1172 Some(ReExport {
1173 patterns: vec!["sensor-*".to_string()], transitive: false,
1175 }),
1176 )
1177 .unwrap();
1178
1179 let validation = manager.validate_module("TARGET").unwrap();
1180 assert!(validation.is_valid);
1181 assert!(validation
1182 .warnings
1183 .iter()
1184 .any(|w| w.contains("Re-export pattern")));
1185 }
1186
1187 #[test]
1188 fn test_validate_all_modules() {
1189 let mut manager = ModuleManager::new();
1190 manager.create_module("MOD1").unwrap();
1191 manager.create_module("MOD2").unwrap();
1192
1193 let validations = manager.validate_all_modules();
1194 assert_eq!(validations.len(), 3); assert!(validations.contains_key("MAIN"));
1196 assert!(validations.contains_key("MOD1"));
1197 assert!(validations.contains_key("MOD2"));
1198 }
1199
1200 #[test]
1201 fn test_selective_reexport() {
1202 let mut manager = ModuleManager::new();
1203 manager.create_module("BASE").unwrap();
1204 manager.create_module("MIDDLE").unwrap();
1205 manager.create_module("TOP").unwrap();
1206
1207 let base = manager.get_module_mut("BASE").unwrap();
1209 base.add_rule("sensor-temp");
1210 base.add_rule("sensor-pressure");
1211 base.add_rule("control-valve");
1212 base.set_exports(ExportList::All);
1213
1214 manager
1216 .import_from_with_reexport(
1217 "MIDDLE",
1218 "BASE",
1219 ImportType::AllRules,
1220 "*",
1221 Some(ReExport {
1222 patterns: vec!["sensor-*".to_string()],
1223 transitive: true,
1224 }),
1225 )
1226 .unwrap();
1227
1228 assert!(manager.is_rule_visible("sensor-temp", "MIDDLE").unwrap());
1230 assert!(manager
1231 .is_rule_visible("sensor-pressure", "MIDDLE")
1232 .unwrap());
1233 assert!(manager.is_rule_visible("control-valve", "MIDDLE").unwrap());
1234
1235 manager
1237 .import_from("TOP", "MIDDLE", ImportType::AllRules, "*")
1238 .unwrap();
1239
1240 assert!(manager.is_rule_visible("sensor-temp", "TOP").unwrap());
1242 assert!(manager.is_rule_visible("sensor-pressure", "TOP").unwrap());
1243 }
1247}