Files
erp/crates/erp-workflow/src/engine/expression.rs
iven 9568dd7875 chore: apply cargo fmt across workspace and update docs
- Run cargo fmt on all Rust crates for consistent formatting
- Update CLAUDE.md with WASM plugin commands and dev.ps1 instructions
- Update wiki: add WASM plugin architecture, rewrite dev environment docs
- Minor frontend cleanup (unused imports)
2026-04-15 00:49:20 +08:00

332 lines
11 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use std::collections::HashMap;
use crate::error::{WorkflowError, WorkflowResult};
/// 简单表达式求值器。
///
/// 支持的比较运算符:>, >=, <, <=, ==, !=
/// 支持 && 和 || 逻辑运算。
/// 操作数可以是变量名(从 variables map 查找)或字面量(数字、字符串)。
///
/// 示例:
/// - `amount > 1000`
/// - `status == "approved"`
/// - `score >= 60 && attendance > 80`
pub struct ExpressionEvaluator;
impl ExpressionEvaluator {
/// 求值单个条件表达式。
///
/// 表达式格式: `{left} {op} {right}` 或复合表达式 `{expr1} && {expr2}`
pub fn eval(
expr: &str,
variables: &HashMap<String, serde_json::Value>,
) -> WorkflowResult<bool> {
let expr = expr.trim();
// 处理逻辑 OR
if let Some(idx) = Self::find_logical_op(expr, "||") {
let left = &expr[..idx];
let right = &expr[idx + 2..];
return Ok(Self::eval(left, variables)? || Self::eval(right, variables)?);
}
// 处理逻辑 AND
if let Some(idx) = Self::find_logical_op(expr, "&&") {
let left = &expr[..idx];
let right = &expr[idx + 2..];
return Ok(Self::eval(left, variables)? && Self::eval(right, variables)?);
}
// 处理单个比较表达式
Self::eval_comparison(expr, variables)
}
/// 查找逻辑运算符位置,跳过引号内的内容。
fn find_logical_op(expr: &str, op: &str) -> Option<usize> {
let mut in_string = false;
let mut string_char = ' ';
let chars: Vec<char> = expr.chars().collect();
let op_chars: Vec<char> = op.chars().collect();
let op_len = op_chars.len();
for i in 0..chars.len().saturating_sub(op_len - 1) {
let c = chars[i];
if !in_string && (c == '"' || c == '\'') {
in_string = true;
string_char = c;
continue;
}
if in_string && c == string_char {
in_string = false;
continue;
}
if in_string {
continue;
}
if chars[i..].starts_with(&op_chars) {
return Some(i);
}
}
None
}
/// 求值单个比较表达式。
fn eval_comparison(
expr: &str,
variables: &HashMap<String, serde_json::Value>,
) -> WorkflowResult<bool> {
let operators = [">=", "<=", "!=", "==", ">", "<"];
for op in &operators {
if let Some(idx) = Self::find_comparison_op(expr, op) {
let left = expr[..idx].trim();
let right = expr[idx + op.len()..].trim();
let left_val = Self::resolve_value(left, variables)?;
let right_val = Self::resolve_value(right, variables)?;
return Self::compare(&left_val, &right_val, op);
}
}
Err(WorkflowError::ExpressionError(format!(
"无法解析表达式: '{}'",
expr
)))
}
/// 查找比较运算符位置,跳过引号内的内容。
fn find_comparison_op(expr: &str, op: &str) -> Option<usize> {
let mut in_string = false;
let mut string_char = ' ';
let bytes = expr.as_bytes();
let op_bytes = op.as_bytes();
let op_len = op_bytes.len();
for i in 0..bytes.len().saturating_sub(op_len - 1) {
let c = bytes[i] as char;
if !in_string && (c == '"' || c == '\'') {
in_string = true;
string_char = c;
continue;
}
if in_string && c == string_char {
in_string = false;
continue;
}
if in_string {
continue;
}
if bytes[i..].starts_with(op_bytes) {
// 确保不是被嵌在其他运算符里(如 != 中的 =
// 对于 > 和 < 检查后面不是 = 或 >
if op == ">" || op == "<" {
if i + op_len < bytes.len() {
let next = bytes[i + op_len] as char;
if next == '=' || (op == ">" && next == '>') {
continue;
}
}
// 也检查前面不是 ! 或 = 或 < 或 >
if i > 0 {
let prev = bytes[i - 1] as char;
if prev == '!' || prev == '=' || prev == '<' || prev == '>' {
continue;
}
}
}
// 对于 ==, >=, <=, != 确保前面不是 ! 或 = (避免匹配到 == 中的第二个 =)
// 这已经通过从长到短匹配处理了
return Some(i);
}
}
None
}
/// 解析值:字符串字面量、数字字面量或变量引用。
fn resolve_value(
token: &str,
variables: &HashMap<String, serde_json::Value>,
) -> WorkflowResult<serde_json::Value> {
let token = token.trim();
// 字符串字面量
if (token.starts_with('"') && token.ends_with('"'))
|| (token.starts_with('\'') && token.ends_with('\''))
{
return Ok(serde_json::Value::String(
token[1..token.len() - 1].to_string(),
));
}
// 数字字面量
if let Ok(n) = token.parse::<i64>() {
return Ok(serde_json::Value::Number(n.into()));
}
if let Ok(f) = token.parse::<f64>()
&& let Some(n) = serde_json::Number::from_f64(f)
{
return Ok(serde_json::Value::Number(n));
}
// 布尔字面量
if token == "true" {
return Ok(serde_json::Value::Bool(true));
}
if token == "false" {
return Ok(serde_json::Value::Bool(false));
}
// 变量引用
if let Some(val) = variables.get(token) {
return Ok(val.clone());
}
Err(WorkflowError::ExpressionError(format!(
"未知的变量或值: '{}'",
token
)))
}
/// 比较两个 JSON 值。
fn compare(
left: &serde_json::Value,
right: &serde_json::Value,
op: &str,
) -> WorkflowResult<bool> {
match op {
"==" => Ok(Self::values_equal(left, right)),
"!=" => Ok(!Self::values_equal(left, right)),
">" => Ok(Self::values_compare(left, right)? == std::cmp::Ordering::Greater),
">=" => Ok(Self::values_compare(left, right)? != std::cmp::Ordering::Less),
"<" => Ok(Self::values_compare(left, right)? == std::cmp::Ordering::Less),
"<=" => Ok(Self::values_compare(left, right)? != std::cmp::Ordering::Greater),
_ => Err(WorkflowError::ExpressionError(format!(
"不支持的比较运算符: '{}'",
op
))),
}
}
fn values_equal(left: &serde_json::Value, right: &serde_json::Value) -> bool {
// 数值比较:允许整数和浮点数互比
if left.is_number() && right.is_number() {
return left.as_f64() == right.as_f64();
}
left == right
}
fn values_compare(
left: &serde_json::Value,
right: &serde_json::Value,
) -> WorkflowResult<std::cmp::Ordering> {
if left.is_number() && right.is_number() {
let l = left.as_f64().unwrap_or(0.0);
let r = right.as_f64().unwrap_or(0.0);
return Ok(l.partial_cmp(&r).unwrap_or(std::cmp::Ordering::Equal));
}
if let (Some(l), Some(r)) = (left.as_str(), right.as_str()) {
return Ok(l.cmp(r));
}
Err(WorkflowError::ExpressionError(format!(
"无法比较 {:?}{:?}",
left, right
)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
fn make_vars() -> HashMap<String, serde_json::Value> {
let mut m = HashMap::new();
m.insert("amount".to_string(), json!(1500));
m.insert("status".to_string(), json!("approved"));
m.insert("score".to_string(), json!(85));
m.insert("name".to_string(), json!("Alice"));
m.insert("active".to_string(), json!(true));
m
}
#[test]
fn test_number_greater_than() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("amount > 1000", &vars).unwrap());
assert!(!ExpressionEvaluator::eval("amount > 2000", &vars).unwrap());
}
#[test]
fn test_number_less_than() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("amount < 2000", &vars).unwrap());
assert!(!ExpressionEvaluator::eval("amount < 1000", &vars).unwrap());
}
#[test]
fn test_number_equals() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("amount == 1500", &vars).unwrap());
assert!(!ExpressionEvaluator::eval("amount == 1000", &vars).unwrap());
}
#[test]
fn test_string_equals() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("status == \"approved\"", &vars).unwrap());
assert!(!ExpressionEvaluator::eval("status == \"rejected\"", &vars).unwrap());
}
#[test]
fn test_string_not_equals() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("status != \"rejected\"", &vars).unwrap());
}
#[test]
fn test_greater_or_equal() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("amount >= 1500", &vars).unwrap());
assert!(ExpressionEvaluator::eval("amount >= 1000", &vars).unwrap());
assert!(!ExpressionEvaluator::eval("amount >= 2000", &vars).unwrap());
}
#[test]
fn test_logical_and() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("amount > 1000 && score > 80", &vars).unwrap());
assert!(!ExpressionEvaluator::eval("amount > 2000 && score > 80", &vars).unwrap());
}
#[test]
fn test_logical_or() {
let vars = make_vars();
assert!(ExpressionEvaluator::eval("amount > 2000 || score > 80", &vars).unwrap());
assert!(!ExpressionEvaluator::eval("amount > 2000 || score > 90", &vars).unwrap());
}
#[test]
fn test_unknown_variable() {
let vars = make_vars();
let result = ExpressionEvaluator::eval("unknown > 0", &vars);
assert!(result.is_err());
}
#[test]
fn test_invalid_expression() {
let vars = make_vars();
let result = ExpressionEvaluator::eval("justavariable", &vars);
assert!(result.is_err());
}
}