Skip to main content

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    /// Array membership check (value in array)
239    In,
240}
241
242impl Operator {
243    /// Parse operator from string representation
244    #[allow(clippy::should_implement_trait)]
245    pub fn from_str(s: &str) -> Option<Self> {
246        match s {
247            "==" | "eq" => Some(Operator::Equal),
248            "!=" | "ne" => Some(Operator::NotEqual),
249            ">" | "gt" => Some(Operator::GreaterThan),
250            ">=" | "gte" => Some(Operator::GreaterThanOrEqual),
251            "<" | "lt" => Some(Operator::LessThan),
252            "<=" | "lte" => Some(Operator::LessThanOrEqual),
253            "contains" => Some(Operator::Contains),
254            "not_contains" => Some(Operator::NotContains),
255            "starts_with" | "startsWith" => Some(Operator::StartsWith),
256            "ends_with" | "endsWith" => Some(Operator::EndsWith),
257            "matches" => Some(Operator::Matches),
258            "in" => Some(Operator::In),
259            _ => None,
260        }
261    }
262
263    /// Evaluate the operator against two values
264    pub fn evaluate(&self, left: &Value, right: &Value) -> bool {
265        match self {
266            Operator::Equal => {
267                // Special handling for null comparison
268                // "null" string should be treated as Value::Null
269                if matches!(left, Value::Null) || matches!(right, Value::Null) {
270                    // Convert "null" string to Value::Null for comparison
271                    let left_is_null = matches!(left, Value::Null)
272                        || (matches!(left, Value::String(s) if s == "null"));
273                    let right_is_null = matches!(right, Value::Null)
274                        || (matches!(right, Value::String(s) if s == "null"));
275
276                    left_is_null == right_is_null
277                } else {
278                    left == right
279                }
280            }
281            Operator::NotEqual => {
282                // Special handling for null comparison
283                if matches!(left, Value::Null) || matches!(right, Value::Null) {
284                    let left_is_null = matches!(left, Value::Null)
285                        || (matches!(left, Value::String(s) if s == "null"));
286                    let right_is_null = matches!(right, Value::Null)
287                        || (matches!(right, Value::String(s) if s == "null"));
288
289                    left_is_null != right_is_null
290                } else {
291                    left != right
292                }
293            }
294            Operator::GreaterThan => {
295                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
296                    l > r
297                } else {
298                    false
299                }
300            }
301            Operator::GreaterThanOrEqual => {
302                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
303                    l >= r
304                } else {
305                    false
306                }
307            }
308            Operator::LessThan => {
309                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
310                    l < r
311                } else {
312                    false
313                }
314            }
315            Operator::LessThanOrEqual => {
316                if let (Some(l), Some(r)) = (left.to_number(), right.to_number()) {
317                    l <= r
318                } else {
319                    false
320                }
321            }
322            Operator::Contains => {
323                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
324                    l.contains(&r)
325                } else {
326                    false
327                }
328            }
329            Operator::NotContains => {
330                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
331                    !l.contains(&r)
332                } else {
333                    false
334                }
335            }
336            Operator::StartsWith => {
337                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
338                    l.starts_with(&r)
339                } else {
340                    false
341                }
342            }
343            Operator::EndsWith => {
344                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
345                    l.ends_with(&r)
346                } else {
347                    false
348                }
349            }
350            Operator::Matches => {
351                // Simple regex match implementation
352                if let (Some(l), Some(r)) = (left.as_string(), right.as_string()) {
353                    // For now, just use contains as a simple match
354                    l.contains(&r)
355                } else {
356                    false
357                }
358            }
359            Operator::In => {
360                // Check if left value is in right array
361                match right {
362                    Value::Array(arr) => arr.contains(left),
363                    _ => false,
364                }
365            }
366        }
367    }
368}
369
370/// Logical operators for combining conditions
371#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
372pub enum LogicalOperator {
373    /// Logical AND
374    And,
375    /// Logical OR
376    Or,
377    /// Logical NOT
378    Not,
379}
380
381impl LogicalOperator {
382    /// Parse logical operator from string representation
383    #[allow(clippy::should_implement_trait)]
384    pub fn from_str(s: &str) -> Option<Self> {
385        match s.to_lowercase().as_str() {
386            "and" | "&&" => Some(LogicalOperator::And),
387            "or" | "||" => Some(LogicalOperator::Or),
388            "not" | "!" => Some(LogicalOperator::Not),
389            _ => None,
390        }
391    }
392}
393
394/// Represents the data context for rule evaluation
395pub type Context = HashMap<String, Value>;
396
397/// Action types that can be performed when a rule matches
398#[derive(Debug, Clone, PartialEq)]
399pub enum ActionType {
400    /// Set a field to a specific value
401    Set {
402        /// Field name to set
403        field: String,
404        /// Value to set
405        value: Value,
406    },
407    /// Log a message
408    Log {
409        /// Message to log
410        message: String,
411    },
412    /// Call a method on an object
413    MethodCall {
414        /// Object name
415        object: String,
416        /// Method name
417        method: String,
418        /// Method arguments
419        args: Vec<Value>,
420    },
421    /// Retract (delete) a fact from working memory
422    Retract {
423        /// Object/fact to retract
424        object: String,
425    },
426    /// Custom action
427    Custom {
428        /// Action type identifier
429        action_type: String,
430        /// Action parameters
431        params: HashMap<String, Value>,
432    },
433    /// Activate a specific agenda group for workflow progression
434    ActivateAgendaGroup {
435        /// Agenda group name to activate
436        group: String,
437    },
438    /// Schedule a rule to execute after a delay
439    ScheduleRule {
440        /// Rule name to schedule
441        rule_name: String,
442        /// Delay in milliseconds
443        delay_ms: u64,
444    },
445    /// Complete a workflow and trigger cleanup
446    CompleteWorkflow {
447        /// Workflow name to complete
448        workflow_name: String,
449    },
450    /// Set workflow-specific data
451    SetWorkflowData {
452        /// Data key
453        key: String,
454        /// Data value
455        value: Value,
456    },
457    /// Append a value to an array field
458    Append {
459        /// Field name (must be an array)
460        field: String,
461        /// Value to append
462        value: Value,
463    },
464}