Skip to main content

earl_core/
render.rs

1use std::collections::BTreeMap;
2
3use anyhow::Result;
4use serde_json::Value;
5
6/// Trait abstracting template rendering so protocol crates can render
7/// strings and JSON values without depending on a specific engine.
8pub trait TemplateRenderer {
9    /// Render a template string with the given context, returning the raw string.
10    fn render_str(&self, template: &str, context: &Value) -> Result<String>;
11
12    /// Render a JSON value, recursively expanding any template strings.
13    fn render_value(&self, value: &Value, context: &Value) -> Result<Value>;
14}
15
16/// Render a `BTreeMap<String, Value>` of template key-value pairs into a
17/// flat list of `(String, String)` pairs, expanding arrays into multiple
18/// entries with the same key.
19pub fn render_key_value_map(
20    input: Option<&BTreeMap<String, Value>>,
21    context: &Value,
22    renderer: &dyn TemplateRenderer,
23) -> Result<Vec<(String, String)>> {
24    let mut out = Vec::new();
25    let Some(input) = input else {
26        return Ok(out);
27    };
28
29    for (key, value) in input {
30        let rendered_key = renderer.render_str(key, context)?;
31        let rendered_value = renderer.render_value(value, context)?;
32
33        match rendered_value {
34            Value::Array(values) => {
35                for value in values {
36                    out.push((rendered_key.clone(), value_to_string(value)?));
37                }
38            }
39            other => out.push((rendered_key, value_to_string(other)?)),
40        }
41    }
42
43    Ok(out)
44}
45
46/// Convert a `serde_json::Value` into its string representation.
47pub fn value_to_string(value: Value) -> Result<String> {
48    let out = match value {
49        Value::Null => String::new(),
50        Value::Bool(v) => v.to_string(),
51        Value::Number(v) => v.to_string(),
52        Value::String(v) => v,
53        Value::Array(_) | Value::Object(_) => serde_json::to_string(&value)?,
54    };
55    Ok(out)
56}