rust_rule_engine/
types.rs

1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4/// Represents a value that can be used in rule conditions and actions
5#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
6pub enum Value {
7    /// String value
8    String(String),
9    /// Floating point number
10    Number(f64),
11    /// Integer value
12    Integer(i64),
13    /// Boolean value
14    Boolean(bool),
15    /// Array of values
16    Array(Vec<Value>),
17    /// Object with key-value pairs
18    Object(HashMap<String, Value>),
19    /// Null value
20    Null,
21    /// Expression to be evaluated at runtime (e.g., "Order.quantity * Order.price")
22    Expression(String),
23}
24
25impl Value {
26    /// Convert Value to string representation
27    #[allow(clippy::inherent_to_string)]
28    pub fn to_string(&self) -> String {
29        match self {
30            Value::String(s) => s.clone(),
31            Value::Number(n) => n.to_string(),
32            Value::Integer(i) => i.to_string(),
33            Value::Boolean(b) => b.to_string(),
34            Value::Array(_) => "[Array]".to_string(),
35            Value::Object(_) => "[Object]".to_string(),
36            Value::Null => "null".to_string(),
37            Value::Expression(expr) => format!("[Expr: {}]", expr),
38        }
39    }
40
41    /// Convert Value to number if possible
42    pub fn to_number(&self) -> Option<f64> {
43        match self {
44            Value::Number(n) => Some(*n),
45            Value::Integer(i) => Some(*i as f64),
46            Value::String(s) => s.parse::<f64>().ok(),
47            _ => None,
48        }
49    }
50
51    /// Get string value if this is a string
52    pub fn as_string(&self) -> Option<String> {
53        match self {
54            Value::String(s) => Some(s.clone()),
55            _ => None,
56        }
57    }
58
59    /// Get integer value if this is an integer
60    pub fn as_integer(&self) -> Option<i64> {
61        match self {
62            Value::Integer(i) => Some(*i),
63            _ => None,
64        }
65    }
66
67    /// Get boolean value if this is a boolean
68    pub fn as_boolean(&self) -> Option<bool> {
69        match self {
70            Value::Boolean(b) => Some(*b),
71            _ => None,
72        }
73    }
74
75    /// Get number value if this is a number
76    pub fn as_number(&self) -> Option<f64> {
77        match self {
78            Value::Number(n) => Some(*n),
79            _ => None,
80        }
81    }
82
83    /// Convert Value to boolean
84    pub fn to_bool(&self) -> bool {
85        match self {
86            Value::Boolean(b) => *b,
87            Value::String(s) => !s.is_empty(),
88            Value::Number(n) => *n != 0.0,
89            Value::Integer(i) => *i != 0,
90            Value::Array(arr) => !arr.is_empty(),
91            Value::Object(obj) => !obj.is_empty(),
92            Value::Null => false,
93            Value::Expression(_) => false, // Expression needs to be evaluated first
94        }
95    }
96
97    /// Call a method on this value with given arguments
98    pub fn call_method(&mut self, method: &str, args: Vec<Value>) -> Result<Value, String> {
99        match self {
100            Value::Object(ref mut obj) => match method {
101                "setSpeed" => {
102                    if let Some(Value::Number(speed)) = args.first() {
103                        obj.insert("Speed".to_string(), Value::Number(*speed));
104                        Ok(Value::Null)
105                    } else if let Some(Value::Integer(speed)) = args.first() {
106                        obj.insert("Speed".to_string(), Value::Integer(*speed));
107                        Ok(Value::Null)
108                    } else {
109                        Err("setSpeed requires a number argument".to_string())
110                    }
111                }
112                "setTotalDistance" => {
113                    if let Some(Value::Number(distance)) = args.first() {
114                        obj.insert("TotalDistance".to_string(), Value::Number(*distance));
115                        Ok(Value::Null)
116                    } else if let Some(Value::Integer(distance)) = args.first() {
117                        obj.insert("TotalDistance".to_string(), Value::Integer(*distance));
118                        Ok(Value::Null)
119                    } else {
120                        Err("setTotalDistance requires a number argument".to_string())
121                    }
122                }
123                "getTotalDistance" => Ok(obj
124                    .get("TotalDistance")
125                    .cloned()
126                    .unwrap_or(Value::Number(0.0))),
127                "getSpeed" => Ok(obj.get("Speed").cloned().unwrap_or(Value::Number(0.0))),
128                _ => Err(format!("Method {} not found", method)),
129            },
130            _ => Err("Cannot call method on non-object value".to_string()),
131        }
132    }
133
134    /// Get a property from this object
135    pub fn get_property(&self, property: &str) -> Option<Value> {
136        match self {
137            Value::Object(obj) => obj.get(property).cloned(),
138            _ => None,
139        }
140    }
141
142    /// Set a property on this object
143    pub fn set_property(&mut self, property: &str, value: Value) -> Result<(), String> {
144        match self {
145            Value::Object(ref mut obj) => {
146                obj.insert(property.to_string(), value);
147                Ok(())
148            }
149            _ => Err("Cannot set property on non-object value".to_string()),
150        }
151    }
152}
153
154impl From<String> for Value {
155    fn from(s: String) -> Self {
156        Value::String(s)
157    }
158}
159
160impl From<&str> for Value {
161    fn from(s: &str) -> Self {
162        Value::String(s.to_string())
163    }
164}
165
166impl From<f64> for Value {
167    fn from(n: f64) -> Self {
168        Value::Number(n)
169    }
170}
171
172impl From<i64> for Value {
173    fn from(i: i64) -> Self {
174        Value::Integer(i)
175    }
176}
177
178impl From<bool> for Value {
179    fn from(b: bool) -> Self {
180        Value::Boolean(b)
181    }
182}
183
184impl From<serde_json::Value> for Value {
185    fn from(json_value: serde_json::Value) -> Self {
186        match json_value {
187            serde_json::Value::String(s) => Value::String(s),
188            serde_json::Value::Number(n) => {
189                if let Some(i) = n.as_i64() {
190                    Value::Integer(i)
191                } else if let Some(f) = n.as_f64() {
192                    Value::Number(f)
193                } else {
194                    Value::Null
195                }
196            }
197            serde_json::Value::Bool(b) => Value::Boolean(b),
198            serde_json::Value::Array(arr) => {
199                Value::Array(arr.into_iter().map(Value::from).collect())
200            }
201            serde_json::Value::Object(obj) => {
202                let mut map = HashMap::new();
203                for (k, v) in obj {
204                    map.insert(k, Value::from(v));
205                }
206                Value::Object(map)
207            }
208            serde_json::Value::Null => Value::Null,
209        }
210    }
211}
212
213/// Comparison operators for rule conditions
214#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
215pub enum Operator {
216    /// Equality comparison
217    Equal,
218    /// Inequality comparison
219    NotEqual,
220    /// Greater than comparison
221    GreaterThan,
222    /// Greater than or equal comparison
223    GreaterThanOrEqual,
224    /// Less than comparison
225    LessThan,
226    /// Less than or equal comparison
227    LessThanOrEqual,
228    /// String contains check
229    Contains,
230    /// String does not contain check
231    NotContains,
232    /// String starts with check
233    StartsWith,
234    /// String ends with check
235    EndsWith,
236    /// Regex pattern match
237    Matches,
238}
239
240impl Operator {
241    /// Parse operator from string representation
242    #[allow(clippy::should_implement_trait)]
243    pub fn from_str(s: &str) -> Option<Self> {
244        match s {
245            "==" | "eq" => Some(Operator::Equal),
246            "!=" | "ne" => Some(Operator::NotEqual),
247            ">" | "gt" => Some(Operator::GreaterThan),
248            ">=" | "gte" => Some(Operator::GreaterThanOrEqual),
249            "<" | "lt" => Some(Operator::LessThan),
250            "<=" | "lte" => Some(Operator::LessThanOrEqual),
251            "contains" => Some(Operator::Contains),
252            "not_contains" => Some(Operator::NotContains),
253            "starts_with" => Some(Operator::StartsWith),
254            "ends_with" => Some(Operator::EndsWith),
255            "matches" => Some(Operator::Matches),
256            _ => None,
257        }
258    }
259
260    /// Evaluate the operator against two values
261    pub fn evaluate(&self, left: &Value, right: &Value) -> bool {
262        match self {
263            Operator::Equal => left == right,
264            Operator::NotEqual => left != right,
265            Operator::GreaterThan => {
266                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
267                    l > r
268                } else {
269                    false
270                }
271            }
272            Operator::GreaterThanOrEqual => {
273                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
274                    l >= r
275                } else {
276                    false
277                }
278            }
279            Operator::LessThan => {
280                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
281                    l < r
282                } else {
283                    false
284                }
285            }
286            Operator::LessThanOrEqual => {
287                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
288                    l <= r
289                } else {
290                    false
291                }
292            }
293            Operator::Contains => {
294                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
295                    l.contains(&r)
296                } else {
297                    false
298                }
299            }
300            Operator::NotContains => {
301                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
302                    !l.contains(&r)
303                } else {
304                    false
305                }
306            }
307            Operator::StartsWith => {
308                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
309                    l.starts_with(&r)
310                } else {
311                    false
312                }
313            }
314            Operator::EndsWith => {
315                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
316                    l.ends_with(&r)
317                } else {
318                    false
319                }
320            }
321            Operator::Matches => {
322                // Simple regex match implementation
323                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
324                    // For now, just use contains as a simple match
325                    l.contains(&r)
326                } else {
327                    false
328                }
329            }
330        }
331    }
332}
333
334/// Logical operators for combining conditions
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
336pub enum LogicalOperator {
337    /// Logical AND
338    And,
339    /// Logical OR
340    Or,
341    /// Logical NOT
342    Not,
343}
344
345impl LogicalOperator {
346    /// Parse logical operator from string representation
347    #[allow(clippy::should_implement_trait)]
348    pub fn from_str(s: &str) -> Option<Self> {
349        match s.to_lowercase().as_str() {
350            "and" | "&&" => Some(LogicalOperator::And),
351            "or" | "||" => Some(LogicalOperator::Or),
352            "not" | "!" => Some(LogicalOperator::Not),
353            _ => None,
354        }
355    }
356}
357
358/// Represents the data context for rule evaluation
359pub type Context = HashMap<String, Value>;
360
361/// Action types that can be performed when a rule matches
362#[derive(Debug, Clone, PartialEq)]
363pub enum ActionType {
364    /// Set a field to a specific value
365    Set {
366        /// Field name to set
367        field: String,
368        /// Value to set
369        value: Value,
370    },
371    /// Log a message
372    Log {
373        /// Message to log
374        message: String,
375    },
376    /// Call a function
377    Call {
378        /// Function name to call
379        function: String,
380        /// Arguments to pass
381        args: Vec<Value>,
382    },
383    /// Call a method on an object
384    MethodCall {
385        /// Object name
386        object: String,
387        /// Method name
388        method: String,
389        /// Method arguments
390        args: Vec<Value>,
391    },
392    /// Update an object
393    Update {
394        /// Object name to update
395        object: String,
396    },
397    /// Retract (delete) a fact from working memory
398    Retract {
399        /// Object/fact to retract
400        object: String,
401    },
402    /// Custom action
403    Custom {
404        /// Action type identifier
405        action_type: String,
406        /// Action parameters
407        params: HashMap<String, Value>,
408    },
409    /// Activate a specific agenda group for workflow progression
410    ActivateAgendaGroup {
411        /// Agenda group name to activate
412        group: String,
413    },
414    /// Schedule a rule to execute after a delay
415    ScheduleRule {
416        /// Rule name to schedule
417        rule_name: String,
418        /// Delay in milliseconds
419        delay_ms: u64,
420    },
421    /// Complete a workflow and trigger cleanup
422    CompleteWorkflow {
423        /// Workflow name to complete
424        workflow_name: String,
425    },
426    /// Set workflow-specific data
427    SetWorkflowData {
428        /// Data key
429        key: String,
430        /// Data value
431        value: Value,
432    },
433}