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) {
20 self.values.insert(key.into(), value);
21 }
22
23 pub fn get(&self, key: &str) -> Option<&minijinja::Value> {
25 self.values.get(key)
26 }
27
28 pub(crate) fn merge(&self, handler_context: minijinja::Value) -> minijinja::Value {
34 let mut merged = BTreeMap::new();
35
36 for (k, v) in &self.values {
38 merged.insert(k.clone(), v.clone());
39 }
40
41 if let Ok(keys) = handler_context.try_iter() {
43 for key in keys {
44 if let Ok(val) = handler_context.get_attr(&key.to_string()) {
45 merged.insert(key.to_string(), val);
46 }
47 }
48 } else if !handler_context.is_none() && !handler_context.is_undefined() {
49 tracing::warn!(
50 "Handler context is not a map — handler values ignored. Use context! {{ ... }}"
51 );
52 }
53
54 minijinja::Value::from(merged)
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use minijinja::context;
62
63 #[test]
64 fn set_and_get_value() {
65 let mut ctx = TemplateContext::default();
66 ctx.set("name", minijinja::Value::from("Dmytro"));
67 let val = ctx.get("name").unwrap();
68 assert_eq!(val.to_string(), "Dmytro");
69 }
70
71 #[test]
72 fn get_missing_key_returns_none() {
73 let ctx = TemplateContext::default();
74 assert!(ctx.get("missing").is_none());
75 }
76
77 #[test]
78 fn set_overwrites_existing_value() {
79 let mut ctx = TemplateContext::default();
80 ctx.set("key", minijinja::Value::from("old"));
81 ctx.set("key", minijinja::Value::from("new"));
82 assert_eq!(ctx.get("key").unwrap().to_string(), "new");
83 }
84
85 #[test]
86 fn merge_combines_middleware_and_handler_context() {
87 let mut ctx = TemplateContext::default();
88 ctx.set("locale", minijinja::Value::from("en"));
89 ctx.set("name", minijinja::Value::from("middleware"));
90
91 let handler_ctx = context! { name => "handler", items => vec![1, 2, 3] };
92 let merged = ctx.merge(handler_ctx);
93
94 assert_eq!(merged.get_attr("name").unwrap().to_string(), "handler");
96 assert_eq!(merged.get_attr("locale").unwrap().to_string(), "en");
98 assert!(merged.get_attr("items").is_ok());
100 }
101
102 #[test]
103 fn default_context_is_empty() {
104 let ctx = TemplateContext::default();
105 assert!(ctx.get("anything").is_none());
106 }
107
108 #[test]
109 fn context_is_clone() {
110 let mut ctx = TemplateContext::default();
111 ctx.set("key", minijinja::Value::from("value"));
112 let cloned = ctx.clone();
113 assert_eq!(cloned.get("key").unwrap().to_string(), "value");
114 }
115
116 #[test]
117 fn merge_with_non_map_ignores_handler_values() {
118 let mut ctx = TemplateContext::default();
119 ctx.set("locale", minijinja::Value::from("en"));
120 ctx.set("name", minijinja::Value::from("middleware"));
121
122 let merged = ctx.merge(minijinja::Value::from("not a map"));
124
125 assert_eq!(merged.get_attr("locale").unwrap().to_string(), "en");
127 assert_eq!(merged.get_attr("name").unwrap().to_string(), "middleware");
128 }
129}