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}