1 use std::borrow::Cow;
2 use std::collections::BTreeMap;
3 
4 use serde::ser::Serialize;
5 use serde_json::value::{to_value, Map, Value};
6 
7 use crate::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)]
14 pub struct Context {
15     data: BTreeMap<String, Value>,
16 }
17 
18 impl Context {
19     /// Initializes an empty context
new() -> Self20     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 tera::Context;
30     /// let mut context = tera::Context::new();
31     /// context.insert("number_users", &42);
32     /// ```
insert<T: Serialize + ?Sized, S: Into<String>>(&mut self, key: S, val: &T)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 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     /// ```
try_insert<T: Serialize + ?Sized, S: Into<String>>( &mut self, key: S, val: &T, ) -> TeraResult<()>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 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     /// ```
extend(&mut self, mut source: Context)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.
into_json(self) -> Value84     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.
from_value(obj: Value) -> TeraResult<Self>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.
from_serialize(value: impl Serialize) -> TeraResult<Self>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.
get(&self, index: &str) -> Option<&Value>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.
contains_key(&self, index: &str) -> bool122     pub fn contains_key(&self, index: &str) -> bool {
123         self.data.contains_key(index)
124     }
125 }
126 
127 impl Default for Context {
default() -> Context128     fn default() -> Context {
129         Context::new()
130     }
131 }
132 
133 pub trait ValueRender {
render(&self) -> Cow<str>134     fn render(&self) -> Cow<str>;
135 }
136 
137 // Convert serde Value to String.
138 impl ValueRender for Value {
render(&self) -> Cow<str>139     fn render(&self) -> Cow<str> {
140         match *self {
141             Value::String(ref s) => Cow::Borrowed(s),
142             Value::Number(ref i) => Cow::Owned(i.to_string()),
143             Value::Bool(i) => Cow::Owned(i.to_string()),
144             Value::Null => Cow::Owned(String::new()),
145             Value::Array(ref a) => {
146                 let mut buf = String::new();
147                 buf.push('[');
148                 for i in a.iter() {
149                     if buf.len() > 1 {
150                         buf.push_str(", ");
151                     }
152                     buf.push_str(i.render().as_ref());
153                 }
154                 buf.push(']');
155                 Cow::Owned(buf)
156             }
157             Value::Object(_) => Cow::Borrowed("[object]"),
158         }
159     }
160 }
161 
162 pub trait ValueNumber {
to_number(&self) -> Result<f64, ()>163     fn to_number(&self) -> Result<f64, ()>;
164 }
165 // Needed for all the maths
166 // Convert everything to f64, seems like a terrible idea
167 impl ValueNumber for Value {
to_number(&self) -> Result<f64, ()>168     fn to_number(&self) -> Result<f64, ()> {
169         match *self {
170             Value::Number(ref i) => Ok(i.as_f64().unwrap()),
171             _ => Err(()),
172         }
173     }
174 }
175 
176 // From handlebars-rust
177 pub trait ValueTruthy {
is_truthy(&self) -> bool178     fn is_truthy(&self) -> bool;
179 }
180 
181 impl ValueTruthy for Value {
is_truthy(&self) -> bool182     fn is_truthy(&self) -> bool {
183         match *self {
184             Value::Number(ref i) => {
185                 if i.is_i64() {
186                     return i.as_i64().unwrap() != 0;
187                 }
188                 if i.is_u64() {
189                     return i.as_u64().unwrap() != 0;
190                 }
191                 let f = i.as_f64().unwrap();
192                 f != 0.0 && !f.is_nan()
193             }
194             Value::Bool(ref i) => *i,
195             Value::Null => false,
196             Value::String(ref i) => !i.is_empty(),
197             Value::Array(ref i) => !i.is_empty(),
198             Value::Object(ref i) => !i.is_empty(),
199         }
200     }
201 }
202 
203 /// Converts a dotted path to a json pointer one
204 #[inline]
get_json_pointer(key: &str) -> String205 pub fn get_json_pointer(key: &str) -> String {
206     ["/", &key.replace(".", "/")].join("")
207 }
208 
209 #[cfg(test)]
210 mod tests {
211     use super::*;
212 
213     use serde_json::json;
214     use std::collections::HashMap;
215 
216     #[test]
can_extend_context()217     fn can_extend_context() {
218         let mut target = Context::new();
219         target.insert("a", &1);
220         target.insert("b", &2);
221         let mut source = Context::new();
222         source.insert("b", &3);
223         source.insert("c", &4);
224         target.extend(source);
225         assert_eq!(*target.data.get("a").unwrap(), to_value(1).unwrap());
226         assert_eq!(*target.data.get("b").unwrap(), to_value(3).unwrap());
227         assert_eq!(*target.data.get("c").unwrap(), to_value(4).unwrap());
228     }
229 
230     #[test]
can_create_context_from_value()231     fn can_create_context_from_value() {
232         let obj = json!({
233             "name": "bob",
234             "age": 25
235         });
236         let context_from_value = Context::from_value(obj).unwrap();
237         let mut context = Context::new();
238         context.insert("name", "bob");
239         context.insert("age", &25);
240         assert_eq!(context_from_value, context);
241     }
242 
243     #[test]
can_create_context_from_impl_serialize()244     fn can_create_context_from_impl_serialize() {
245         let mut map = HashMap::new();
246         map.insert("name", "bob");
247         map.insert("last_name", "something");
248         let context_from_serialize = Context::from_serialize(&map).unwrap();
249         let mut context = Context::new();
250         context.insert("name", "bob");
251         context.insert("last_name", "something");
252         assert_eq!(context_from_serialize, context);
253     }
254 }
255