rust_rule_engine/backward/
goal.rs1use super::expression::Expression;
4use super::unification::Bindings;
5use std::collections::HashMap;
6
7#[derive(Debug, Clone)]
9pub struct Goal {
10 pub pattern: String,
12
13 pub expression: Option<Expression>,
15
16 pub status: GoalStatus,
18
19 pub sub_goals: Vec<Goal>,
21
22 pub candidate_rules: Vec<String>,
24
25 pub bindings: Bindings,
27
28 pub depth: usize,
30
31 pub is_negated: bool,
34}
35
36#[derive(Debug, Clone, PartialEq, Eq)]
38pub enum GoalStatus {
39 Pending,
41
42 InProgress,
44
45 Proven,
47
48 Unprovable,
50}
51
52impl Goal {
53 pub fn new(pattern: String) -> Self {
55 Self {
56 pattern,
57 expression: None,
58 status: GoalStatus::Pending,
59 sub_goals: Vec::new(),
60 candidate_rules: Vec::new(),
61 bindings: Bindings::new(),
62 depth: 0,
63 is_negated: false,
64 }
65 }
66
67 pub fn with_expression(pattern: String, expression: Expression) -> Self {
69 Self {
70 pattern,
71 expression: Some(expression),
72 status: GoalStatus::Pending,
73 sub_goals: Vec::new(),
74 candidate_rules: Vec::new(),
75 bindings: Bindings::new(),
76 depth: 0,
77 is_negated: false,
78 }
79 }
80
81 pub fn negated(pattern: String) -> Self {
83 Self {
84 pattern,
85 expression: None,
86 status: GoalStatus::Pending,
87 sub_goals: Vec::new(),
88 candidate_rules: Vec::new(),
89 bindings: Bindings::new(),
90 depth: 0,
91 is_negated: true,
92 }
93 }
94
95 pub fn negated_with_expression(pattern: String, expression: Expression) -> Self {
97 Self {
98 pattern,
99 expression: Some(expression),
100 status: GoalStatus::Pending,
101 sub_goals: Vec::new(),
102 candidate_rules: Vec::new(),
103 bindings: Bindings::new(),
104 depth: 0,
105 is_negated: true,
106 }
107 }
108
109 pub fn is_proven(&self) -> bool {
111 self.status == GoalStatus::Proven
112 }
113
114 pub fn is_unprovable(&self) -> bool {
116 self.status == GoalStatus::Unprovable
117 }
118
119 pub fn all_subgoals_proven(&self) -> bool {
121 self.sub_goals.iter().all(|g| g.is_proven())
122 }
123
124 pub fn add_subgoal(&mut self, goal: Goal) {
126 self.sub_goals.push(goal);
127 }
128
129 pub fn add_candidate_rule(&mut self, rule_name: String) {
131 if !self.candidate_rules.contains(&rule_name) {
132 self.candidate_rules.push(rule_name);
133 }
134 }
135}
136
137#[derive(Debug)]
139pub struct GoalManager {
140 goals: Vec<Goal>,
142
143 max_depth: usize,
145
146 proven_cache: HashMap<String, bool>,
148}
149
150impl GoalManager {
151 pub fn new(max_depth: usize) -> Self {
153 Self {
154 goals: Vec::new(),
155 max_depth,
156 proven_cache: HashMap::new(),
157 }
158 }
159
160 pub fn add_goal(&mut self, goal: Goal) {
162 self.goals.push(goal);
163 }
164
165 pub fn next_pending(&mut self) -> Option<&mut Goal> {
167 self.goals
168 .iter_mut()
169 .find(|g| g.status == GoalStatus::Pending)
170 }
171
172 pub fn is_cached(&self, pattern: &str) -> Option<bool> {
174 self.proven_cache.get(pattern).copied()
175 }
176
177 pub fn cache_result(&mut self, pattern: String, proven: bool) {
179 self.proven_cache.insert(pattern, proven);
180 }
181
182 pub fn is_too_deep(&self, depth: usize) -> bool {
184 depth > self.max_depth
185 }
186
187 pub fn goals(&self) -> &[Goal] {
189 &self.goals
190 }
191
192 pub fn clear(&mut self) {
194 self.goals.clear();
195 self.proven_cache.clear();
196 }
197}
198
199impl Default for GoalManager {
200 fn default() -> Self {
201 Self::new(10) }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::Value;
209
210 #[test]
211 fn test_goal_creation() {
212 let goal = Goal::new("User.IsVIP == true".to_string());
213 assert_eq!(goal.status, GoalStatus::Pending);
214 assert_eq!(goal.depth, 0);
215 assert!(goal.sub_goals.is_empty());
216 }
217
218 #[test]
219 fn test_goal_status_checks() {
220 let mut goal = Goal::new("test".to_string());
221 assert!(!goal.is_proven());
222
223 goal.status = GoalStatus::Proven;
224 assert!(goal.is_proven());
225
226 goal.status = GoalStatus::Unprovable;
227 assert!(goal.is_unprovable());
228 }
229
230 #[test]
231 fn test_subgoal_management() {
232 let mut parent = Goal::new("parent".to_string());
233 let mut child1 = Goal::new("child1".to_string());
234 let mut child2 = Goal::new("child2".to_string());
235
236 child1.status = GoalStatus::Proven;
237 child2.status = GoalStatus::Proven;
238
239 parent.add_subgoal(child1);
240 parent.add_subgoal(child2);
241
242 assert_eq!(parent.sub_goals.len(), 2);
243 assert!(parent.all_subgoals_proven());
244 }
245
246 #[test]
247 fn test_goal_manager() {
248 let mut manager = GoalManager::new(5);
249
250 let goal1 = Goal::new("goal1".to_string());
251 let goal2 = Goal::new("goal2".to_string());
252
253 manager.add_goal(goal1);
254 manager.add_goal(goal2);
255
256 assert_eq!(manager.goals().len(), 2);
257
258 assert!(manager.is_cached("goal1").is_none());
260 manager.cache_result("goal1".to_string(), true);
261 assert_eq!(manager.is_cached("goal1"), Some(true));
262
263 assert!(!manager.is_too_deep(3));
265 assert!(manager.is_too_deep(10));
266 }
267
268 #[test]
269 fn test_candidate_rules() {
270 let mut goal = Goal::new("test".to_string());
271
272 goal.add_candidate_rule("Rule1".to_string());
273 goal.add_candidate_rule("Rule2".to_string());
274 goal.add_candidate_rule("Rule1".to_string()); assert_eq!(goal.candidate_rules.len(), 2);
277 }
278
279 #[test]
280 fn test_goal_with_expression() {
281 use super::super::expression::Expression;
282 use crate::types::{Operator, Value};
283
284 let expr = Expression::Comparison {
285 left: Box::new(Expression::Field("User.IsVIP".to_string())),
286 operator: Operator::Equal,
287 right: Box::new(Expression::Literal(Value::Boolean(true))),
288 };
289
290 let goal = Goal::with_expression("User.IsVIP == true".to_string(), expr);
291 assert!(goal.expression.is_some());
292 assert_eq!(goal.pattern, "User.IsVIP == true");
293 assert_eq!(goal.status, GoalStatus::Pending);
294 }
295
296 #[test]
297 fn test_subgoals_not_all_proven() {
298 let mut parent = Goal::new("parent".to_string());
299 let mut child1 = Goal::new("child1".to_string());
300 let mut child2 = Goal::new("child2".to_string());
301
302 child1.status = GoalStatus::Proven;
303 child2.status = GoalStatus::Pending; parent.add_subgoal(child1);
306 parent.add_subgoal(child2);
307
308 assert_eq!(parent.sub_goals.len(), 2);
309 assert!(!parent.all_subgoals_proven());
310 }
311
312 #[test]
313 fn test_goal_manager_next_pending() {
314 let mut manager = GoalManager::new(5);
315
316 let mut goal1 = Goal::new("goal1".to_string());
317 goal1.status = GoalStatus::Proven; let goal2 = Goal::new("goal2".to_string()); let mut goal3 = Goal::new("goal3".to_string());
322 goal3.status = GoalStatus::InProgress;
323
324 manager.add_goal(goal1);
325 manager.add_goal(goal2);
326 manager.add_goal(goal3);
327
328 let next = manager.next_pending();
330 assert!(next.is_some());
331 assert_eq!(next.unwrap().pattern, "goal2");
332 }
333
334 #[test]
335 fn test_goal_manager_clear() {
336 let mut manager = GoalManager::new(5);
337
338 manager.add_goal(Goal::new("goal1".to_string()));
339 manager.add_goal(Goal::new("goal2".to_string()));
340 manager.cache_result("goal1".to_string(), true);
341
342 assert_eq!(manager.goals().len(), 2);
343 assert!(manager.is_cached("goal1").is_some());
344
345 manager.clear();
346
347 assert_eq!(manager.goals().len(), 0);
348 assert!(manager.is_cached("goal1").is_none());
349 }
350
351 #[test]
352 fn test_goal_bindings() {
353 let mut goal = Goal::new("User.?X == true".to_string());
354
355 assert!(goal.bindings.get("X").is_none());
357
358 goal.bindings
360 .bind("X".to_string(), Value::String("IsVIP".to_string()))
361 .ok();
362
363 assert!(goal.bindings.get("X").is_some());
365 assert_eq!(
366 goal.bindings.get("X"),
367 Some(&Value::String("IsVIP".to_string()))
368 );
369 }
370
371 #[test]
372 fn test_goal_depth() {
373 let mut goal = Goal::new("test".to_string());
374 assert_eq!(goal.depth, 0);
375
376 goal.depth = 5;
377 assert_eq!(goal.depth, 5);
378 }
379
380 #[test]
381 fn test_goal_manager_default() {
382 let manager = GoalManager::default();
383 assert_eq!(manager.max_depth, 10);
384 assert_eq!(manager.goals().len(), 0);
385 }
386
387 #[test]
388 fn test_negated_goal() {
389 let goal = Goal::negated("User.IsBanned == true".to_string());
390 assert_eq!(goal.pattern, "User.IsBanned == true");
391 assert!(goal.is_negated);
392 assert_eq!(goal.status, GoalStatus::Pending);
393 }
394
395 #[test]
396 fn test_negated_goal_with_expression() {
397 use super::super::expression::Expression;
398 use crate::types::{Operator, Value};
399
400 let expr = Expression::Comparison {
401 left: Box::new(Expression::Field("User.IsBanned".to_string())),
402 operator: Operator::Equal,
403 right: Box::new(Expression::Literal(Value::Boolean(true))),
404 };
405
406 let goal = Goal::negated_with_expression("User.IsBanned == true".to_string(), expr);
407 assert!(goal.expression.is_some());
408 assert!(goal.is_negated);
409 assert_eq!(goal.pattern, "User.IsBanned == true");
410 }
411
412 #[test]
413 fn test_normal_goal_not_negated() {
414 let goal = Goal::new("test".to_string());
415 assert!(!goal.is_negated);
416
417 let expr = Expression::Field("test".to_string());
418 let goal_with_expr = Goal::with_expression("test".to_string(), expr);
419 assert!(!goal_with_expr.is_negated);
420 }
421}