use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Deserialize, Serialize)]
#[serde(deny_unknown_fields)]
pub struct DeliverCfg {
pub channel: String,
#[serde(alias = "recipient")]
pub to: String,
}
pub fn render_template(template: &str, item: &serde_json::Value) -> String {
let mut out = template.to_string();
if let serde_json::Value::Object(map) = item {
for (k, v) in map {
let needle = format!("{{{k}}}");
let replacement = match v {
serde_json::Value::String(s) => s.clone(),
other => other.to_string(),
};
out = out.replace(&needle, &replacement);
}
}
out = out.replace("{json}", &item.to_string());
out
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn parses_canonical_to() {
let cfg: DeliverCfg =
serde_json::from_value(json!({ "channel": "whatsapp", "to": "57300" })).unwrap();
assert_eq!(cfg.channel, "whatsapp");
assert_eq!(cfg.to, "57300");
}
#[test]
fn parses_recipient_alias() {
let cfg: DeliverCfg =
serde_json::from_value(json!({ "channel": "telegram", "recipient": "-100" })).unwrap();
assert_eq!(cfg.to, "-100");
}
#[test]
fn rejects_unknown_field() {
let r: Result<DeliverCfg, _> =
serde_json::from_value(json!({ "channel": "x", "to": "y", "bogus": 1 }));
assert!(r.is_err());
}
#[test]
fn render_substitutes_string_fields() {
let item = json!({ "name": "Ana", "phone": "+57" });
assert_eq!(
render_template("Hi {name} ({phone})", &item),
"Hi Ana (+57)"
);
}
#[test]
fn render_stringifies_non_string_fields() {
let item = json!({ "count": 42, "active": true });
let out = render_template("{count} items, active={active}", &item);
assert_eq!(out, "42 items, active=true");
}
#[test]
fn render_leaves_unknown_placeholders_intact() {
let item = json!({ "x": "y" });
assert_eq!(render_template("hello {missing}", &item), "hello {missing}");
}
#[test]
fn render_json_placeholder_dumps_whole_item() {
let item = json!({ "a": 1 });
let out = render_template("debug={json}", &item);
assert!(out.starts_with("debug={"));
assert!(out.contains("\"a\":1"));
}
}