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