use std::collections::HashMap;
use crate::spec::{ParamsSpec, ParamDef};
pub type ParamValues = HashMap<String, serde_json::Value>;
pub fn collect_param_defaults(params_specs: &[&ParamsSpec]) -> ParamValues {
let mut values = ParamValues::new();
for spec in params_specs {
for param in &spec.params {
let key = if let Some(ref name) = spec.name {
format!("{}.{}", name, param.id)
} else {
param.id.clone()
};
if let Some(ref default) = param.default {
values.insert(key, default.clone());
}
}
}
values
}
pub fn resolve_param_references(yaml: &str, param_values: &ParamValues) -> String {
let mut result = yaml.to_string();
let mut keys: Vec<&String> = param_values.keys().collect();
keys.sort_by_key(|b| std::cmp::Reverse(b.len()));
for key in keys {
let value = ¶m_values[key];
let pattern = format!("\"${}\"", key);
if result.contains(&pattern) {
let replacement = value_to_yaml(value);
result = result.replace(&pattern, &replacement);
}
let _unquoted_pattern = format!("${}", key);
}
result
}
fn value_to_yaml(value: &serde_json::Value) -> String {
match value {
serde_json::Value::String(s) => format!("\"{}\"", s),
serde_json::Value::Number(n) => n.to_string(),
serde_json::Value::Bool(b) => b.to_string(),
serde_json::Value::Null => "null".to_string(),
serde_json::Value::Array(arr) => {
let items: Vec<String> = arr.iter().map(value_to_yaml).collect();
format!("[{}]", items.join(", "))
}
serde_json::Value::Object(_obj) => {
serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string())
}
}
}
pub fn extract_inline_param_defaults(yaml: &str) -> ParamValues {
let mut values = ParamValues::new();
if let Ok(serde_yaml::Value::Mapping(map)) = serde_yaml::from_str::<serde_yaml::Value>(yaml) {
if let Some(params_val) = map.get(serde_yaml::Value::String("params".into())) {
if let Ok(params) = serde_yaml::from_value::<Vec<ParamDef>>(params_val.clone()) {
for param in ¶ms {
if let Some(ref default) = param.default {
values.insert(param.id.clone(), default.clone());
}
}
}
}
}
values
}
pub fn extract_param_references(yaml: &str) -> Vec<String> {
let mut refs = Vec::new();
let mut i = 0;
let bytes = yaml.as_bytes();
while i < bytes.len() {
if bytes[i] == b'$' && i > 0 && (bytes[i - 1] == b'"' || bytes[i - 1] == b' ' || bytes[i - 1] == b':') {
let start = i + 1;
let mut end = start;
while end < bytes.len()
&& (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_' || bytes[end] == b'.')
{
end += 1;
}
if end > start {
refs.push(yaml[start..end].to_string());
}
i = end;
} else {
i += 1;
}
}
refs.sort();
refs.dedup();
refs
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn collect_defaults_named_params() {
let spec = ParamsSpec {
version: 1,
name: Some("dashboard_filters".to_string()),
params: vec![
ParamDef {
id: "region".to_string(),
param_type: "multiselect".to_string(),
label: "Region".to_string(),
options: Some(vec!["US".into(), "EU".into()]),
default: Some(serde_json::json!(["US", "EU"])),
placeholder: None,
layout: None,
},
ParamDef {
id: "min_revenue".to_string(),
param_type: "number".to_string(),
label: "Min Revenue".to_string(),
options: None,
default: Some(serde_json::json!(50000)),
placeholder: None,
layout: None,
},
],
};
let defaults = collect_param_defaults(&[&spec]);
assert_eq!(defaults.get("dashboard_filters.region"), Some(&serde_json::json!(["US", "EU"])));
assert_eq!(defaults.get("dashboard_filters.min_revenue"), Some(&serde_json::json!(50000)));
}
#[test]
fn resolve_string_param() {
let mut params = ParamValues::new();
params.insert("top_n".to_string(), serde_json::json!(10));
let yaml = r#"limit: "$top_n""#;
let resolved = resolve_param_references(yaml, ¶ms);
assert_eq!(resolved, "limit: 10");
}
#[test]
fn resolve_array_param() {
let mut params = ParamValues::new();
params.insert("dashboard_filters.region".to_string(), serde_json::json!(["US", "EU"]));
let yaml = r#"value: "$dashboard_filters.region""#;
let resolved = resolve_param_references(yaml, ¶ms);
assert_eq!(resolved, r#"value: ["US", "EU"]"#);
}
#[test]
fn unresolved_param_stays() {
let params = ParamValues::new();
let yaml = r#"value: "$unknown.param""#;
let resolved = resolve_param_references(yaml, ¶ms);
assert_eq!(resolved, r#"value: "$unknown.param""#);
}
#[test]
fn extract_refs() {
let yaml = r#"
value: "$dashboard_filters.region"
limit: "$top_n"
"#;
let refs = extract_param_references(yaml);
assert!(refs.contains(&"dashboard_filters.region".to_string()));
assert!(refs.contains(&"top_n".to_string()));
}
}