rust_rule_engine/engine/
agenda.rs1use crate::engine::rule::Rule;
2use std::collections::{HashMap, HashSet};
3
4#[derive(Debug, Clone)]
6pub struct AgendaManager {
7    active_group: String,
9    focus_stack: Vec<String>,
11    activated_groups: HashSet<String>,
13    fired_rules_per_activation: HashMap<String, HashSet<String>>,
15}
16
17impl Default for AgendaManager {
18    fn default() -> Self {
19        Self::new()
20    }
21}
22
23impl AgendaManager {
24    pub fn new() -> Self {
26        Self {
27            active_group: "MAIN".to_string(),
28            focus_stack: vec!["MAIN".to_string()],
29            activated_groups: HashSet::new(),
30            fired_rules_per_activation: HashMap::new(),
31        }
32    }
33
34    pub fn set_focus(&mut self, group: &str) {
36        let group = group.to_string();
37
38        self.focus_stack.retain(|g| g != &group);
40
41        self.focus_stack.push(group.clone());
43        self.active_group = group.clone();
44
45        self.activated_groups.insert(group.clone());
47
48        self.fired_rules_per_activation
50            .insert(group, HashSet::new());
51    }
52
53    pub fn get_active_group(&self) -> &str {
55        &self.active_group
56    }
57
58    pub fn should_evaluate_rule(&self, rule: &Rule) -> bool {
60        match &rule.agenda_group {
61            Some(group) => group == &self.active_group,
62            None => self.active_group == "MAIN", }
64    }
65
66    pub fn can_fire_rule(&self, rule: &Rule) -> bool {
68        if !rule.lock_on_active {
69            return true;
70        }
71
72        let main_group = "MAIN".to_string();
73        let group = rule.agenda_group.as_ref().unwrap_or(&main_group);
74
75        if !self.activated_groups.contains(group) {
77            return true;
78        }
79
80        if let Some(fired_rules) = self.fired_rules_per_activation.get(group) {
82            !fired_rules.contains(&rule.name)
83        } else {
84            true
85        }
86    }
87
88    pub fn mark_rule_fired(&mut self, rule: &Rule) {
90        if rule.lock_on_active {
91            let main_group = "MAIN".to_string();
92            let group = rule.agenda_group.as_ref().unwrap_or(&main_group);
93
94            self.activated_groups.insert(group.clone());
96
97            self.fired_rules_per_activation
99                .entry(group.clone())
100                .or_default()
101                .insert(rule.name.clone());
102        }
103    }
104
105    pub fn pop_focus(&mut self) -> Option<String> {
107        if self.focus_stack.len() > 1 {
108            self.focus_stack.pop();
109            if let Some(previous) = self.focus_stack.last() {
110                self.active_group = previous.clone();
111                Some(previous.clone())
112            } else {
113                None
114            }
115        } else {
116            None
117        }
118    }
119
120    pub fn clear_focus(&mut self) {
122        self.focus_stack.clear();
123        self.focus_stack.push("MAIN".to_string());
124        self.active_group = "MAIN".to_string();
125    }
126
127    pub fn get_agenda_groups(&self, rules: &[Rule]) -> Vec<String> {
129        let mut groups = HashSet::new();
130        groups.insert("MAIN".to_string());
131
132        for rule in rules {
133            if let Some(group) = &rule.agenda_group {
134                groups.insert(group.clone());
135            }
136        }
137
138        groups.into_iter().collect()
139    }
140
141    pub fn filter_rules<'a>(&self, rules: &'a [Rule]) -> Vec<&'a Rule> {
143        rules
144            .iter()
145            .filter(|rule| self.should_evaluate_rule(rule))
146            .collect()
147    }
148
149    pub fn reset_cycle(&mut self) {
151        }
155}
156
157#[derive(Debug, Clone)]
159pub struct ActivationGroupManager {
160    fired_groups: HashSet<String>,
162}
163
164impl Default for ActivationGroupManager {
165    fn default() -> Self {
166        Self::new()
167    }
168}
169
170impl ActivationGroupManager {
171    pub fn new() -> Self {
173        Self {
174            fired_groups: HashSet::new(),
175        }
176    }
177
178    pub fn can_fire(&self, rule: &Rule) -> bool {
180        if let Some(group) = &rule.activation_group {
181            !self.fired_groups.contains(group)
182        } else {
183            true }
185    }
186
187    pub fn mark_fired(&mut self, rule: &Rule) {
189        if let Some(group) = &rule.activation_group {
190            self.fired_groups.insert(group.clone());
191        }
192    }
193
194    pub fn reset_cycle(&mut self) {
196        self.fired_groups.clear();
197    }
198
199    pub fn get_activation_groups(&self, rules: &[Rule]) -> Vec<String> {
201        rules
202            .iter()
203            .filter_map(|rule| rule.activation_group.clone())
204            .collect::<HashSet<_>>()
205            .into_iter()
206            .collect()
207    }
208
209    pub fn has_group_fired(&self, group: &str) -> bool {
211        self.fired_groups.contains(group)
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218    use crate::engine::rule::{Condition, ConditionGroup, Rule};
219    use crate::types::{Operator, Value};
220
221    fn create_dummy_condition() -> ConditionGroup {
222        let condition = Condition {
223            field: "test".to_string(),
224            operator: Operator::Equal,
225            value: Value::Boolean(true),
226        };
227        ConditionGroup::single(condition)
228    }
229
230    #[test]
231    fn test_agenda_manager_basic() {
232        let mut manager = AgendaManager::new();
233        assert_eq!(manager.get_active_group(), "MAIN");
234
235        manager.set_focus("validation");
236        assert_eq!(manager.get_active_group(), "validation");
237
238        manager.set_focus("processing");
239        assert_eq!(manager.get_active_group(), "processing");
240
241        manager.pop_focus();
242        assert_eq!(manager.get_active_group(), "validation");
243    }
244
245    #[test]
246    fn test_agenda_manager_rule_filtering() {
247        let mut manager = AgendaManager::new();
248
249        let rule1 = Rule::new("Rule1".to_string(), create_dummy_condition(), vec![])
250            .with_agenda_group("validation".to_string());
251        let rule2 = Rule::new("Rule2".to_string(), create_dummy_condition(), vec![]);
252
253        let rules = vec![rule1.clone(), rule2.clone()];
255        let filtered = manager.filter_rules(&rules);
256        assert_eq!(filtered.len(), 1);
257        assert_eq!(filtered[0].name, "Rule2");
258
259        manager.set_focus("validation");
261        let filtered = manager.filter_rules(&rules);
262        assert_eq!(filtered.len(), 1);
263        assert_eq!(filtered[0].name, "Rule1");
264    }
265
266    #[test]
267    fn test_activation_group_manager() {
268        let mut manager = ActivationGroupManager::new();
269
270        let rule1 = Rule::new("Rule1".to_string(), create_dummy_condition(), vec![])
271            .with_activation_group("discount".to_string());
272        let rule2 = Rule::new("Rule2".to_string(), create_dummy_condition(), vec![])
273            .with_activation_group("discount".to_string());
274
275        assert!(manager.can_fire(&rule1));
277        assert!(manager.can_fire(&rule2));
278
279        manager.mark_fired(&rule1);
281        assert!(!manager.can_fire(&rule2));
282        assert!(manager.has_group_fired("discount"));
283
284        manager.reset_cycle();
286        assert!(manager.can_fire(&rule1));
287        assert!(manager.can_fire(&rule2));
288    }
289
290    #[test]
291    fn test_lock_on_active() {
292        let mut manager = AgendaManager::new();
293
294        let rule = Rule::new("TestRule".to_string(), create_dummy_condition(), vec![])
295            .with_lock_on_active(true);
296
297        assert!(manager.can_fire_rule(&rule));
299
300        manager.mark_rule_fired(&rule);
302
303        assert!(!manager.can_fire_rule(&rule));
305
306        manager.reset_cycle();
308        assert!(!manager.can_fire_rule(&rule));
309
310        manager.set_focus("validation");
312        manager.set_focus("MAIN");
313        assert!(manager.can_fire_rule(&rule));
314    }
315}