1use alloc::string::String;
4
5use crate::{compat::HashMap, value::Value};
6
7#[derive(Debug, Clone, Default)]
25pub struct Context {
26 pub(crate) values: HashMap<String, Value>,
27}
28
29impl Context {
30 #[must_use]
32 pub fn new() -> Self {
33 Self::default()
34 }
35
36 #[must_use]
38 pub fn with_capacity(capacity: usize) -> Self {
39 Self {
40 values: HashMap::with_capacity(capacity),
41 }
42 }
43
44 #[must_use]
46 pub fn len(&self) -> usize {
47 self.values.len()
48 }
49
50 #[must_use]
52 pub fn is_empty(&self) -> bool {
53 self.values.is_empty()
54 }
55
56 #[must_use]
58 pub fn contains_key(&self, key: &str) -> bool {
59 self.values.contains_key(key)
60 }
61
62 pub fn set(&mut self, key: impl Into<String>, value: impl Into<Value>) {
79 let key = key.into();
80 assert!(
81 key != crate::consts::ENUM_TAG_KEY,
82 "cannot set reserved internal key '{}' directly in Context — \
83 use enum types in the template frontmatter instead",
84 crate::consts::ENUM_TAG_KEY,
85 );
86 self.values.insert(key, value.into());
87 }
88
89 #[must_use]
91 pub fn var(mut self, key: impl Into<String>, value: impl Into<Value>) -> Self {
92 self.set(key, value);
93 self
94 }
95
96 #[must_use]
98 pub fn get(&self, key: &str) -> Option<&Value> {
99 self.values.get(key)
100 }
101
102 #[must_use]
106 pub fn into_inner(self) -> HashMap<String, Value> {
107 self.values
108 }
109}
110
111impl<K: Into<String>, V: Into<Value>> FromIterator<(K, V)> for Context {
113 fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
114 let iter = iter.into_iter();
115 let (lower, _) = iter.size_hint();
116 let mut ctx = Self::with_capacity(lower);
117 for (k, v) in iter {
118 ctx.set(k, v);
119 }
120 ctx
121 }
122}
123
124#[cfg(feature = "serde")]
125impl Context {
126 pub fn from_value(val: Value) -> Result<Self, crate::error::TemplateError> {
132 match val {
133 Value::Struct(arc_map) => {
134 let values =
137 alloc::sync::Arc::try_unwrap(arc_map).unwrap_or_else(|arc| (*arc).clone());
138 Ok(Self { values })
139 }
140 other => Err(crate::error::TemplateError::syntax(format!(
141 "expected struct/map, got {}",
142 other.type_name()
143 ))),
144 }
145 }
146
147 pub fn from_serialize<T: serde::Serialize>(
153 value: &T,
154 ) -> Result<Self, crate::error::TemplateError> {
155 let val = crate::serde_support::to_value(value).map_err(|e| {
156 crate::error::TemplateError::syntax(format!("serde conversion failed: {e}"))
157 })?;
158 Self::from_value(val)
159 }
160
161 pub fn from_cbor(data: &[u8]) -> Result<Self, crate::error::TemplateError> {
170 let val: Value = ciborium::from_reader(data).map_err(|e| {
171 crate::error::TemplateError::syntax(format!("cbor deserialization failed: {e}"))
172 })?;
173 Self::from_value(val)
174 }
175}
176
177#[cfg(feature = "std")]
180#[cfg(feature = "serde")]
181impl Context {
182 pub fn from_flexbuffers(data: &[u8]) -> Result<Self, crate::error::TemplateError> {
188 let r = flexbuffers::Reader::get_root(data).map_err(|e| {
189 crate::error::TemplateError::syntax(format!("flexbuffers root error: {e}"))
190 })?;
191 let val: Value = serde::Deserialize::deserialize(r).map_err(|e| {
192 crate::error::TemplateError::syntax(format!("flexbuffers deserialization failed: {e}"))
193 })?;
194 Self::from_value(val)
195 }
196}
197
198#[cfg(test)]
203mod tests {
204 use super::*;
205
206 #[test]
207 fn new_context_is_empty() {
208 let ctx = Context::new();
209 assert!(ctx.get("anything").is_none());
210 }
211
212 #[test]
213 fn set_and_get_str() {
214 let mut ctx = Context::new();
215 ctx.set("greeting", "hello");
216 assert_eq!(ctx.get("greeting"), Some(&Value::Str("hello".into())));
217 }
218
219 #[test]
220 fn set_and_get_bool() {
221 let mut ctx = Context::new();
222 ctx.set("flag", true);
223 assert_eq!(ctx.get("flag"), Some(&Value::Bool(true)));
224 }
225
226 #[test]
227 fn set_and_get_int() {
228 let mut ctx = Context::new();
229 ctx.set("count", 42_i64);
230 assert_eq!(ctx.get("count"), Some(&Value::Int(42)));
231 }
232
233 #[test]
234 fn overwrite_value() {
235 let mut ctx = Context::new();
236 ctx.set("k", "first");
237 ctx.set("k", "second");
238 assert_eq!(ctx.get("k"), Some(&Value::Str("second".into())));
239 }
240
241 #[test]
242 fn get_missing_returns_none() {
243 let ctx = Context::new();
244 assert_eq!(ctx.get("nonexistent"), None);
245 }
246
247 #[test]
248 fn default_is_same_as_new() {
249 let a = Context::new();
250 let b = Context::default();
251 assert!(a.values.is_empty());
252 assert!(b.values.is_empty());
253 }
254
255 #[test]
256 #[should_panic(expected = "reserved internal key")]
257 fn set_rejects_internal_kind_key() {
258 let mut ctx = Context::new();
259 ctx.set(crate::consts::ENUM_TAG_KEY, "Variant");
260 }
261
262 #[test]
263 fn set_allows_similar_but_different_keys() {
264 let mut ctx = Context::new();
266 ctx.set("kind", "some_kind");
267 ctx.set("__type__", "some_type");
268 ctx.set("kind_of", "something");
269 assert!(ctx.get("kind").is_some());
270 assert!(ctx.get("__type__").is_some());
271 }
272}