use crate::errors::{Result, RuleEngineError};
use std::collections::{HashMap, HashSet, VecDeque};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ItemType {
Rule,
Template,
Fact,
All,
}
#[derive(Debug, Clone, PartialEq)]
pub enum ExportList {
All,
None,
Specific(Vec<ExportItem>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct ExportItem {
pub item_type: ItemType,
pub pattern: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ImportType {
AllRules,
AllTemplates,
Rules,
Templates,
All,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ReExport {
pub patterns: Vec<String>,
pub transitive: bool,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ImportDecl {
pub from_module: String,
pub import_type: ImportType,
pub pattern: String,
pub re_export: Option<ReExport>,
}
#[derive(Debug, Clone)]
pub struct Module {
pub name: String,
rules: HashSet<String>,
templates: HashSet<String>,
fact_types: HashSet<String>,
exports: ExportList,
imports: Vec<ImportDecl>,
pub doc: Option<String>,
salience: i32,
}
impl Module {
pub fn new(name: impl Into<String>) -> Self {
let name = name.into();
let exports = if name == "MAIN" {
ExportList::All
} else {
ExportList::None
};
Self {
name,
rules: HashSet::new(),
templates: HashSet::new(),
fact_types: HashSet::new(),
exports,
imports: Vec::new(),
doc: None,
salience: 0,
}
}
pub fn with_doc(mut self, doc: impl Into<String>) -> Self {
self.doc = Some(doc.into());
self
}
pub fn add_rule(&mut self, rule_name: impl Into<String>) {
self.rules.insert(rule_name.into());
}
pub fn add_template(&mut self, template_name: impl Into<String>) {
self.templates.insert(template_name.into());
}
pub fn add_fact_type(&mut self, fact_type: impl Into<String>) {
self.fact_types.insert(fact_type.into());
}
pub fn set_exports(&mut self, exports: ExportList) {
self.exports = exports;
}
pub fn get_exports(&self) -> &ExportList {
&self.exports
}
pub fn add_import(&mut self, import: ImportDecl) {
self.imports.push(import);
}
pub fn exports_rule(&self, rule_name: &str) -> bool {
let is_owned = self.rules.contains(rule_name);
let exports_owned = match &self.exports {
ExportList::All => is_owned,
ExportList::None => false,
ExportList::Specific(items) => {
is_owned
&& items.iter().any(|item| {
matches!(item.item_type, ItemType::Rule | ItemType::All)
&& pattern_matches(&item.pattern, rule_name)
})
}
};
exports_owned || self.should_re_export_rule(rule_name)
}
pub fn exports_template(&self, template_name: &str) -> bool {
let is_owned = self.templates.contains(template_name);
let exports_owned = match &self.exports {
ExportList::All => is_owned,
ExportList::None => false,
ExportList::Specific(items) => {
is_owned
&& items.iter().any(|item| {
matches!(item.item_type, ItemType::Template | ItemType::All)
&& pattern_matches(&item.pattern, template_name)
})
}
};
exports_owned || self.should_re_export_template(template_name)
}
pub fn get_rules(&self) -> &HashSet<String> {
&self.rules
}
pub fn get_templates(&self) -> &HashSet<String> {
&self.templates
}
pub fn get_imports(&self) -> &[ImportDecl] {
&self.imports
}
pub fn set_salience(&mut self, salience: i32) {
self.salience = salience;
}
pub fn get_salience(&self) -> i32 {
self.salience
}
pub fn should_re_export_rule(&self, rule_name: &str) -> bool {
for import in &self.imports {
if let Some(re_export) = &import.re_export {
if re_export
.patterns
.iter()
.any(|p| pattern_matches(p, rule_name))
{
return true;
}
}
}
false
}
pub fn should_re_export_template(&self, template_name: &str) -> bool {
for import in &self.imports {
if let Some(re_export) = &import.re_export {
if re_export
.patterns
.iter()
.any(|p| pattern_matches(p, template_name))
{
return true;
}
}
}
false
}
}
#[derive(Debug, Clone)]
pub struct CycleError {
pub cycle_path: Vec<String>,
}
impl std::fmt::Display for CycleError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Cyclic import detected: {}",
self.cycle_path.join(" -> ")
)
}
}
#[derive(Debug, Clone)]
pub struct ModuleManager {
modules: HashMap<String, Module>,
current_focus: String,
default_module: String,
import_graph: HashMap<String, HashSet<String>>,
}
impl ModuleManager {
pub fn new() -> Self {
let mut modules = HashMap::new();
modules.insert("MAIN".to_string(), Module::new("MAIN"));
Self {
modules,
current_focus: "MAIN".to_string(),
default_module: "MAIN".to_string(),
import_graph: HashMap::new(),
}
}
pub fn create_module(&mut self, name: impl Into<String>) -> Result<&mut Module> {
let name = name.into();
if self.modules.contains_key(&name) {
return Err(RuleEngineError::ModuleError {
message: format!("Module '{}' already exists", name),
});
}
self.modules.insert(name.clone(), Module::new(&name));
Ok(self.modules.get_mut(&name).unwrap())
}
pub fn get_module_mut(&mut self, name: &str) -> Result<&mut Module> {
self.modules
.get_mut(name)
.ok_or_else(|| RuleEngineError::ModuleError {
message: format!("Module '{}' not found", name),
})
}
pub fn get_module(&self, name: &str) -> Result<&Module> {
self.modules
.get(name)
.ok_or_else(|| RuleEngineError::ModuleError {
message: format!("Module '{}' not found", name),
})
}
pub fn delete_module(&mut self, name: &str) -> Result<()> {
if name == self.default_module {
return Err(RuleEngineError::ModuleError {
message: "Cannot delete default module".to_string(),
});
}
if name == self.current_focus {
self.current_focus = self.default_module.clone();
}
self.modules
.remove(name)
.ok_or_else(|| RuleEngineError::ModuleError {
message: format!("Module '{}' not found", name),
})?;
self.import_graph.remove(name);
for (_, imports) in self.import_graph.iter_mut() {
imports.remove(name);
}
Ok(())
}
pub fn set_focus(&mut self, module_name: impl Into<String>) -> Result<()> {
let module_name = module_name.into();
if !self.modules.contains_key(&module_name) {
return Err(RuleEngineError::ModuleError {
message: format!("Module '{}' not found", module_name),
});
}
self.current_focus = module_name;
Ok(())
}
pub fn get_focus(&self) -> &str {
&self.current_focus
}
pub fn list_modules(&self) -> Vec<String> {
self.modules.keys().cloned().collect()
}
pub fn export_all_from(&mut self, module_name: &str, export_list: ExportList) -> Result<()> {
let module = self.get_module_mut(module_name)?;
module.set_exports(export_list);
Ok(())
}
fn detect_cycle(&self, to_module: &str, from_module: &str) -> Result<()> {
if to_module == from_module {
return Err(RuleEngineError::ModuleError {
message: format!(
"Cyclic import detected: {} cannot import from itself",
to_module
),
});
}
let mut queue = VecDeque::new();
let mut visited = HashSet::new();
let mut parent_map: HashMap<String, String> = HashMap::new();
queue.push_back(from_module.to_string());
visited.insert(from_module.to_string());
while let Some(current) = queue.pop_front() {
if let Some(imports) = self.import_graph.get(¤t) {
for imported in imports {
if imported == to_module {
let mut cycle_path = vec![to_module.to_string()];
let mut node = current.clone();
while let Some(parent) = parent_map.get(&node) {
cycle_path.push(node.clone());
node = parent.clone();
}
cycle_path.push(node);
cycle_path.reverse();
return Err(RuleEngineError::ModuleError {
message: format!("Cyclic import detected: {}", cycle_path.join(" -> ")),
});
}
if !visited.contains(imported) {
visited.insert(imported.clone());
parent_map.insert(imported.clone(), current.clone());
queue.push_back(imported.clone());
}
}
}
}
Ok(())
}
pub fn get_import_graph(&self) -> &HashMap<String, HashSet<String>> {
&self.import_graph
}
pub fn get_import_graph_debug(&self) -> Vec<(String, Vec<String>)> {
self.import_graph
.iter()
.map(|(module, imports)| (module.clone(), imports.iter().cloned().collect()))
.collect()
}
pub fn import_from(
&mut self,
to_module: &str,
from_module: &str,
import_type: ImportType,
pattern: impl Into<String>,
) -> Result<()> {
self.import_from_with_reexport(to_module, from_module, import_type, pattern, None)
}
pub fn import_from_with_reexport(
&mut self,
to_module: &str,
from_module: &str,
import_type: ImportType,
pattern: impl Into<String>,
re_export: Option<ReExport>,
) -> Result<()> {
if !self.modules.contains_key(from_module) {
return Err(RuleEngineError::ModuleError {
message: format!("Source module '{}' not found", from_module),
});
}
self.detect_cycle(to_module, from_module)?;
let module = self.get_module_mut(to_module)?;
module.add_import(ImportDecl {
from_module: from_module.to_string(),
import_type,
pattern: pattern.into(),
re_export,
});
self.import_graph
.entry(to_module.to_string())
.or_default()
.insert(from_module.to_string());
Ok(())
}
pub fn is_rule_visible(&self, rule_name: &str, to_module: &str) -> Result<bool> {
let module = self.get_module(to_module)?;
if module.get_rules().contains(rule_name) {
return Ok(true);
}
for import in module.get_imports() {
if !matches!(
import.import_type,
ImportType::AllRules | ImportType::Rules | ImportType::All
) {
continue;
}
let from_module = self.get_module(&import.from_module)?;
if from_module.exports_rule(rule_name) && pattern_matches(&import.pattern, rule_name) {
return Ok(true);
}
}
Ok(false)
}
pub fn is_template_visible(&self, template_name: &str, to_module: &str) -> Result<bool> {
let module = self.get_module(to_module)?;
if module.get_templates().contains(template_name) {
return Ok(true);
}
for import in module.get_imports() {
if !matches!(
import.import_type,
ImportType::AllTemplates | ImportType::Templates | ImportType::All
) {
continue;
}
let from_module = self.get_module(&import.from_module)?;
if from_module.exports_template(template_name)
&& pattern_matches(&import.pattern, template_name)
{
return Ok(true);
}
}
Ok(false)
}
pub fn get_visible_rules(&self, module_name: &str) -> Result<Vec<String>> {
let module = self.get_module(module_name)?;
let mut visible = HashSet::new();
visible.extend(module.get_rules().iter().cloned());
for import in module.get_imports() {
if !matches!(
import.import_type,
ImportType::AllRules | ImportType::Rules | ImportType::All
) {
continue;
}
let from_module = self.get_module(&import.from_module)?;
for rule in from_module.get_rules() {
if from_module.exports_rule(rule) && pattern_matches(&import.pattern, rule) {
visible.insert(rule.clone());
}
}
}
Ok(visible.into_iter().collect())
}
pub fn get_stats(&self) -> ModuleStats {
ModuleStats {
total_modules: self.modules.len(),
current_focus: self.current_focus.clone(),
modules: self
.modules
.iter()
.map(|(name, module)| {
(
name.clone(),
ModuleInfo {
name: name.clone(),
rules_count: module.rules.len(),
templates_count: module.templates.len(),
imports_count: module.imports.len(),
exports_type: match &module.exports {
ExportList::All => "All".to_string(),
ExportList::None => "None".to_string(),
ExportList::Specific(items) => format!("Specific({})", items.len()),
},
salience: module.salience,
},
)
})
.collect(),
}
}
pub fn set_module_salience(&mut self, module_name: &str, salience: i32) -> Result<()> {
let module = self.get_module_mut(module_name)?;
module.set_salience(salience);
Ok(())
}
pub fn get_module_salience(&self, module_name: &str) -> Result<i32> {
let module = self.get_module(module_name)?;
Ok(module.get_salience())
}
pub fn get_transitive_dependencies(&self, module_name: &str) -> Result<Vec<String>> {
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut result = Vec::new();
queue.push_back(module_name.to_string());
visited.insert(module_name.to_string());
while let Some(current) = queue.pop_front() {
if let Some(imports) = self.import_graph.get(¤t) {
for imported in imports {
if !visited.contains(imported) {
visited.insert(imported.clone());
result.push(imported.clone());
queue.push_back(imported.clone());
}
}
}
}
Ok(result)
}
pub fn validate_module(&self, module_name: &str) -> Result<ModuleValidation> {
let module = self.get_module(module_name)?;
let mut warnings = Vec::new();
let mut errors = Vec::new();
for import in module.get_imports() {
if !self.modules.contains_key(&import.from_module) {
errors.push(format!(
"Import references non-existent module: {}",
import.from_module
));
}
}
for import in module.get_imports() {
if let Ok(from_module) = self.get_module(&import.from_module) {
let mut has_visible = false;
match import.import_type {
ImportType::AllRules | ImportType::Rules | ImportType::All => {
for rule in from_module.get_rules() {
if from_module.exports_rule(rule)
&& pattern_matches(&import.pattern, rule)
{
has_visible = true;
break;
}
}
}
ImportType::AllTemplates | ImportType::Templates => {
for template in from_module.get_templates() {
if from_module.exports_template(template)
&& pattern_matches(&import.pattern, template)
{
has_visible = true;
break;
}
}
}
}
if !has_visible {
warnings.push(format!(
"Import from '{}' with pattern '{}' doesn't match any exported items",
import.from_module, import.pattern
));
}
}
}
for import in module.get_imports() {
if let Some(re_export) = &import.re_export {
for pattern in &re_export.patterns {
let mut matches_any = false;
if let Ok(from_module) = self.get_module(&import.from_module) {
for rule in from_module.get_rules() {
if from_module.exports_rule(rule) && pattern_matches(pattern, rule) {
matches_any = true;
break;
}
}
if !matches_any {
for template in from_module.get_templates() {
if from_module.exports_template(template)
&& pattern_matches(pattern, template)
{
matches_any = true;
break;
}
}
}
}
if !matches_any {
warnings.push(format!(
"Re-export pattern '{}' from import '{}' doesn't match any items",
pattern, import.from_module
));
}
}
}
}
if module.get_rules().is_empty()
&& module.get_templates().is_empty()
&& module.get_imports().is_empty()
{
warnings.push("Module is empty (no rules, templates, or imports)".to_string());
}
Ok(ModuleValidation {
module_name: module_name.to_string(),
is_valid: errors.is_empty(),
errors,
warnings,
})
}
pub fn validate_all_modules(&self) -> HashMap<String, ModuleValidation> {
self.modules
.keys()
.filter_map(|name| self.validate_module(name).ok().map(|v| (name.clone(), v)))
.collect()
}
}
impl Default for ModuleManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub struct ModuleStats {
pub total_modules: usize,
pub current_focus: String,
pub modules: HashMap<String, ModuleInfo>,
}
#[derive(Debug, Clone)]
pub struct ModuleInfo {
pub name: String,
pub rules_count: usize,
pub templates_count: usize,
pub imports_count: usize,
pub exports_type: String,
pub salience: i32,
}
#[derive(Debug, Clone)]
pub struct ModuleValidation {
pub module_name: String,
pub is_valid: bool,
pub errors: Vec<String>,
pub warnings: Vec<String>,
}
fn pattern_matches(pattern: &str, name: &str) -> bool {
if pattern == "*" || pattern == "?ALL" {
return true;
}
if let Some(prefix) = pattern.strip_suffix('*') {
name.starts_with(prefix)
} else if let Some(suffix) = pattern.strip_prefix('*') {
name.ends_with(suffix)
} else {
pattern == name
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_module_creation() {
let mut manager = ModuleManager::new();
assert!(manager.create_module("TEST").is_ok());
assert!(manager.create_module("TEST").is_err());
assert_eq!(manager.list_modules().len(), 2); }
#[test]
fn test_module_focus() {
let mut manager = ModuleManager::new();
manager.create_module("SENSORS").unwrap();
assert_eq!(manager.get_focus(), "MAIN");
manager.set_focus("SENSORS").unwrap();
assert_eq!(manager.get_focus(), "SENSORS");
assert!(manager.set_focus("NONEXISTENT").is_err());
}
#[test]
fn test_export_import() {
let mut manager = ModuleManager::new();
manager.create_module("SENSORS").unwrap();
manager.create_module("CONTROL").unwrap();
let sensors = manager.get_module_mut("SENSORS").unwrap();
sensors.add_rule("sensor-temp");
sensors.add_rule("sensor-pressure");
sensors.set_exports(ExportList::Specific(vec![ExportItem {
item_type: ItemType::Rule,
pattern: "sensor-*".to_string(),
}]));
manager
.import_from("CONTROL", "SENSORS", ImportType::AllRules, "*")
.unwrap();
assert!(manager.is_rule_visible("sensor-temp", "CONTROL").unwrap());
assert!(manager
.is_rule_visible("sensor-pressure", "CONTROL")
.unwrap());
}
#[test]
fn test_pattern_matching() {
assert!(pattern_matches("*", "anything"));
assert!(pattern_matches("sensor-*", "sensor-temp"));
assert!(pattern_matches("sensor-*", "sensor-pressure"));
assert!(!pattern_matches("sensor-*", "control-temp"));
assert!(pattern_matches("*-temp", "sensor-temp"));
assert!(pattern_matches("exact", "exact"));
assert!(!pattern_matches("exact", "not-exact"));
}
#[test]
fn test_main_module_default_export() {
let manager = ModuleManager::new();
let main_module = manager.get_module("MAIN").unwrap();
assert!(matches!(main_module.exports, ExportList::All));
}
#[test]
fn test_user_module_default_export() {
let mut manager = ModuleManager::new();
manager.create_module("USER").unwrap();
let user_module = manager.get_module("USER").unwrap();
assert!(matches!(user_module.exports, ExportList::None));
}
#[test]
fn test_visibility_own_rules() {
let mut manager = ModuleManager::new();
manager.create_module("TEST").unwrap();
let test_module = manager.get_module_mut("TEST").unwrap();
test_module.add_rule("my-rule");
assert!(manager.is_rule_visible("my-rule", "TEST").unwrap());
}
#[test]
fn test_get_visible_rules() {
let mut manager = ModuleManager::new();
manager.create_module("MOD1").unwrap();
manager.create_module("MOD2").unwrap();
let mod1 = manager.get_module_mut("MOD1").unwrap();
mod1.add_rule("rule1");
mod1.add_rule("rule2");
mod1.set_exports(ExportList::All);
let mod2 = manager.get_module_mut("MOD2").unwrap();
mod2.add_rule("rule3");
manager
.import_from("MOD2", "MOD1", ImportType::AllRules, "*")
.unwrap();
let visible = manager.get_visible_rules("MOD2").unwrap();
assert!(visible.contains(&"rule1".to_string()));
assert!(visible.contains(&"rule2".to_string()));
assert!(visible.contains(&"rule3".to_string()));
assert_eq!(visible.len(), 3);
}
#[test]
fn test_module_stats() {
let mut manager = ModuleManager::new();
manager.create_module("TEST").unwrap();
let test_module = manager.get_module_mut("TEST").unwrap();
test_module.add_rule("rule1");
test_module.add_template("template1");
let stats = manager.get_stats();
assert_eq!(stats.total_modules, 2); assert_eq!(stats.current_focus, "MAIN");
let test_info = stats.modules.get("TEST").unwrap();
assert_eq!(test_info.rules_count, 1);
assert_eq!(test_info.templates_count, 1);
}
#[test]
fn test_transitive_reexport() {
let mut manager = ModuleManager::new();
manager.create_module("BASE").unwrap();
manager.create_module("MIDDLE").unwrap();
manager.create_module("TOP").unwrap();
let base = manager.get_module_mut("BASE").unwrap();
base.add_rule("base-rule1");
base.add_rule("base-rule2");
base.set_exports(ExportList::All);
manager
.import_from_with_reexport(
"MIDDLE",
"BASE",
ImportType::AllRules,
"*",
Some(ReExport {
patterns: vec!["base-*".to_string()],
transitive: true,
}),
)
.unwrap();
manager
.import_from("TOP", "MIDDLE", ImportType::AllRules, "*")
.unwrap();
assert!(manager.is_rule_visible("base-rule1", "TOP").unwrap());
assert!(manager.is_rule_visible("base-rule2", "TOP").unwrap());
}
#[test]
fn test_module_salience() {
let mut manager = ModuleManager::new();
manager.create_module("HIGH_PRIORITY").unwrap();
manager.create_module("LOW_PRIORITY").unwrap();
manager.set_module_salience("HIGH_PRIORITY", 100).unwrap();
manager.set_module_salience("LOW_PRIORITY", -50).unwrap();
assert_eq!(manager.get_module_salience("HIGH_PRIORITY").unwrap(), 100);
assert_eq!(manager.get_module_salience("LOW_PRIORITY").unwrap(), -50);
assert_eq!(manager.get_module_salience("MAIN").unwrap(), 0);
let stats = manager.get_stats();
assert_eq!(stats.modules.get("HIGH_PRIORITY").unwrap().salience, 100);
assert_eq!(stats.modules.get("LOW_PRIORITY").unwrap().salience, -50);
}
#[test]
fn test_transitive_dependencies() {
let mut manager = ModuleManager::new();
manager.create_module("A").unwrap();
manager.create_module("B").unwrap();
manager.create_module("C").unwrap();
manager.create_module("D").unwrap();
manager.import_from("A", "B", ImportType::All, "*").unwrap();
manager.import_from("B", "C", ImportType::All, "*").unwrap();
manager.import_from("C", "D", ImportType::All, "*").unwrap();
let deps = manager.get_transitive_dependencies("A").unwrap();
assert!(deps.contains(&"B".to_string()));
assert!(deps.contains(&"C".to_string()));
assert!(deps.contains(&"D".to_string()));
assert_eq!(deps.len(), 3);
}
#[test]
fn test_module_validation_broken_import() {
let mut manager = ModuleManager::new();
manager.create_module("TEST").unwrap();
let test_module = manager.get_module_mut("TEST").unwrap();
test_module.add_import(ImportDecl {
from_module: "NONEXISTENT".to_string(),
import_type: ImportType::All,
pattern: "*".to_string(),
re_export: None,
});
let validation = manager.validate_module("TEST").unwrap();
assert!(!validation.is_valid);
assert!(validation.errors.iter().any(|e| e.contains("NONEXISTENT")));
}
#[test]
fn test_module_validation_unused_import() {
let mut manager = ModuleManager::new();
manager.create_module("SOURCE").unwrap();
manager.create_module("TARGET").unwrap();
let source = manager.get_module_mut("SOURCE").unwrap();
source.add_rule("my-rule");
source.set_exports(ExportList::None);
manager
.import_from("TARGET", "SOURCE", ImportType::AllRules, "*")
.unwrap();
let validation = manager.validate_module("TARGET").unwrap();
assert!(validation.is_valid); assert!(!validation.warnings.is_empty());
assert!(validation
.warnings
.iter()
.any(|w| w.contains("doesn't match any exported items")));
}
#[test]
fn test_module_validation_empty_module() {
let mut manager = ModuleManager::new();
manager.create_module("EMPTY").unwrap();
let validation = manager.validate_module("EMPTY").unwrap();
assert!(validation.is_valid);
assert!(validation.warnings.iter().any(|w| w.contains("empty")));
}
#[test]
fn test_module_validation_reexport_pattern() {
let mut manager = ModuleManager::new();
manager.create_module("SOURCE").unwrap();
manager.create_module("TARGET").unwrap();
let source = manager.get_module_mut("SOURCE").unwrap();
source.add_rule("rule1");
source.set_exports(ExportList::All);
manager
.import_from_with_reexport(
"TARGET",
"SOURCE",
ImportType::AllRules,
"*",
Some(ReExport {
patterns: vec!["sensor-*".to_string()], transitive: false,
}),
)
.unwrap();
let validation = manager.validate_module("TARGET").unwrap();
assert!(validation.is_valid);
assert!(validation
.warnings
.iter()
.any(|w| w.contains("Re-export pattern")));
}
#[test]
fn test_validate_all_modules() {
let mut manager = ModuleManager::new();
manager.create_module("MOD1").unwrap();
manager.create_module("MOD2").unwrap();
let validations = manager.validate_all_modules();
assert_eq!(validations.len(), 3); assert!(validations.contains_key("MAIN"));
assert!(validations.contains_key("MOD1"));
assert!(validations.contains_key("MOD2"));
}
#[test]
fn test_selective_reexport() {
let mut manager = ModuleManager::new();
manager.create_module("BASE").unwrap();
manager.create_module("MIDDLE").unwrap();
manager.create_module("TOP").unwrap();
let base = manager.get_module_mut("BASE").unwrap();
base.add_rule("sensor-temp");
base.add_rule("sensor-pressure");
base.add_rule("control-valve");
base.set_exports(ExportList::All);
manager
.import_from_with_reexport(
"MIDDLE",
"BASE",
ImportType::AllRules,
"*",
Some(ReExport {
patterns: vec!["sensor-*".to_string()],
transitive: true,
}),
)
.unwrap();
assert!(manager.is_rule_visible("sensor-temp", "MIDDLE").unwrap());
assert!(manager
.is_rule_visible("sensor-pressure", "MIDDLE")
.unwrap());
assert!(manager.is_rule_visible("control-valve", "MIDDLE").unwrap());
manager
.import_from("TOP", "MIDDLE", ImportType::AllRules, "*")
.unwrap();
assert!(manager.is_rule_visible("sensor-temp", "TOP").unwrap());
assert!(manager.is_rule_visible("sensor-pressure", "TOP").unwrap());
}
}