1use std::collections::BTreeMap;
2
3#[derive(Debug, Clone, Default)]
13pub struct TemplateContext {
14 values: BTreeMap<String, minijinja::Value>,
15}
16
17impl TemplateContext {
18 pub fn set(&mut self, key: impl Into<String>, value: minijinja::Value) {
25 self.values.insert(key.into(), value);
26 }
27
28 pub fn get(&self, key: &str) -> Option<&minijinja::Value> {
31 self.values.get(key)
32 }
33
34 pub(crate) fn merge(&self, handler_context: minijinja::Value) -> minijinja::Value {
40 let mut merged = BTreeMap::new();
41
42 for (k, v) in &self.values {
44 merged.insert(k.clone(), v.clone());
45 }
46
47 if let Ok(keys) = handler_context.try_iter() {
49 for key in keys {
50 if let Ok(val) = handler_context.get_attr(&key.to_string()) {
51 merged.insert(key.to_string(), val);
52 }
53 }
54 } else if !handler_context.is_none() && !handler_context.is_undefined() {
55 tracing::warn!(
56 "Handler context is not a map — handler values ignored. Use context! {{ ... }}"
57 );
58 }
59
60 minijinja::Value::from(merged)
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67 use minijinja::context;
68
69 #[test]
70 fn set_and_get_value() {
71 let mut ctx = TemplateContext::default();
72 ctx.set("name", minijinja::Value::from("Dmytro"));
73 let val = ctx.get("name").unwrap();
74 assert_eq!(val.to_string(), "Dmytro");
75 }
76
77 #[test]
78 fn get_missing_key_returns_none() {
79 let ctx = TemplateContext::default();
80 assert!(ctx.get("missing").is_none());
81 }
82
83 #[test]
84 fn set_overwrites_existing_value() {
85 let mut ctx = TemplateContext::default();
86 ctx.set("key", minijinja::Value::from("old"));
87 ctx.set("key", minijinja::Value::from("new"));
88 assert_eq!(ctx.get("key").unwrap().to_string(), "new");
89 }
90
91 #[test]
92 fn merge_combines_middleware_and_handler_context() {
93 let mut ctx = TemplateContext::default();
94 ctx.set("locale", minijinja::Value::from("en"));
95 ctx.set("name", minijinja::Value::from("middleware"));
96
97 let handler_ctx = context! { name => "handler", items => vec![1, 2, 3] };
98 let merged = ctx.merge(handler_ctx);
99
100 assert_eq!(merged.get_attr("name").unwrap().to_string(), "handler");
102 assert_eq!(merged.get_attr("locale").unwrap().to_string(), "en");
104 assert!(merged.get_attr("items").is_ok());
106 }
107
108 #[test]
109 fn default_context_is_empty() {
110 let ctx = TemplateContext::default();
111 assert!(ctx.get("anything").is_none());
112 }
113
114 #[test]
115 fn context_is_clone() {
116 let mut ctx = TemplateContext::default();
117 ctx.set("key", minijinja::Value::from("value"));
118 let cloned = ctx.clone();
119 assert_eq!(cloned.get("key").unwrap().to_string(), "value");
120 }
121
122 #[test]
123 fn merge_with_non_map_ignores_handler_values() {
124 let mut ctx = TemplateContext::default();
125 ctx.set("locale", minijinja::Value::from("en"));
126 ctx.set("name", minijinja::Value::from("middleware"));
127
128 let merged = ctx.merge(minijinja::Value::from("not a map"));
130
131 assert_eq!(merged.get_attr("locale").unwrap().to_string(), "en");
133 assert_eq!(merged.get_attr("name").unwrap().to_string(), "middleware");
134 }
135}