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