askama/
values.rs

1use core::any::Any;
2use core::borrow::Borrow;
3
4use crate::Error;
5
6/// No runtime values provided.
7pub const NO_VALUES: &dyn Values = &();
8
9/// Try to find `key` in `values` and then to convert it to `T`.
10pub fn get_value<T: Any>(values: &dyn Values, key: impl AsRef<str>) -> Result<&T, Error> {
11    let Some(src) = values.get_value(key.as_ref()) else {
12        return Err(Error::ValueMissing);
13    };
14
15    if let Some(value) = src.downcast_ref::<T>() {
16        return Ok(value);
17    } else if let Some(value) = src.downcast_ref::<&T>() {
18        return Ok(value);
19    }
20
21    #[cfg(feature = "alloc")]
22    if let Some(value) = src.downcast_ref::<alloc::boxed::Box<T>>() {
23        return Ok(value);
24    } else if let Some(value) = src.downcast_ref::<alloc::rc::Rc<T>>() {
25        return Ok(value);
26    } else if let Some(value) = src.downcast_ref::<alloc::sync::Arc<T>>() {
27        return Ok(value);
28    }
29
30    Err(Error::ValueType)
31}
32
33/// A runtime value store for [`Template::render_with_values()`][crate::Template::render_with_values].
34pub trait Values {
35    /// Try to find `key` in this store.
36    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any>;
37}
38
39crate::impl_for_ref! {
40    impl Values for T {
41        #[inline]
42        fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
43            T::get_value(self, key)
44        }
45    }
46}
47
48impl Values for () {
49    #[inline]
50    fn get_value<'a>(&'a self, _: &str) -> Option<&'a dyn Any> {
51        None
52    }
53}
54
55impl<K, V> Values for (K, V)
56where
57    K: Borrow<str>,
58    V: Value,
59{
60    #[inline]
61    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
62        if self.0.borrow() == key {
63            self.1.ref_any()
64        } else {
65            None
66        }
67    }
68}
69
70impl<T: Values> Values for Option<T> {
71    #[inline]
72    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
73        self.as_ref()?.get_value(key)
74    }
75}
76
77impl<K, V, const N: usize> Values for [(K, V); N]
78where
79    K: Borrow<str>,
80    V: Value,
81{
82    #[inline]
83    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
84        find_value_linear(self.iter(), key)
85    }
86}
87
88impl<K, V> Values for [(K, V)]
89where
90    K: Borrow<str>,
91    V: Value,
92{
93    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
94        find_value_linear(self.iter(), key)
95    }
96}
97
98#[cfg(feature = "alloc")]
99impl<K, V> Values for alloc::vec::Vec<(K, V)>
100where
101    K: Borrow<str>,
102    V: Value,
103{
104    #[inline]
105    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
106        find_value_linear(self.iter(), key)
107    }
108}
109
110#[cfg(feature = "alloc")]
111impl<K, V> Values for alloc::collections::VecDeque<(K, V)>
112where
113    K: Borrow<str>,
114    V: Value,
115{
116    #[inline]
117    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
118        find_value_linear(self.iter(), key)
119    }
120}
121
122#[cfg(feature = "alloc")]
123impl<K, V> Values for alloc::collections::LinkedList<(K, V)>
124where
125    K: Borrow<str>,
126    V: Value,
127{
128    #[inline]
129    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
130        find_value_linear(self.iter(), key)
131    }
132}
133
134fn find_value_linear<'a, K, V, I>(it: I, key: &str) -> Option<&'a dyn Any>
135where
136    K: Borrow<str> + 'a,
137    V: Value + 'a,
138    I: Iterator<Item = &'a (K, V)>,
139{
140    for (k, v) in it {
141        if k.borrow() == key {
142            return v.ref_any();
143        }
144    }
145    None
146}
147
148#[cfg(feature = "alloc")]
149impl<K, V> Values for alloc::collections::BTreeMap<K, V>
150where
151    K: Borrow<str> + core::cmp::Ord,
152    V: Value,
153{
154    #[inline]
155    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
156        self.get(key)?.ref_any()
157    }
158}
159
160#[cfg(feature = "std")]
161impl<K, V, S> Values for std::collections::HashMap<K, V, S>
162where
163    K: Borrow<str> + Eq + core::hash::Hash,
164    V: Value,
165    S: core::hash::BuildHasher,
166{
167    #[inline]
168    fn get_value<'a>(&'a self, key: &str) -> Option<&'a dyn Any> {
169        self.get(key)?.ref_any()
170    }
171}
172
173/// A value in a [`Values`] collection.
174///
175/// This is <code>[dyn](https://doc.rust-lang.org/stable/std/keyword.dyn.html) [Any]</code>,
176/// <code>[Option]&lt;dyn Any&gt;</code>, or a reference to either.
177pub trait Value {
178    /// Returns a reference to this value unless it is `None`.
179    fn ref_any(&self) -> Option<&dyn Any>;
180}
181
182crate::impl_for_ref! {
183    impl Value for T {
184        #[inline]
185        fn ref_any(&self) -> Option<&dyn Any> {
186            T::ref_any(self)
187        }
188    }
189}
190
191impl Value for dyn Any {
192    #[inline]
193    fn ref_any(&self) -> Option<&dyn Any> {
194        Some(self)
195    }
196}
197
198impl<T: Value> Value for Option<T> {
199    #[inline]
200    fn ref_any(&self) -> Option<&dyn Any> {
201        T::ref_any(self.as_ref()?)
202    }
203}
204
205#[cfg(test)]
206mod tests {
207    use assert_matches::assert_matches;
208
209    use super::*;
210
211    #[track_caller]
212    fn assert_a_10_c_blam(values: &dyn Values) {
213        assert_matches!(get_value::<u32>(values, "a"), Ok(10u32));
214        assert_matches!(get_value::<&str>(values, "c"), Ok(&"blam"));
215        assert_matches!(get_value::<u8>(values, "a"), Err(Error::ValueType));
216        assert_matches!(get_value::<u8>(values, "d"), Err(Error::ValueMissing));
217    }
218
219    #[track_caller]
220    fn assert_a_12_c_blam(values: &dyn Values) {
221        assert_matches!(get_value::<u32>(values, "a"), Ok(12u32));
222        assert_matches!(get_value::<&str>(values, "c"), Ok(&"blam"));
223        assert_matches!(get_value::<u8>(values, "a"), Err(Error::ValueType));
224        assert_matches!(get_value::<u8>(values, "d"), Err(Error::ValueMissing));
225    }
226
227    #[cfg(feature = "std")]
228    #[test]
229    fn values_on_hashmap() {
230        use alloc::boxed::Box;
231        use alloc::string::String;
232        use std::collections::HashMap;
233
234        let mut values: HashMap<String, Box<dyn Any>> = HashMap::new();
235        values.insert("a".into(), Box::new(12u32));
236        values.insert("c".into(), Box::new("blam"));
237        assert_a_12_c_blam(&values);
238
239        let mut values: HashMap<&str, Box<dyn Any>> = HashMap::new();
240        values.insert("a", Box::new(10u32));
241        assert_matches!(get_value::<u32>(&values, "a"), Ok(&10u32));
242    }
243
244    #[cfg(feature = "alloc")]
245    #[test]
246    fn values_on_btreemap() {
247        use alloc::boxed::Box;
248        use alloc::collections::BTreeMap;
249        use alloc::string::String;
250
251        let mut values: BTreeMap<String, Box<dyn Any>> = BTreeMap::new();
252        values.insert("a".into(), Box::new(12u32));
253        values.insert("c".into(), Box::new("blam"));
254        assert_a_12_c_blam(&values);
255
256        let mut values: BTreeMap<&str, Box<dyn Any>> = BTreeMap::new();
257        values.insert("a", Box::new(10u32));
258        assert_matches!(get_value::<u32>(&values, "a"), Ok(&10u32));
259    }
260
261    #[test]
262    fn values_on_slice() {
263        let slice: &[(&str, &dyn Any)] = &[("a", &12u32), ("c", &"blam")];
264        assert_a_12_c_blam(&slice);
265    }
266
267    #[cfg(feature = "alloc")]
268    #[test]
269    fn values_vec() {
270        let vec: alloc::vec::Vec<(&str, &dyn Any)> = alloc::vec![("a", &12u32), ("c", &"blam")];
271        assert_a_12_c_blam(&vec);
272    }
273
274    #[cfg(feature = "alloc")]
275    #[test]
276    fn values_deque() {
277        let mut deque = alloc::collections::VecDeque::<(&str, &dyn Any)>::new();
278        deque.push_back(("a", &12u32));
279        deque.push_back(("c", &"blam"));
280        assert_a_12_c_blam(&deque);
281        deque.pop_front();
282        deque.push_back(("a", &10u32));
283        assert_a_10_c_blam(&deque);
284    }
285
286    #[cfg(feature = "alloc")]
287    #[test]
288    fn values_list() {
289        let mut list = alloc::collections::LinkedList::<(&str, &dyn Any)>::new();
290        list.push_back(("a", &12u32));
291        list.push_back(("c", &"blam"));
292        assert_a_12_c_blam(&list);
293        list.pop_front();
294        list.push_back(("a", &10u32));
295        assert_a_10_c_blam(&list);
296    }
297
298    #[test]
299    fn values_on_tuple() {
300        let tuple: (&str, &dyn Any) = ("a", &10u32);
301
302        assert_matches!(get_value::<u32>(&tuple, "a"), Ok(10u32));
303        assert_matches!(get_value::<i32>(&tuple, "a"), Err(Error::ValueType));
304        assert_matches!(get_value::<i32>(&tuple, "b"), Err(Error::ValueMissing));
305    }
306}