use crate::application::projection_registry::TargetForm;
pub fn render(target_form: TargetForm, template: &str, data: &serde_json::Value) -> String {
let _ = target_form;
let mut out = String::with_capacity(template.len());
let bytes = template.as_bytes();
let mut i = 0;
while i < bytes.len() {
if i + 1 < bytes.len() && bytes[i] == b'{' && bytes[i + 1] == b'{' {
if let Some(end) = find_closing(bytes, i + 2) {
let key = std::str::from_utf8(&bytes[i + 2..end]).unwrap_or("").trim();
out.push_str(&lookup(data, key));
i = end + 2;
continue;
}
}
out.push(bytes[i] as char);
i += 1;
}
out
}
fn find_closing(bytes: &[u8], start: usize) -> Option<usize> {
let mut j = start;
while j + 1 < bytes.len() {
if bytes[j] == b'}' && bytes[j + 1] == b'}' {
return Some(j);
}
j += 1;
}
None
}
fn lookup(data: &serde_json::Value, key: &str) -> String {
if key.is_empty() {
return String::new();
}
let mut current = data;
for part in key.split('.') {
match current.get(part) {
Some(next) => current = next,
None => return String::new(),
}
}
match current {
serde_json::Value::String(s) => s.clone(),
serde_json::Value::Null => String::new(),
other => other.to_string(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn substitutes_simple_key() {
let out = render(
TargetForm::Prompt,
"Hi, {{name}}!",
&json!({"name": "alpha"}),
);
assert_eq!(out, "Hi, alpha!");
}
#[test]
fn substitutes_nested_path() {
let out = render(
TargetForm::Markdown,
"Owner is {{owner.name}}.",
&json!({"owner": {"name": "user_a"}}),
);
assert_eq!(out, "Owner is user_a.");
}
#[test]
fn trims_inner_whitespace() {
let out = render(TargetForm::Prompt, "X={{ a.b }}", &json!({"a": {"b": 7}}));
assert_eq!(out, "X=7");
}
#[test]
fn missing_path_renders_empty() {
let out = render(
TargetForm::Prompt,
"[{{absent}}]",
&json!({"name": "alpha"}),
);
assert_eq!(out, "[]");
}
#[test]
fn passes_through_text_without_braces() {
let out = render(TargetForm::Markdown, "plain text", &json!({}));
assert_eq!(out, "plain text");
}
#[test]
fn unmatched_open_brace_passes_through() {
let out = render(TargetForm::Prompt, "{{ no_close", &json!({}));
assert_eq!(out, "{{ no_close");
}
#[test]
fn number_renders_as_json_number() {
let out = render(TargetForm::Json, "n={{x}}", &json!({"x": 42}));
assert_eq!(out, "n=42");
}
#[test]
fn null_renders_as_empty() {
let out = render(
TargetForm::Prompt,
"v=[{{x}}]",
&json!({"x": serde_json::Value::Null}),
);
assert_eq!(out, "v=[]");
}
#[test]
fn multiple_substitutions_in_one_template() {
let out = render(
TargetForm::Prompt,
"{{a}}-{{b}}-{{c}}",
&json!({"a": "x", "b": "y", "c": "z"}),
);
assert_eq!(out, "x-y-z");
}
}