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