use anyhow::{Context, Result};
use regex::Regex;
use std::collections::HashMap;
#[derive(Clone)]
pub struct ExpressionEvaluator {
env_vars: HashMap<String, String>,
flow_vars: HashMap<String, serde_yaml::Value>,
context_vars: HashMap<String, serde_yaml::Value>,
}
impl ExpressionEvaluator {
pub fn new() -> Self {
Self {
env_vars: HashMap::new(),
flow_vars: HashMap::new(),
context_vars: HashMap::new(),
}
}
pub fn set_env_vars(&mut self, env_vars: HashMap<String, String>) {
self.env_vars = env_vars;
}
pub fn set_flow_vars(
&mut self,
flow_vars: HashMap<String, serde_yaml::Value>,
) {
self.flow_vars = flow_vars;
}
pub fn set_context_var<S: AsRef<str>>(
&mut self,
key: S,
value: serde_yaml::Value,
) {
self.context_vars.insert(key.as_ref().to_string(), value);
}
pub fn get_context_var<S: AsRef<str>>(
&self,
key: S,
) -> Option<&serde_yaml::Value> {
self.context_vars.get(key.as_ref())
}
pub fn evaluate(&self, expression: &str) -> Result<serde_yaml::Value> {
let env_regex = Regex::new(r"\$\{\{\s*env\.(\w+)\s*\}\}")
.context("Failed to compile env regex")?;
let mut result = expression.to_string();
for cap in env_regex.captures_iter(expression) {
let var_name = &cap[1];
if let Some(env_value) = self.env_vars.get(var_name) {
result = result.replace(&cap[0], env_value);
} else {
return Err(anyhow::anyhow!(
"Environment variable not found: {}",
var_name
));
}
}
let vars_regex = Regex::new(r"\$\{\{\s*vars\.(\w+)\s*\}\}")
.context("Failed to compile vars regex")?;
for cap in vars_regex.captures_iter(&result.clone()) {
let var_name = &cap[1];
if let Some(var_value) = self.flow_vars.get(var_name) {
let value_str = self.yaml_value_to_string(var_value);
result = result.replace(&cap[0], &value_str);
} else {
return Err(anyhow::anyhow!(
"Flow variable not found: {}",
var_name
));
}
}
let output_regex = Regex::new(r"\$\{([^}]+)\}")
.context("Failed to compile output regex")?;
for cap in output_regex.captures_iter(&result.clone()) {
let path = &cap[1];
if let Some(value) = self.resolve_context_path(path)? {
let value_str = self.yaml_value_to_string(&value);
result = result.replace(&cap[0], &value_str);
} else {
return Err(anyhow::anyhow!(
"Context path not found: {}",
path
));
}
}
self.parse_result(&result)
}
pub fn evaluate_condition(&self, condition: &str) -> Result<bool> {
let result = self.evaluate(condition)?;
match result {
serde_yaml::Value::Bool(b) => Ok(b),
serde_yaml::Value::String(s) => {
if s.contains("==") {
self.evaluate_equality(&s)
} else if s.contains("!=") {
self.evaluate_inequality(&s)
} else if s.contains("&&") {
self.evaluate_and(&s)
} else if s.contains("||") {
self.evaluate_or(&s)
} else {
Ok(!s.is_empty())
}
}
serde_yaml::Value::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(i != 0)
} else if let Some(f) = n.as_f64() {
Ok(f != 0.0)
} else {
Ok(false)
}
}
serde_yaml::Value::Null => Ok(false),
_ => Ok(true),
}
}
fn resolve_context_path(
&self,
path: &str,
) -> Result<Option<serde_yaml::Value>> {
let parts: Vec<&str> = path.split('.').collect();
if parts.len() < 2 {
return Ok(None);
}
let key = parts.join(".");
if let Some(value) = self.context_vars.get(&key) {
return Ok(Some(value.clone()));
}
let mut current = None;
for (ctx_key, ctx_value) in &self.context_vars {
if ctx_key.starts_with(parts[0]) {
current = Some(ctx_value.clone());
break;
}
}
Ok(current)
}
fn yaml_value_to_string(&self, value: &serde_yaml::Value) -> String {
match value {
serde_yaml::Value::String(s) => s.clone(),
serde_yaml::Value::Number(n) => n.to_string(),
serde_yaml::Value::Bool(b) => b.to_string(),
serde_yaml::Value::Null => "null".to_string(),
_ => serde_yaml::to_string(value).unwrap_or_default(),
}
}
fn parse_result(&self, result: &str) -> Result<serde_yaml::Value> {
if let Ok(i) = result.parse::<i64>() {
return Ok(serde_yaml::Value::Number(serde_yaml::Number::from(i)));
}
if let Ok(f) = result.parse::<f64>() {
return Ok(serde_yaml::Value::Number(serde_yaml::Number::from(f)));
}
if let Ok(b) = result.parse::<bool>() {
return Ok(serde_yaml::Value::Bool(b));
}
Ok(serde_yaml::Value::String(result.to_string()))
}
fn evaluate_equality(&self, expr: &str) -> Result<bool> {
let parts: Vec<&str> = expr.splitn(2, "==").collect();
if parts.len() != 2 {
return Ok(false);
}
let left = parts[0].trim();
let right = parts[1].trim();
Ok(left == right)
}
fn evaluate_inequality(&self, expr: &str) -> Result<bool> {
let parts: Vec<&str> = expr.splitn(2, "!=").collect();
if parts.len() != 2 {
return Ok(false);
}
let left = parts[0].trim();
let right = parts[1].trim();
Ok(left != right)
}
fn evaluate_and(&self, expr: &str) -> Result<bool> {
let parts: Vec<&str> = expr.split("&&").collect();
for part in parts {
if !self.evaluate_condition(part.trim())? {
return Ok(false);
}
}
Ok(true)
}
fn evaluate_or(&self, expr: &str) -> Result<bool> {
let parts: Vec<&str> = expr.split("||").collect();
for part in parts {
if self.evaluate_condition(part.trim())? {
return Ok(true);
}
}
Ok(false)
}
}
impl Default for ExpressionEvaluator {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_env_var_evaluation() {
let mut evaluator = ExpressionEvaluator::new();
let mut env_vars = HashMap::new();
env_vars.insert("TEST_VAR".to_string(), "test_value".to_string());
evaluator.set_env_vars(env_vars);
let result = evaluator.evaluate("${{ env.TEST_VAR }}").unwrap();
assert_eq!(result, serde_yaml::Value::String("test_value".to_string()));
}
#[test]
fn test_flow_var_evaluation() {
let mut evaluator = ExpressionEvaluator::new();
let mut flow_vars = HashMap::new();
flow_vars.insert(
"name".to_string(),
serde_yaml::Value::String("FlowBuilder".to_string()),
);
evaluator.set_flow_vars(flow_vars);
let result = evaluator.evaluate("${{ vars.name }}").unwrap();
assert_eq!(
result,
serde_yaml::Value::String("FlowBuilder".to_string())
);
}
#[test]
fn test_condition_evaluation() {
let evaluator = ExpressionEvaluator::new();
assert!(evaluator.evaluate_condition("true").unwrap());
assert!(!evaluator.evaluate_condition("false").unwrap());
assert!(evaluator.evaluate_condition("test == test").unwrap());
assert!(!evaluator.evaluate_condition("test != test").unwrap());
}
}