mockforge_foundation/state_machine/
condition_evaluator.rs1use serde_json::Value;
7use std::collections::HashMap;
8use thiserror::Error;
9
10#[derive(Debug, Error)]
12pub enum ConditionError {
13 #[error("Expression syntax error: {0}")]
15 SyntaxError(String),
16
17 #[error("Evaluation error: {0}")]
19 EvaluationError(String),
20
21 #[error("Type error: {0}")]
23 TypeError(String),
24
25 #[error("Variable not found: {0}")]
27 VariableNotFound(String),
28}
29
30pub type ConditionResult<T> = Result<T, ConditionError>;
32
33pub struct ConditionEvaluator {
39 context: HashMap<String, Value>,
41}
42
43impl ConditionEvaluator {
44 pub fn new() -> Self {
46 Self {
47 context: HashMap::new(),
48 }
49 }
50
51 pub fn with_context(context: HashMap<String, Value>) -> Self {
53 Self { context }
54 }
55
56 pub fn set_variable(&mut self, name: impl Into<String>, value: Value) {
58 self.context.insert(name.into(), value);
59 }
60
61 pub fn get_variable(&self, name: &str) -> Option<&Value> {
63 self.context.get(name)
64 }
65
66 pub fn evaluate(&self, expression: &str) -> ConditionResult<bool> {
77 self.evaluate_simple(expression)
84 }
85
86 fn evaluate_simple(&self, expression: &str) -> ConditionResult<bool> {
91 let expr = expression.trim();
92
93 if expr == "true" {
95 return Ok(true);
96 }
97 if expr == "false" {
98 return Ok(false);
99 }
100
101 if let Some(result) = self.evaluate_comparison(expr)? {
103 return Ok(result);
104 }
105
106 if let Some(result) = self.evaluate_logical(expr)? {
108 return Ok(result);
109 }
110
111 if let Some(value) = self.get_variable_value(expr)? {
113 return self.value_to_bool(&value);
114 }
115
116 Err(ConditionError::SyntaxError(format!("Unable to evaluate expression: {}", expr)))
117 }
118
119 fn evaluate_comparison(&self, expr: &str) -> ConditionResult<Option<bool>> {
121 if let Some((left, right)) = expr.split_once("==") {
126 let left_val = self.evaluate_value(left.trim())?;
127 let right_val = self.evaluate_value(right.trim())?;
128 return Ok(Some(left_val == right_val));
129 }
130
131 if let Some((left, right)) = expr.split_once("!=") {
133 let left_val = self.evaluate_value(left.trim())?;
134 let right_val = self.evaluate_value(right.trim())?;
135 return Ok(Some(left_val != right_val));
136 }
137
138 for op in [">=", "<=", ">", "<"] {
140 if let Some((left, right)) = expr.split_once(op) {
141 let left_val = self.evaluate_value(left.trim())?;
142 let right_val = self.evaluate_value(right.trim())?;
143
144 if let (Some(a), Some(b)) = (
146 left_val.as_f64().or_else(|| left_val.as_i64().map(|i| i as f64)),
147 right_val.as_f64().or_else(|| right_val.as_i64().map(|i| i as f64)),
148 ) {
149 let result = match op {
150 ">=" => a >= b,
151 "<=" => a <= b,
152 ">" => a > b,
153 "<" => a < b,
154 _ => false,
155 };
156 return Ok(Some(result));
157 }
158 }
159 }
160
161 Ok(None)
162 }
163
164 fn evaluate_logical(&self, expr: &str) -> ConditionResult<Option<bool>> {
166 if let Some(stripped) = expr.strip_prefix('!') {
168 let inner = stripped.trim();
169 let inner_result = self.evaluate(inner)?;
170 return Ok(Some(!inner_result));
171 }
172
173 if let Some((left, right)) = expr.split_once("&&") {
175 let left_result = self.evaluate(left.trim())?;
176 if !left_result {
177 return Ok(Some(false));
178 }
179 return Ok(Some(self.evaluate(right.trim())?));
180 }
181
182 if let Some((left, right)) = expr.split_once("||") {
184 let left_result = self.evaluate(left.trim())?;
185 if left_result {
186 return Ok(Some(true));
187 }
188 return Ok(Some(self.evaluate(right.trim())?));
189 }
190
191 Ok(None)
192 }
193
194 fn evaluate_value(&self, expr: &str) -> ConditionResult<Value> {
196 if let Some(value) = self.get_variable_value(expr)? {
198 return Ok(value.clone());
199 }
200
201 if let Ok(value) = serde_json::from_str::<Value>(expr) {
203 return Ok(value);
204 }
205
206 if let Ok(num) = expr.parse::<f64>() {
208 return Ok(Value::Number(
209 serde_json::Number::from_f64(num).unwrap_or_else(|| serde_json::Number::from(0)),
210 ));
211 }
212
213 if expr == "true" {
215 return Ok(Value::Bool(true));
216 }
217 if expr == "false" {
218 return Ok(Value::Bool(false));
219 }
220
221 Ok(Value::String(expr.to_string()))
223 }
224
225 fn get_variable_value(&self, path: &str) -> ConditionResult<Option<Value>> {
227 let parts: Vec<&str> = path.split('.').collect();
228
229 if parts.is_empty() {
230 return Ok(None);
231 }
232
233 let root = self.context.get(parts[0]);
235 if root.is_none() {
236 return Ok(None);
237 }
238
239 let mut value = root.unwrap().clone();
240
241 for part in parts.iter().skip(1) {
243 match value {
244 Value::Object(ref obj) => {
245 value = obj
246 .get(*part)
247 .ok_or_else(|| {
248 ConditionError::VariableNotFound(format!("{}.{}", parts[0], part))
249 })?
250 .clone();
251 }
252 _ => {
253 return Err(ConditionError::TypeError(format!(
254 "Cannot access property '{}' on non-object",
255 part
256 )));
257 }
258 }
259 }
260
261 Ok(Some(value))
262 }
263
264 fn value_to_bool(&self, value: &Value) -> ConditionResult<bool> {
266 match value {
267 Value::Bool(b) => Ok(*b),
268 Value::Number(n) => Ok(n.as_f64().unwrap_or(0.0) != 0.0),
269 Value::String(s) => Ok(!s.is_empty()),
270 Value::Array(arr) => Ok(!arr.is_empty()),
271 Value::Object(obj) => Ok(!obj.is_empty()),
272 Value::Null => Ok(false),
273 }
274 }
275}
276
277impl Default for ConditionEvaluator {
278 fn default() -> Self {
279 Self::new()
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn test_simple_boolean() {
289 let evaluator = ConditionEvaluator::new();
290 assert!(evaluator.evaluate("true").unwrap());
291 assert!(!evaluator.evaluate("false").unwrap());
292 }
293
294 #[test]
295 fn test_comparison_operators() {
296 let evaluator = ConditionEvaluator::new();
297 assert!(evaluator.evaluate("5 > 3").unwrap());
298 assert!(evaluator.evaluate("3 < 5").unwrap());
299 assert!(evaluator.evaluate("5 == 5").unwrap());
300 assert!(evaluator.evaluate("5 != 3").unwrap());
301 }
302
303 #[test]
304 fn test_variable_access() {
305 let mut context = HashMap::new();
306 context.insert("status".to_string(), Value::String("active".to_string()));
307 context.insert("count".to_string(), Value::Number(5.into()));
308
309 let evaluator = ConditionEvaluator::with_context(context);
310 assert!(evaluator.evaluate("count > 3").unwrap());
311 }
312
313 #[test]
314 fn test_logical_operators() {
315 let evaluator = ConditionEvaluator::new();
316 assert!(evaluator.evaluate("true && true").unwrap());
317 assert!(!evaluator.evaluate("true && false").unwrap());
318 assert!(evaluator.evaluate("true || false").unwrap());
319 assert!(!evaluator.evaluate("!true").unwrap());
320 }
321}