use minijinja::{context, Environment};
pub(crate) fn render(
template: &str,
user_message: &str,
model: &str,
provider: &str,
request: &serde_json::Value,
) -> Result<String, String> {
let mut env = Environment::new();
env.add_template("t", template)
.map_err(|e| format!("template compile error: {}", e))?;
let tmpl = env
.get_template("t")
.map_err(|e| format!("template lookup error: {}", e))?;
tmpl.render(context! {
user_message => user_message,
model => model,
provider => provider,
request => request,
})
.map_err(|e| format!("template render error: {}", e))
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn should_interpolate_user_message() {
let out = render(
"Hello, {{ user_message }}!",
"world",
"gpt-4",
"openai",
&json!({}),
)
.unwrap();
assert_eq!(out, "Hello, world!");
}
#[test]
fn should_interpolate_model_and_provider() {
let out = render(
"{{ provider }}/{{ model }}",
"",
"claude-sonnet-4-6",
"anthropic",
&json!({}),
)
.unwrap();
assert_eq!(out, "anthropic/claude-sonnet-4-6");
}
#[test]
fn should_reach_into_request_json() {
let req = json!({
"messages": [{"role": "user", "content": "deep value"}]
});
let out = render(
"Got: {{ request.messages[0].content }}",
"ignored",
"m",
"openai",
&req,
)
.unwrap();
assert_eq!(out, "Got: deep value");
}
#[test]
fn should_report_compile_error_for_invalid_syntax() {
let err = render("Hello {{ unclosed", "w", "m", "p", &json!({})).unwrap_err();
assert!(
err.contains("template compile error"),
"got unexpected error: {}",
err
);
}
#[test]
fn should_report_render_error_for_unknown_filter() {
let err = render(
"{{ user_message | nonexistent_filter }}",
"hi",
"m",
"p",
&json!({}),
)
.unwrap_err();
assert!(
err.contains("template render error"),
"got unexpected error: {}",
err
);
}
#[test]
fn should_render_literal_template_unchanged() {
let out = render("no placeholders here", "w", "m", "p", &json!({})).unwrap();
assert_eq!(out, "no placeholders here");
}
}