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