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