dredd_rs/
rule.rs

1use std::{collections::HashMap, fmt};
2
3/// Error types for rule execution and configuration
4#[derive(Debug, Clone, PartialEq)]
5pub enum RuleError {
6    /// Rule context was not set when required
7    ContextNotSet,
8    /// Type mismatch when retrieving value from context
9    TypeMismatch {
10        key: &'static str,
11        expected: &'static str,
12    },
13    /// Too many children added to a rule that supports only one
14    TooManyChildren { max: usize, attempted: usize },
15    /// Rule execution failed
16    ExecutionFailed(String),
17    /// Borrow check failed at runtime
18    BorrowFailed(String),
19}
20
21impl fmt::Display for RuleError {
22    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23        match self {
24            RuleError::ContextNotSet => write!(f, "Rule context was not set"),
25            RuleError::TypeMismatch { key, expected } => {
26                write!(f, "Type mismatch for key '{}': expected {}", key, expected)
27            }
28            RuleError::TooManyChildren { max, attempted } => {
29                write!(
30                    f,
31                    "Too many children: max {} but attempted {}",
32                    max, attempted
33                )
34            }
35            RuleError::ExecutionFailed(msg) => write!(f, "Rule execution failed: {}", msg),
36            RuleError::BorrowFailed(msg) => write!(f, "Borrow check failed: {}", msg),
37        }
38    }
39}
40
41impl std::error::Error for RuleError {}
42
43/// Result type for rule operations
44pub type RuleResult<T> = Result<T, RuleError>;
45
46/// Type aliases for complex function types
47pub type EvalFn = Box<dyn Fn(&RuleContext) -> RuleResult<bool>>;
48pub type ExecuteFn = Box<dyn Fn(&mut RuleContext) -> RuleResult<()>>;
49
50pub use crate::engine::Engine;
51pub use crate::rule::best_first_rule::BestFirstRule;
52pub use crate::rule::chain_rule::ChainRule;
53pub use crate::runner::RuleRunner;
54
55pub(crate) mod best_first_rule;
56pub(crate) mod chain_rule;
57
58// Remove the wrapper types - we'll use direct ownership instead
59/// Context value that can hold any type safely
60#[derive(Debug)]
61pub enum ContextValue {
62    Bool(bool),
63    Int(i64),
64    Float(f64),
65    String(String),
66    Bytes(Vec<u8>),
67}
68
69impl ContextValue {
70    /// Try to extract a boolean value
71    pub fn as_bool(&self) -> RuleResult<bool> {
72        match self {
73            ContextValue::Bool(v) => Ok(*v),
74            _ => Err(RuleError::TypeMismatch {
75                key: "unknown",
76                expected: "bool",
77            }),
78        }
79    }
80
81    /// Try to extract an integer value
82    pub fn as_int(&self) -> RuleResult<i64> {
83        match self {
84            ContextValue::Int(v) => Ok(*v),
85            _ => Err(RuleError::TypeMismatch {
86                key: "unknown",
87                expected: "i64",
88            }),
89        }
90    }
91
92    /// Try to extract a float value
93    pub fn as_float(&self) -> RuleResult<f64> {
94        match self {
95            ContextValue::Float(v) => Ok(*v),
96            _ => Err(RuleError::TypeMismatch {
97                key: "unknown",
98                expected: "f64",
99            }),
100        }
101    }
102
103    /// Try to extract a string value
104    pub fn as_string(&self) -> RuleResult<&str> {
105        match self {
106            ContextValue::String(v) => Ok(v),
107            _ => Err(RuleError::TypeMismatch {
108                key: "unknown",
109                expected: "String",
110            }),
111        }
112    }
113
114    /// Try to extract bytes
115    pub fn as_bytes(&self) -> RuleResult<&[u8]> {
116        match self {
117            ContextValue::Bytes(v) => Ok(v),
118            _ => Err(RuleError::TypeMismatch {
119                key: "unknown",
120                expected: "Vec<u8>",
121            }),
122        }
123    }
124}
125
126/// RuleContext is a struct that holds the context of the rule.
127/// It provides type-safe access to values without using `dyn Any`.
128///
129/// Example:
130/// ```rust
131/// use dredd_rs::rule::*;
132///
133/// let mut rule_context = RuleContext::new();
134/// rule_context.set_bool("test", true);
135/// let test = rule_context.get_bool("test");
136/// ```
137#[derive(Debug, Default)]
138pub struct RuleContext {
139    context_map: HashMap<&'static str, ContextValue>,
140}
141
142impl RuleContext {
143    pub fn new() -> Self {
144        RuleContext {
145            context_map: HashMap::new(),
146        }
147    }
148
149    /// Set a boolean value in the context
150    pub fn set_bool(&mut self, key: &'static str, value: bool) {
151        self.context_map.insert(key, ContextValue::Bool(value));
152    }
153
154    /// Set an integer value in the context
155    pub fn set_int(&mut self, key: &'static str, value: i64) {
156        self.context_map.insert(key, ContextValue::Int(value));
157    }
158
159    /// Set a float value in the context
160    pub fn set_float(&mut self, key: &'static str, value: f64) {
161        self.context_map.insert(key, ContextValue::Float(value));
162    }
163
164    /// Set a string value in the context
165    pub fn set_string(&mut self, key: &'static str, value: String) {
166        self.context_map.insert(key, ContextValue::String(value));
167    }
168
169    /// Set bytes in the context
170    pub fn set_bytes(&mut self, key: &'static str, value: Vec<u8>) {
171        self.context_map.insert(key, ContextValue::Bytes(value));
172    }
173
174    /// Get a boolean value from the context
175    pub fn get_bool(&self, key: &'static str) -> RuleResult<bool> {
176        self.context_map
177            .get(key)
178            .ok_or(RuleError::TypeMismatch {
179                key,
180                expected: "bool",
181            })?
182            .as_bool()
183    }
184
185    /// Get an integer value from the context
186    pub fn get_int(&self, key: &'static str) -> RuleResult<i64> {
187        self.context_map
188            .get(key)
189            .ok_or(RuleError::TypeMismatch {
190                key,
191                expected: "i64",
192            })?
193            .as_int()
194    }
195
196    /// Get a float value from the context
197    pub fn get_float(&self, key: &'static str) -> RuleResult<f64> {
198        self.context_map
199            .get(key)
200            .ok_or(RuleError::TypeMismatch {
201                key,
202                expected: "f64",
203            })?
204            .as_float()
205    }
206
207    /// Get a string value from the context
208    pub fn get_string(&self, key: &'static str) -> RuleResult<&str> {
209        self.context_map
210            .get(key)
211            .ok_or(RuleError::TypeMismatch {
212                key,
213                expected: "String",
214            })?
215            .as_string()
216    }
217
218    /// Get bytes from the context
219    pub fn get_bytes(&self, key: &'static str) -> RuleResult<&[u8]> {
220        self.context_map
221            .get(key)
222            .ok_or(RuleError::TypeMismatch {
223                key,
224                expected: "Vec<u8>",
225            })?
226            .as_bytes()
227    }
228
229    /// Check if a key exists in the context
230    pub fn contains_key(&self, key: &'static str) -> bool {
231        self.context_map.contains_key(key)
232    }
233
234    /// Remove a value from the context
235    pub fn remove(&mut self, key: &'static str) -> Option<ContextValue> {
236        self.context_map.remove(key)
237    }
238
239    /// Clear all values from the context
240    pub fn clear(&mut self) {
241        self.context_map.clear();
242    }
243}
244
245/// Core trait for rule execution with proper error handling
246pub trait Rule {
247    /// Evaluate if this rule should execute
248    fn evaluate(&self, context: &RuleContext) -> RuleResult<bool>;
249
250    /// Execute the rule with proper error handling
251    fn execute(&mut self, context: &mut RuleContext) -> RuleResult<()>;
252
253    /// Get immutable reference to children
254    fn children(&self) -> &[Box<dyn Rule>];
255
256    /// Get mutable reference to children  
257    fn children_mut(&mut self) -> &mut Vec<Box<dyn Rule>>;
258
259    /// Add a child rule
260    fn add_child(&mut self, child: Box<dyn Rule>) -> RuleResult<()>;
261
262    /// Add multiple child rules
263    fn add_children(&mut self, children: Vec<Box<dyn Rule>>) -> RuleResult<()> {
264        for child in children {
265            self.add_child(child)?;
266        }
267        Ok(())
268    }
269
270    /// Execute the complete rule lifecycle: evaluate, execute, and run children
271    fn fire(&mut self, context: &mut RuleContext) -> RuleResult<bool> {
272        if self.evaluate(context)? {
273            self.execute(context)?;
274
275            // Execute children
276            for child in self.children_mut() {
277                child.fire(context)?;
278            }
279
280            Ok(true)
281        } else {
282            Ok(false)
283        }
284    }
285}
286
287/// Base implementation for rules with callback support
288pub struct BaseRule {
289    children: Vec<Box<dyn Rule>>,
290    eval_fn: Option<EvalFn>,
291    pre_execute_fn: Option<ExecuteFn>,
292    execute_fn: Option<ExecuteFn>,
293    post_execute_fn: Option<ExecuteFn>,
294}
295
296impl BaseRule {
297    pub fn new() -> Self {
298        BaseRule {
299            children: Vec::new(),
300            eval_fn: None,
301            pre_execute_fn: None,
302            execute_fn: None,
303            post_execute_fn: None,
304        }
305    }
306
307    /// Set the evaluation function
308    pub fn set_eval_fn<F>(&mut self, f: F)
309    where
310        F: Fn(&RuleContext) -> RuleResult<bool> + 'static,
311    {
312        self.eval_fn = Some(Box::new(f));
313    }
314
315    /// Set the pre-execution function
316    pub fn set_pre_execute_fn<F>(&mut self, f: F)
317    where
318        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
319    {
320        self.pre_execute_fn = Some(Box::new(f));
321    }
322
323    /// Set the execution function
324    pub fn set_execute_fn<F>(&mut self, f: F)
325    where
326        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
327    {
328        self.execute_fn = Some(Box::new(f));
329    }
330
331    /// Set the post-execution function
332    pub fn set_post_execute_fn<F>(&mut self, f: F)
333    where
334        F: Fn(&mut RuleContext) -> RuleResult<()> + 'static,
335    {
336        self.post_execute_fn = Some(Box::new(f));
337    }
338}
339
340impl Rule for BaseRule {
341    fn evaluate(&self, context: &RuleContext) -> RuleResult<bool> {
342        match &self.eval_fn {
343            Some(f) => f(context),
344            None => Ok(true), // Default: always evaluate to true
345        }
346    }
347
348    fn execute(&mut self, context: &mut RuleContext) -> RuleResult<()> {
349        // Pre-execute
350        if let Some(f) = &self.pre_execute_fn {
351            f(context)?;
352        }
353
354        // Main execute
355        if let Some(f) = &self.execute_fn {
356            f(context)?;
357        }
358
359        // Post-execute
360        if let Some(f) = &self.post_execute_fn {
361            f(context)?;
362        }
363
364        Ok(())
365    }
366
367    fn children(&self) -> &[Box<dyn Rule>] {
368        &self.children
369    }
370
371    fn children_mut(&mut self) -> &mut Vec<Box<dyn Rule>> {
372        &mut self.children
373    }
374
375    fn add_child(&mut self, child: Box<dyn Rule>) -> RuleResult<()> {
376        self.children.push(child);
377        Ok(())
378    }
379}
380
381impl Default for BaseRule {
382    fn default() -> Self {
383        Self::new()
384    }
385}