1use std::collections::HashMap;
8use crate::spec::{ParamsSpec, ParamDef};
9
10pub type ParamValues = HashMap<String, serde_json::Value>;
12
13pub fn collect_param_defaults(params_specs: &[&ParamsSpec]) -> ParamValues {
17 let mut values = ParamValues::new();
18
19 for spec in params_specs {
20 for param in &spec.params {
21 let key = if let Some(ref name) = spec.name {
22 format!("{}.{}", name, param.id)
23 } else {
24 param.id.clone()
25 };
26
27 if let Some(ref default) = param.default {
28 values.insert(key, default.clone());
29 }
30 }
31 }
32
33 values
34}
35
36pub fn resolve_param_references(yaml: &str, param_values: &ParamValues) -> String {
45 let mut result = yaml.to_string();
46
47 let mut keys: Vec<&String> = param_values.keys().collect();
50 keys.sort_by_key(|b| std::cmp::Reverse(b.len()));
51
52 for key in keys {
53 let value = ¶m_values[key];
54 let pattern = format!("\"${}\"", key);
55
56 if result.contains(&pattern) {
57 let replacement = value_to_yaml(value);
58 result = result.replace(&pattern, &replacement);
59 }
60
61 let _unquoted_pattern = format!("${}", key);
63 }
66
67 result
68}
69
70fn value_to_yaml(value: &serde_json::Value) -> String {
72 match value {
73 serde_json::Value::String(s) => format!("\"{}\"", s),
74 serde_json::Value::Number(n) => n.to_string(),
75 serde_json::Value::Bool(b) => b.to_string(),
76 serde_json::Value::Null => "null".to_string(),
77 serde_json::Value::Array(arr) => {
78 let items: Vec<String> = arr.iter().map(value_to_yaml).collect();
79 format!("[{}]", items.join(", "))
80 }
81 serde_json::Value::Object(_obj) => {
82 serde_json::to_string(value).unwrap_or_else(|_| "{}".to_string())
84 }
85 }
86}
87
88pub fn extract_inline_param_defaults(yaml: &str) -> ParamValues {
94 let mut values = ParamValues::new();
95
96 if let Ok(serde_yaml::Value::Mapping(map)) = serde_yaml::from_str::<serde_yaml::Value>(yaml) {
99 if let Some(params_val) = map.get(serde_yaml::Value::String("params".into())) {
100 if let Ok(params) = serde_yaml::from_value::<Vec<ParamDef>>(params_val.clone()) {
101 for param in ¶ms {
102 if let Some(ref default) = param.default {
103 values.insert(param.id.clone(), default.clone());
104 }
105 }
106 }
107 }
108 }
109
110 values
111}
112
113pub fn extract_param_references(yaml: &str) -> Vec<String> {
116 let mut refs = Vec::new();
117 let mut i = 0;
119 let bytes = yaml.as_bytes();
120 while i < bytes.len() {
121 if bytes[i] == b'$' && i > 0 && (bytes[i - 1] == b'"' || bytes[i - 1] == b' ' || bytes[i - 1] == b':') {
122 let start = i + 1;
123 let mut end = start;
124 while end < bytes.len()
125 && (bytes[end].is_ascii_alphanumeric() || bytes[end] == b'_' || bytes[end] == b'.')
126 {
127 end += 1;
128 }
129 if end > start {
130 refs.push(yaml[start..end].to_string());
131 }
132 i = end;
133 } else {
134 i += 1;
135 }
136 }
137 refs.sort();
138 refs.dedup();
139 refs
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn collect_defaults_named_params() {
148 let spec = ParamsSpec {
149 version: 1,
150 name: Some("dashboard_filters".to_string()),
151 params: vec![
152 ParamDef {
153 id: "region".to_string(),
154 param_type: "multiselect".to_string(),
155 label: "Region".to_string(),
156 options: Some(vec!["US".into(), "EU".into()]),
157 default: Some(serde_json::json!(["US", "EU"])),
158 placeholder: None,
159 layout: None,
160 },
161 ParamDef {
162 id: "min_revenue".to_string(),
163 param_type: "number".to_string(),
164 label: "Min Revenue".to_string(),
165 options: None,
166 default: Some(serde_json::json!(50000)),
167 placeholder: None,
168 layout: None,
169 },
170 ],
171 };
172
173 let defaults = collect_param_defaults(&[&spec]);
174 assert_eq!(defaults.get("dashboard_filters.region"), Some(&serde_json::json!(["US", "EU"])));
175 assert_eq!(defaults.get("dashboard_filters.min_revenue"), Some(&serde_json::json!(50000)));
176 }
177
178 #[test]
179 fn resolve_string_param() {
180 let mut params = ParamValues::new();
181 params.insert("top_n".to_string(), serde_json::json!(10));
182
183 let yaml = r#"limit: "$top_n""#;
184 let resolved = resolve_param_references(yaml, ¶ms);
185 assert_eq!(resolved, "limit: 10");
186 }
187
188 #[test]
189 fn resolve_array_param() {
190 let mut params = ParamValues::new();
191 params.insert("dashboard_filters.region".to_string(), serde_json::json!(["US", "EU"]));
192
193 let yaml = r#"value: "$dashboard_filters.region""#;
194 let resolved = resolve_param_references(yaml, ¶ms);
195 assert_eq!(resolved, r#"value: ["US", "EU"]"#);
196 }
197
198 #[test]
199 fn unresolved_param_stays() {
200 let params = ParamValues::new();
201 let yaml = r#"value: "$unknown.param""#;
202 let resolved = resolve_param_references(yaml, ¶ms);
203 assert_eq!(resolved, r#"value: "$unknown.param""#);
204 }
205
206 #[test]
207 fn extract_refs() {
208 let yaml = r#"
209 value: "$dashboard_filters.region"
210 limit: "$top_n"
211 "#;
212 let refs = extract_param_references(yaml);
213 assert!(refs.contains(&"dashboard_filters.region".to_string()));
214 assert!(refs.contains(&"top_n".to_string()));
215 }
216}