Skip to main content

bpm_engine_runtime/
el.rs

1//! EL (Expression Language) evaluator for gateway conditions.
2
3use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
6pub struct ElError(pub String);
7
8pub fn eval_condition(expr: &str, variables: &HashMap<String, String>) -> Result<bool, ElError> {
9    let expr = expr.trim();
10    if expr.is_empty() {
11        return Err(ElError("empty expression".into()));
12    }
13    if expr.contains(" or ") {
14        let parts: Vec<&str> = expr.split(" or ").map(str::trim).collect();
15        for part in parts {
16            if eval_condition(part, variables)? {
17                return Ok(true);
18            }
19        }
20        return Ok(false);
21    }
22    if expr.contains(" and ") {
23        let parts: Vec<&str> = expr.split(" and ").map(str::trim).collect();
24        for part in parts {
25            if !eval_condition(part, variables)? {
26                return Ok(false);
27            }
28        }
29        return Ok(true);
30    }
31    if !expr.contains(' ') {
32        let val = variables.get(expr).map(|s| s.as_str()).unwrap_or("");
33        return Ok(!val.is_empty());
34    }
35    let ops = [" == ", " != ", " >= ", " <= ", " > ", " < "];
36    for op in ops {
37        if let Some(pos) = expr.find(op) {
38            let left = expr[..pos].trim();
39            let right = expr[pos + op.len()..].trim();
40            if left.is_empty() {
41                return Err(ElError("missing left operand".into()));
42            }
43            let left_val = variables.get(left).map(|s| s.as_str()).unwrap_or("");
44            let right_trim = op.trim();
45            return match right_trim {
46                "==" => eval_eq(left_val, right, variables),
47                "!=" => eval_neq(left_val, right, variables),
48                ">" => eval_cmp(left_val, right, variables, |a, b| a > b),
49                ">=" => eval_cmp(left_val, right, variables, |a, b| a >= b),
50                "<" => eval_cmp(left_val, right, variables, |a, b| a < b),
51                "<=" => eval_cmp(left_val, right, variables, |a, b| a <= b),
52                _ => Err(ElError(format!("unknown operator: {}", right_trim))),
53            };
54        }
55    }
56    Err(ElError(format!("unrecognized expression: {}", expr)))
57}
58
59fn unquote(s: &str) -> Option<&str> {
60    let s = s.trim();
61    if (s.starts_with('"') && s.ends_with('"')) || (s.starts_with('\'') && s.ends_with('\'')) {
62        Some(s[1..s.len() - 1].trim())
63    } else {
64        None
65    }
66}
67
68fn eval_eq(
69    left_val: &str,
70    right: &str,
71    variables: &HashMap<String, String>,
72) -> Result<bool, ElError> {
73    let right_val = if let Some(q) = unquote(right) {
74        q.to_string()
75    } else {
76        variables
77            .get(right.trim())
78            .cloned()
79            .unwrap_or_else(|| right.trim().to_string())
80    };
81    Ok(left_val == right_val.as_str())
82}
83
84fn eval_neq(
85    left_val: &str,
86    right: &str,
87    variables: &HashMap<String, String>,
88) -> Result<bool, ElError> {
89    let right_val = if let Some(q) = unquote(right) {
90        q.to_string()
91    } else {
92        variables
93            .get(right.trim())
94            .cloned()
95            .unwrap_or_else(|| right.trim().to_string())
96    };
97    Ok(left_val != right_val.as_str())
98}
99
100fn parse_f64(s: &str, variables: &HashMap<String, String>) -> Result<f64, ElError> {
101    let s = s.trim();
102    if let Some(q) = unquote(s) {
103        q.parse::<f64>()
104            .map_err(|_| ElError(format!("not a number: {}", q)))
105    } else if let Some(v) = variables.get(s) {
106        v.trim()
107            .parse::<f64>()
108            .map_err(|_| ElError(format!("variable {} is not a number: {:?}", s, v)))
109    } else {
110        s.parse::<f64>()
111            .map_err(|_| ElError(format!("not a number: {}", s)))
112    }
113}
114
115fn eval_cmp<F>(
116    left_val: &str,
117    right: &str,
118    variables: &HashMap<String, String>,
119    op: F,
120) -> Result<bool, ElError>
121where
122    F: FnOnce(f64, f64) -> bool,
123{
124    let a = parse_f64(left_val, variables).or_else(|_| {
125        left_val
126            .trim()
127            .parse::<f64>()
128            .map_err(|_| ElError(format!("left operand not numeric: {:?}", left_val)))
129    })?;
130    let b = parse_f64(right, variables)?;
131    Ok(op(a, b))
132}