archetect_core/vendor/tera/
context.rs

1use std::collections::BTreeMap;
2use std::io::Write;
3
4use serde::ser::Serialize;
5use serde_json::value::{to_value, Map, Value};
6
7use crate::vendor::tera::errors::{Error, Result as TeraResult};
8
9/// The struct that holds the context of a template rendering.
10///
11/// Light wrapper around a `BTreeMap` for easier insertions of Serializable
12/// values
13#[derive(Debug, Clone, PartialEq)]
14pub struct Context {
15    data: BTreeMap<String, Value>,
16}
17
18impl Context {
19    /// Initializes an empty context
20    pub fn new() -> Self {
21        Context { data: BTreeMap::new() }
22    }
23
24    /// Converts the `val` parameter to `Value` and insert it into the context.
25    ///
26    /// Panics if the serialization fails.
27    ///
28    /// ```rust
29    /// # use crate::archetect_core::vendor::tera::Context;
30    /// let mut context = Context::new();
31    /// context.insert("number_users", &42);
32    /// ```
33    pub fn insert<T: Serialize + ?Sized, S: Into<String>>(&mut self, key: S, val: &T) {
34        self.data.insert(key.into(), to_value(val).unwrap());
35    }
36
37    /// Converts the `val` parameter to `Value` and insert it into the context.
38    ///
39    /// Returns an error if the serialization fails.
40    ///
41    /// ```rust
42    /// # use crate::archetect_core::vendor::tera::Context;
43    /// # struct CannotBeSerialized;
44    /// # impl serde::Serialize for CannotBeSerialized {
45    /// #     fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
46    /// #         Err(serde::ser::Error::custom("Error"))
47    /// #     }
48    /// # }
49    /// # let user = CannotBeSerialized;
50    /// let mut context = Context::new();
51    /// // user is an instance of a struct implementing `Serialize`
52    /// if let Err(_) = context.try_insert("number_users", &user) {
53    ///     // Serialization failed
54    /// }
55    /// ```
56    pub fn try_insert<T: Serialize + ?Sized, S: Into<String>>(
57        &mut self,
58        key: S,
59        val: &T,
60    ) -> TeraResult<()> {
61        self.data.insert(key.into(), to_value(val)?);
62
63        Ok(())
64    }
65
66    /// Appends the data of the `source` parameter to `self`, overwriting existing keys.
67    /// The source context will be dropped.
68    ///
69    /// ```rust
70    /// # use crate::archetect_core::vendor::tera::Context;
71    /// let mut target = Context::new();
72    /// target.insert("a", &1);
73    /// target.insert("b", &2);
74    /// let mut source = Context::new();
75    /// source.insert("b", &3);
76    /// source.insert("d", &4);
77    /// target.extend(source);
78    /// ```
79    pub fn extend(&mut self, mut source: Context) {
80        self.data.append(&mut source.data);
81    }
82
83    /// Converts the context to a `serde_json::Value` consuming the context.
84    pub fn into_json(self) -> Value {
85        let mut m = Map::new();
86        for (key, value) in self.data {
87            m.insert(key, value);
88        }
89        Value::Object(m)
90    }
91
92    /// Takes a serde-json `Value` and convert it into a `Context` with no overhead/cloning.
93    pub fn from_value(obj: Value) -> TeraResult<Self> {
94        match obj {
95            Value::Object(m) => {
96                let mut data = BTreeMap::new();
97                for (key, value) in m {
98                    data.insert(key, value);
99                }
100                Ok(Context { data })
101            }
102            _ => Err(Error::msg(
103                "Creating a Context from a Value/Serialize requires it being a JSON object",
104            )),
105        }
106    }
107
108    /// Takes something that impl Serialize and create a context with it.
109    /// Meant to be used if you have a hashmap or a struct and don't want to insert values
110    /// one by one in the context.
111    pub fn from_serialize(value: impl Serialize) -> TeraResult<Self> {
112        let obj = to_value(value).map_err(Error::json)?;
113        Context::from_value(obj)
114    }
115
116    /// Returns the value at a given key index.
117    pub fn get(&self, index: &str) -> Option<&Value> {
118        self.data.get(index)
119    }
120
121    /// Checks if a value exists at a specific index.
122    pub fn contains_key(&self, index: &str) -> bool {
123        self.data.contains_key(index)
124    }
125}
126
127impl Default for Context {
128    fn default() -> Context {
129        Context::new()
130    }
131}
132
133pub trait ValueRender {
134    fn render(&self, write: &mut impl Write) -> std::io::Result<()>;
135}
136
137// Convert serde Value to String.
138impl ValueRender for Value {
139    fn render(&self, write: &mut impl Write) -> std::io::Result<()> {
140        match *self {
141            Value::String(ref s) => write!(write, "{}", s),
142            Value::Number(ref i) => write!(write, "{}", i),
143            Value::Bool(i) => write!(write, "{}", i),
144            Value::Null => Ok(()),
145            Value::Array(ref a) => {
146                let mut first = true;
147                write!(write, "[")?;
148                for i in a.iter() {
149                    if !first {
150                        write!(write, ", ")?;
151                    }
152                    first = false;
153                    i.render(write)?;
154                }
155                write!(write, "]")?;
156                Ok(())
157            }
158            Value::Object(_) => write!(write, "[object]"),
159        }
160    }
161}
162
163pub trait ValueNumber {
164    fn to_number(&self) -> Result<f64, ()>;
165}
166// Needed for all the maths
167// Convert everything to f64, seems like a terrible idea
168impl ValueNumber for Value {
169    fn to_number(&self) -> Result<f64, ()> {
170        match *self {
171            Value::Number(ref i) => Ok(i.as_f64().unwrap()),
172            _ => Err(()),
173        }
174    }
175}
176
177// From handlebars-rust
178pub trait ValueTruthy {
179    fn is_truthy(&self) -> bool;
180}
181
182impl ValueTruthy for Value {
183    fn is_truthy(&self) -> bool {
184        match *self {
185            Value::Number(ref i) => {
186                if i.is_i64() {
187                    return i.as_i64().unwrap() != 0;
188                }
189                if i.is_u64() {
190                    return i.as_u64().unwrap() != 0;
191                }
192                let f = i.as_f64().unwrap();
193                f != 0.0 && !f.is_nan()
194            }
195            Value::Bool(ref i) => *i,
196            Value::Null => false,
197            Value::String(ref i) => !i.is_empty(),
198            Value::Array(ref i) => !i.is_empty(),
199            Value::Object(ref i) => !i.is_empty(),
200        }
201    }
202}
203
204/// Converts a dotted path to a json pointer one
205#[inline]
206pub fn get_json_pointer(key: &str) -> String {
207    lazy_static::lazy_static! {
208        // Split the key into dot-separated segments, respecting quoted strings as single units
209        // to fix https://github.com/Keats/tera/issues/590
210        static ref JSON_POINTER_REGEX: regex::Regex = regex::Regex::new("\"[^\"]*\"|[^.]+").unwrap();
211    }
212
213    let mut segments = vec![""];
214    segments.extend(JSON_POINTER_REGEX.find_iter(key).map(|mat| mat.as_str().trim_matches('"')));
215    segments.join("/")
216}
217
218#[cfg(test)]
219mod tests {
220    use super::*;
221
222    use serde_json::json;
223    use std::collections::HashMap;
224
225    #[test]
226    fn can_extend_context() {
227        let mut target = Context::new();
228        target.insert("a", &1);
229        target.insert("b", &2);
230        let mut source = Context::new();
231        source.insert("b", &3);
232        source.insert("c", &4);
233        target.extend(source);
234        assert_eq!(*target.data.get("a").unwrap(), to_value(1).unwrap());
235        assert_eq!(*target.data.get("b").unwrap(), to_value(3).unwrap());
236        assert_eq!(*target.data.get("c").unwrap(), to_value(4).unwrap());
237    }
238
239    #[test]
240    fn can_create_context_from_value() {
241        let obj = json!({
242            "name": "bob",
243            "age": 25
244        });
245        let context_from_value = Context::from_value(obj).unwrap();
246        let mut context = Context::new();
247        context.insert("name", "bob");
248        context.insert("age", &25);
249        assert_eq!(context_from_value, context);
250    }
251
252    #[test]
253    fn can_create_context_from_impl_serialize() {
254        let mut map = HashMap::new();
255        map.insert("name", "bob");
256        map.insert("last_name", "something");
257        let context_from_serialize = Context::from_serialize(&map).unwrap();
258        let mut context = Context::new();
259        context.insert("name", "bob");
260        context.insert("last_name", "something");
261        assert_eq!(context_from_serialize, context);
262    }
263}