dredd_rs/rule/
chain_rule.rs

1use crate::rule::{EvalFn, ExecuteFn, Rule, RuleContext, RuleError, RuleResult};
2
3/// ChainRule represents a rule that can have at most one child.
4/// When executed, it will run its child rule if the evaluation succeeds.
5///
6/// # Example
7///
8/// ```rust
9/// use dredd_rs::rule::*;
10///
11/// let mut rule = ChainRule::new();
12/// rule.set_eval_fn(|context| {
13///     context.get_bool("should_execute")
14/// });
15/// rule.set_execute_fn(|context| {
16///     context.set_bool("executed", true);
17///     Ok(())
18/// });
19///
20/// let mut context = RuleContext::new();
21/// context.set_bool("should_execute", true);
22///
23/// let result = rule.fire(&mut context).unwrap();
24/// assert!(result);
25/// ```
26pub struct ChainRule {
27    child: Option<Box<dyn Rule>>,
28    eval_fn: Option<EvalFn>,
29    pre_execute_fn: Option<ExecuteFn>,
30    execute_fn: Option<ExecuteFn>,
31    post_execute_fn: Option<ExecuteFn>,
32}
33
34impl ChainRule {
35    /// Create a new ChainRule
36    pub fn new() -> Self {
37        ChainRule {
38            child: None,
39            eval_fn: None,
40            pre_execute_fn: None,
41            execute_fn: None,
42            post_execute_fn: None,
43        }
44    }
45
46    /// Create a builder for ChainRule
47    pub fn builder() -> ChainRuleBuilder {
48        ChainRuleBuilder::new()
49    }
50
51    /// Set the evaluation function
52    pub fn set_eval_fn<F>(&mut self, f: F) -> &mut Self
53    where
54        F: Fn(&RuleContext) -> RuleResult<bool> + 'static,
55    {
56        self.eval_fn = Some(Box::new(f));
57        self
58    }
59
60    /// Set the pre-execution function
61    pub fn set_pre_execute_fn<F>(&mut self, f: F) -> &mut Self
62    where
63        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
64    {
65        self.pre_execute_fn = Some(Box::new(f));
66        self
67    }
68
69    /// Set the execution function
70    pub fn set_execute_fn<F>(&mut self, f: F) -> &mut Self
71    where
72        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
73    {
74        self.execute_fn = Some(Box::new(f));
75        self
76    }
77
78    /// Set the post-execution function
79    pub fn set_post_execute_fn<F>(&mut self, f: F) -> &mut Self
80    where
81        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
82    {
83        self.post_execute_fn = Some(Box::new(f));
84        self
85    }
86
87    /// Add a child rule (ChainRule can only have one child)
88    pub fn set_child(&mut self, child: Box<dyn Rule>) -> RuleResult<&mut Self> {
89        if self.child.is_some() {
90            return Err(RuleError::TooManyChildren {
91                max: 1,
92                attempted: 2,
93            });
94        }
95        self.child = Some(child);
96        Ok(self)
97    }
98}
99
100impl Rule for ChainRule {
101    fn evaluate(&self, context: &RuleContext) -> RuleResult<bool> {
102        match &self.eval_fn {
103            Some(f) => f(context),
104            None => Ok(true), // Default: always evaluate to true
105        }
106    }
107
108    fn execute(&mut self, context: &mut RuleContext) -> RuleResult<()> {
109        // Pre-execute
110        if let Some(f) = &self.pre_execute_fn {
111            f(context)?;
112        }
113
114        // Main execute
115        if let Some(f) = &self.execute_fn {
116            f(context)?;
117        }
118
119        // Post-execute
120        if let Some(f) = &self.post_execute_fn {
121            f(context)?;
122        }
123
124        Ok(())
125    }
126
127    fn children(&self) -> &[Box<dyn Rule>] {
128        match &self.child {
129            Some(child) => std::slice::from_ref(child),
130            None => &[],
131        }
132    }
133
134    fn children_mut(&mut self) -> &mut Vec<Box<dyn Rule>> {
135        // This is a bit tricky for ChainRule since it has at most one child
136        // We'll implement it differently in the fire method
137        unimplemented!("ChainRule uses custom child execution in fire()")
138    }
139
140    fn add_child(&mut self, child: Box<dyn Rule>) -> RuleResult<()> {
141        if self.child.is_some() {
142            return Err(RuleError::TooManyChildren {
143                max: 1,
144                attempted: 2,
145            });
146        }
147        self.child = Some(child);
148        Ok(())
149    }
150
151    /// Custom fire implementation for ChainRule that handles single child execution
152    fn fire(&mut self, context: &mut RuleContext) -> RuleResult<bool> {
153        if self.evaluate(context)? {
154            self.execute(context)?;
155
156            // Execute the single child if it exists
157            if let Some(child) = &mut self.child {
158                child.fire(context)?;
159            }
160
161            Ok(true)
162        } else {
163            Ok(false)
164        }
165    }
166}
167
168impl Default for ChainRule {
169    fn default() -> Self {
170        Self::new()
171    }
172}
173
174/// Builder for ChainRule to provide a more ergonomic API
175pub struct ChainRuleBuilder {
176    rule: ChainRule,
177}
178
179impl ChainRuleBuilder {
180    /// Create a new ChainRuleBuilder
181    pub fn new() -> Self {
182        ChainRuleBuilder {
183            rule: ChainRule::new(),
184        }
185    }
186
187    /// Set the evaluation function
188    pub fn eval_fn<F>(mut self, f: F) -> Self
189    where
190        F: Fn(&RuleContext) -> RuleResult<bool> + 'static,
191    {
192        self.rule.set_eval_fn(f);
193        self
194    }
195
196    /// Set the pre-execution function
197    pub fn pre_execute_fn<F>(mut self, f: F) -> Self
198    where
199        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
200    {
201        self.rule.set_pre_execute_fn(f);
202        self
203    }
204
205    /// Set the execution function
206    pub fn execute_fn<F>(mut self, f: F) -> Self
207    where
208        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
209    {
210        self.rule.set_execute_fn(f);
211        self
212    }
213
214    /// Set the post-execution function
215    pub fn post_execute_fn<F>(mut self, f: F) -> Self
216    where
217        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
218    {
219        self.rule.set_post_execute_fn(f);
220        self
221    }
222
223    /// Add a child rule
224    pub fn child(mut self, child: Box<dyn Rule>) -> RuleResult<Self> {
225        self.rule.add_child(child)?;
226        Ok(self)
227    }
228
229    /// Build the ChainRule
230    pub fn build(self) -> ChainRule {
231        self.rule
232    }
233}
234
235impl Default for ChainRuleBuilder {
236    fn default() -> Self {
237        Self::new()
238    }
239}