use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum VariableScope {
Global,
Step(String),
Loop { step: String, iteration: usize },
}
pub struct VariableResolver {
global: HashMap<String, serde_json::Value>,
step_scoped: HashMap<String, HashMap<String, serde_json::Value>>,
}
impl VariableResolver {
pub fn new(global: HashMap<String, serde_json::Value>) -> Self {
Self {
global,
step_scoped: HashMap::new(),
}
}
pub fn set_global(&mut self, name: String, value: serde_json::Value) {
self.global.insert(name, value);
}
pub fn set_step_variable(&mut self, step: String, name: String, value: serde_json::Value) {
self.step_scoped
.entry(step)
.or_default()
.insert(name, value);
}
pub fn get(&self, name: &str, current_step: Option<&str>) -> Option<serde_json::Value> {
if let Some(step_name) = current_step {
if let Some(step_vars) = self.step_scoped.get(step_name) {
if let Some(value) = step_vars.get(name) {
return Some(value.clone());
}
}
}
self.global.get(name).cloned()
}
pub fn resolve_string(&self, input: &str, current_step: Option<&str>) -> String {
let mut result = String::new();
let mut remaining = input;
while let Some(start) = remaining.find("${") {
result.push_str(&remaining[..start]);
remaining = &remaining[start..];
if let Some(end) = remaining.find('}') {
let var_name = &remaining[2..end];
match self.get(var_name, current_step).and_then(|v| match v {
serde_json::Value::String(s) => Some(s),
serde_json::Value::Number(n) => Some(n.to_string()),
serde_json::Value::Bool(b) => Some(b.to_string()),
_ => None,
}) {
Some(replacement) => {
result.push_str(&replacement);
}
None => {
result.push_str(&remaining[..end + 1]);
}
}
remaining = &remaining[end + 1..];
} else {
result.push_str(remaining);
return result;
}
}
result.push_str(remaining);
result
}
pub fn resolve_parameters(
&self,
params: &HashMap<String, serde_json::Value>,
current_step: Option<&str>,
) -> HashMap<String, serde_json::Value> {
let mut resolved = HashMap::new();
for (key, value) in params {
let resolved_value = self.resolve_value(value, current_step);
resolved.insert(key.clone(), resolved_value);
}
resolved
}
fn resolve_value(
&self,
value: &serde_json::Value,
current_step: Option<&str>,
) -> serde_json::Value {
match value {
serde_json::Value::String(s) => {
if let Some(var_name) = s.strip_prefix("${").and_then(|s| s.strip_suffix('}')) {
self.get(var_name, current_step)
.unwrap_or(serde_json::Value::Null)
} else {
serde_json::Value::String(self.resolve_string(s, current_step))
}
}
serde_json::Value::Array(arr) => serde_json::Value::Array(
arr.iter()
.map(|v| self.resolve_value(v, current_step))
.collect(),
),
serde_json::Value::Object(obj) => serde_json::Value::Object(
obj.iter()
.map(|(k, v)| (k.clone(), self.resolve_value(v, current_step)))
.collect(),
),
_ => value.clone(),
}
}
pub fn get_all(&self, current_step: Option<&str>) -> HashMap<String, serde_json::Value> {
let mut all_vars = self.global.clone();
if let Some(step_name) = current_step {
if let Some(step_vars) = self.step_scoped.get(step_name) {
all_vars.extend(step_vars.clone());
}
}
all_vars
}
pub fn clear_step_scope(&mut self, step: &str) {
self.step_scoped.remove(step);
}
pub fn has_variable(&self, name: &str, current_step: Option<&str>) -> bool {
self.get(name, current_step).is_some()
}
pub fn list_variable_names(&self, current_step: Option<&str>) -> Vec<String> {
let mut names: Vec<String> = self.global.keys().cloned().collect();
if let Some(step_name) = current_step {
if let Some(step_vars) = self.step_scoped.get(step_name) {
names.extend(step_vars.keys().cloned());
}
}
names.sort();
names.dedup();
names
}
}
impl Default for VariableResolver {
fn default() -> Self {
Self::new(HashMap::new())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_variable_resolver_creation() {
let resolver = VariableResolver::new(HashMap::new());
assert_eq!(resolver.global.len(), 0);
}
#[test]
fn test_set_and_get_global() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_global("key1".to_string(), serde_json::json!("value1"));
let value = resolver.get("key1", None);
assert!(value.is_some());
assert_eq!(value.unwrap().as_str().unwrap_or_default(), "value1");
}
#[test]
fn test_set_and_get_step_variable() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_step_variable(
"step1".to_string(),
"key1".to_string(),
serde_json::json!("value1"),
);
let value = resolver.get("key1", Some("step1"));
assert!(value.is_some());
assert_eq!(value.unwrap().as_str().unwrap_or_default(), "value1");
let value2 = resolver.get("key1", Some("step2"));
assert!(value2.is_none());
}
#[test]
fn test_step_scope_overrides_global() {
let mut global = HashMap::new();
global.insert("key1".to_string(), serde_json::json!("global_value"));
let mut resolver = VariableResolver::new(global);
resolver.set_step_variable(
"step1".to_string(),
"key1".to_string(),
serde_json::json!("step_value"),
);
let value = resolver.get("key1", Some("step1"));
assert_eq!(value.unwrap().as_str().unwrap_or_default(), "step_value");
let value2 = resolver.get("key1", None);
assert_eq!(value2.unwrap().as_str().unwrap_or_default(), "global_value");
}
#[test]
fn test_resolve_string_simple() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_global("name".to_string(), serde_json::json!("World"));
let result = resolver.resolve_string("Hello, ${name}!", None);
assert_eq!(result, "Hello, World!");
}
#[test]
fn test_resolve_string_multiple_variables() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_global("first".to_string(), serde_json::json!("John"));
resolver.set_global("last".to_string(), serde_json::json!("Doe"));
let result = resolver.resolve_string("Name: ${first} ${last}", None);
assert_eq!(result, "Name: John Doe");
}
#[test]
fn test_resolve_string_missing_variable() {
let resolver = VariableResolver::new(HashMap::new());
let result = resolver.resolve_string("Hello, ${missing}!", None);
assert_eq!(result, "Hello, ${missing}!");
}
#[test]
fn test_resolve_parameters() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_global("voice".to_string(), serde_json::json!("en-US-neural"));
let mut params = HashMap::new();
params.insert("voice".to_string(), serde_json::json!("${voice}"));
params.insert("text".to_string(), serde_json::json!("Hello"));
let resolved = resolver.resolve_parameters(¶ms, None);
assert_eq!(
resolved.get("voice").unwrap().as_str().unwrap_or_default(),
"en-US-neural"
);
assert_eq!(
resolved.get("text").unwrap().as_str().unwrap_or_default(),
"Hello"
);
}
#[test]
fn test_has_variable() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_global("exists".to_string(), serde_json::json!("yes"));
assert!(resolver.has_variable("exists", None));
assert!(!resolver.has_variable("missing", None));
}
#[test]
fn test_list_variable_names() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_global("var1".to_string(), serde_json::json!("value1"));
resolver.set_global("var2".to_string(), serde_json::json!("value2"));
resolver.set_step_variable(
"step1".to_string(),
"var3".to_string(),
serde_json::json!("value3"),
);
let names = resolver.list_variable_names(Some("step1"));
assert_eq!(names.len(), 3);
assert!(names.contains(&"var1".to_string()));
assert!(names.contains(&"var2".to_string()));
assert!(names.contains(&"var3".to_string()));
}
#[test]
fn test_clear_step_scope() {
let mut resolver = VariableResolver::new(HashMap::new());
resolver.set_step_variable(
"step1".to_string(),
"key1".to_string(),
serde_json::json!("value1"),
);
assert!(resolver.get("key1", Some("step1")).is_some());
resolver.clear_step_scope("step1");
assert!(resolver.get("key1", Some("step1")).is_none());
}
}