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