use serde_json::Value;
use std::collections::HashMap;
pub fn resolve_url(url: &str, base_url: &str, step_responses: &HashMap<String, Value>) -> String {
let mut result = url.to_string();
result = result.replace("{base_url}", base_url);
resolve_env_templates(&mut result);
resolve_step_templates(&mut result, step_responses);
result
}
pub fn resolve_body(body: &mut Value, step_responses: &HashMap<String, Value>) {
match body {
Value::String(s) => {
resolve_env_templates(s);
resolve_step_templates(s, step_responses);
}
Value::Object(obj) => {
for v in obj.values_mut() {
resolve_body(v, step_responses);
}
}
Value::Array(arr) => {
for v in arr.iter_mut() {
resolve_body(v, step_responses);
}
}
_ => {}
}
}
pub fn resolve_headers(
headers: &mut HashMap<String, String>,
step_responses: &HashMap<String, Value>,
) {
for value in headers.values_mut() {
resolve_env_templates(value);
resolve_step_templates(value, step_responses);
}
}
fn resolve_env_templates(s: &mut String) {
let re = regex::Regex::new(r"\{env\.([A-Za-z_][A-Za-z0-9_]*)\}").unwrap();
*s = re
.replace_all(s, |caps: ®ex::Captures| {
let var_name = &caps[1];
std::env::var(var_name).unwrap_or_else(|_| {
tracing::warn!(
"Environment variable '{}' not set, leaving template unresolved",
var_name
);
caps[0].to_string()
})
})
.to_string();
}
fn resolve_step_templates(s: &mut String, step_responses: &HashMap<String, Value>) {
for (step_name, response) in step_responses {
let id_pattern = format!("{{steps.{}.id}}", step_name);
if let Some(id) = response.get("id").and_then(|v| v.as_str())
&& s.contains(&id_pattern)
{
*s = s.replace(&id_pattern, id);
}
if let Some(obj) = response.as_object() {
resolve_step_fields(s, step_name, "", obj);
}
}
}
fn resolve_step_fields(
s: &mut String,
step_name: &str,
prefix: &str,
obj: &serde_json::Map<String, Value>,
) {
for (key, value) in obj {
let current_path = if prefix.is_empty() {
key.clone()
} else {
format!("{}.{}", prefix, key)
};
let pattern = format!("{{steps.{}.{}}}", step_name, current_path);
match value {
Value::String(v) => {
if s.contains(&pattern) {
*s = s.replace(&pattern, v);
}
}
Value::Number(n) => {
let v = n.to_string();
if s.contains(&pattern) {
*s = s.replace(&pattern, &v);
}
}
Value::Array(arr) => {
if let Some(Value::Object(child)) = arr.first() {
resolve_step_fields(s, step_name, ¤t_path, child);
}
}
Value::Object(child) => {
resolve_step_fields(s, step_name, ¤t_path, child);
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn resolve_base_url() {
let result = resolve_url(
"{base_url}/Patient",
"http://localhost:8080",
&HashMap::new(),
);
assert_eq!(result, "http://localhost:8080/Patient");
}
#[test]
fn resolve_step_id() {
let mut steps = HashMap::new();
steps.insert(
"create_patient".into(),
json!({
"resourceType": "Patient",
"id": "pat-001"
}),
);
let result = resolve_url("/Patient/{steps.create_patient.id}", "", &steps);
assert_eq!(result, "/Patient/pat-001");
}
#[test]
fn resolve_step_field() {
let mut steps = HashMap::new();
steps.insert(
"create_patient".into(),
json!({
"resourceType": "Patient",
"id": "pat-001",
"name": [{"family": "Smith"}]
}),
);
let result = resolve_url(
"/Patient?name={steps.create_patient.name.family}",
"",
&steps,
);
assert_eq!(result, "/Patient?name=Smith");
}
#[test]
fn resolve_body_templates() {
let mut steps = HashMap::new();
steps.insert(
"parent".into(),
json!({
"id": "org-001"
}),
);
let mut body = json!({
"resourceType": "Organization",
"partOf": {"reference": "Organization/{steps.parent.id}"}
});
resolve_body(&mut body, &steps);
assert_eq!(
body,
json!({
"resourceType": "Organization",
"partOf": {"reference": "Organization/org-001"}
})
);
}
#[test]
fn resolve_headers_templates() {
let mut steps = HashMap::new();
steps.insert(
"auth".into(),
json!({
"token": "abc-123"
}),
);
let mut headers = HashMap::new();
headers.insert("Authorization".into(), "Bearer {steps.auth.token}".into());
resolve_headers(&mut headers, &steps);
assert_eq!(headers.get("Authorization").unwrap(), "Bearer abc-123");
}
#[test]
fn no_templates_unchanged() {
let result = resolve_url("/plain/url", "http://base", &HashMap::new());
assert_eq!(result, "/plain/url");
}
#[test]
fn resolve_env_var() {
unsafe {
std::env::set_var("MOMUS_TEST_URL", "http://test:8080");
}
let result = resolve_url(
"{env.MOMUS_TEST_URL}/api",
"http://fallback",
&HashMap::new(),
);
assert_eq!(result, "http://test:8080/api");
unsafe {
std::env::remove_var("MOMUS_TEST_URL");
}
}
#[test]
fn resolve_env_var_missing() {
let result = resolve_url("{env.NONEXISTENT_VAR}/path", "http://base", &HashMap::new());
assert_eq!(result, "{env.NONEXISTENT_VAR}/path");
}
}