1use std::collections::BTreeMap;
2use std::io::Write;
3
4use serde::ser::Serialize;
5use serde_json::value::{to_value, Map, Value};
6
7use crate::errors::{Error, Result as TeraResult};
8
9#[derive(Debug, Clone, PartialEq)]
14pub struct Context {
15 data: BTreeMap<String, Value>,
16}
17
18impl Context {
19 pub fn new() -> Self {
21 Context { data: BTreeMap::new() }
22 }
23
24 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 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 pub fn extend(&mut self, mut source: Context) {
80 self.data.append(&mut source.data);
81 }
82
83 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 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 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 pub fn get(&self, index: &str) -> Option<&Value> {
118 self.data.get(index)
119 }
120
121 pub fn remove(&mut self, index: &str) -> Option<Value> {
123 self.data.remove(index)
124 }
125
126 pub fn contains_key(&self, index: &str) -> bool {
128 self.data.contains_key(index)
129 }
130}
131
132impl Default for Context {
133 fn default() -> Context {
134 Context::new()
135 }
136}
137
138pub trait ValueRender {
139 fn render(&self, write: &mut impl Write) -> std::io::Result<()>;
140}
141
142impl ValueRender for Value {
144 fn render(&self, write: &mut impl Write) -> std::io::Result<()> {
145 match *self {
146 Value::String(ref s) => write!(write, "{}", s),
147 Value::Number(ref i) => {
148 if let Some(v) = i.as_i64() {
149 write!(write, "{}", v)
150 } else if let Some(v) = i.as_u64() {
151 write!(write, "{}", v)
152 } else if let Some(v) = i.as_f64() {
153 write!(write, "{}", v)
154 } else {
155 unreachable!()
156 }
157 }
158 Value::Bool(i) => write!(write, "{}", i),
159 Value::Null => Ok(()),
160 Value::Array(ref a) => {
161 let mut first = true;
162 write!(write, "[")?;
163 for i in a.iter() {
164 if !first {
165 write!(write, ", ")?;
166 }
167 first = false;
168 i.render(write)?;
169 }
170 write!(write, "]")?;
171 Ok(())
172 }
173 Value::Object(_) => write!(write, "[object]"),
174 }
175 }
176}
177
178pub trait ValueNumber {
179 fn to_number(&self) -> Result<f64, ()>;
180}
181impl ValueNumber for Value {
184 fn to_number(&self) -> Result<f64, ()> {
185 match *self {
186 Value::Number(ref i) => Ok(i.as_f64().unwrap()),
187 _ => Err(()),
188 }
189 }
190}
191
192pub trait ValueTruthy {
194 fn is_truthy(&self) -> bool;
195}
196
197impl ValueTruthy for Value {
198 fn is_truthy(&self) -> bool {
199 match *self {
200 Value::Number(ref i) => {
201 if i.is_i64() {
202 return i.as_i64().unwrap() != 0;
203 }
204 if i.is_u64() {
205 return i.as_u64().unwrap() != 0;
206 }
207 let f = i.as_f64().unwrap();
208 f != 0.0 && !f.is_nan()
209 }
210 Value::Bool(ref i) => *i,
211 Value::Null => false,
212 Value::String(ref i) => !i.is_empty(),
213 Value::Array(ref i) => !i.is_empty(),
214 Value::Object(ref i) => !i.is_empty(),
215 }
216 }
217}
218
219#[inline]
221#[deprecated(
222 since = "1.8.0",
223 note = "`get_json_pointer` converted a dotted pointer to a json pointer, use dotted_pointer for direct lookups of values"
224)]
225pub fn get_json_pointer(key: &str) -> String {
226 lazy_static::lazy_static! {
227 static ref JSON_POINTER_REGEX: regex::Regex = regex::Regex::new(r#""[^"]*"|[^.]+"#).unwrap();
230 }
231 let mut res = String::with_capacity(key.len() + 1);
232 if key.find('"').is_some() {
233 for mat in JSON_POINTER_REGEX.find_iter(key) {
234 res.push('/');
235 res.push_str(mat.as_str().trim_matches('"'));
236 }
237 } else {
238 res.push('/');
239 res.push_str(&key.replace('.', "/"));
240 }
241 res
242}
243
244struct PointerMachina<'a> {
246 pointer: &'a str,
247 single_quoted: bool,
248 dual_quoted: bool,
249 escaped: bool,
250 last_position: usize,
251}
252
253impl PointerMachina<'_> {
254 fn new(pointer: &str) -> PointerMachina {
255 PointerMachina {
256 pointer,
257 single_quoted: false,
258 dual_quoted: false,
259 escaped: false,
260 last_position: 0,
261 }
262 }
263}
264
265impl<'a> Iterator for PointerMachina<'a> {
266 type Item = &'a str;
267
268 fn next(&mut self) -> Option<Self::Item> {
270 let forwarded = &self.pointer[self.last_position..];
271 let mut offset: usize = 0;
272 for (i, character) in forwarded.chars().enumerate() {
273 match character {
274 '"' => {
275 if !self.escaped {
276 self.dual_quoted = !self.dual_quoted;
277 if i == offset {
278 offset += 1;
279 } else {
280 let result =
281 &self.pointer[self.last_position + offset..self.last_position + i];
282
283 self.last_position += i + 1; if !result.is_empty() {
285 return Some(result);
286 }
287 }
288 }
289 }
290 '\'' => {
291 if !self.escaped {
292 self.single_quoted = !self.single_quoted;
293 if i == offset {
294 offset += 1;
295 } else {
296 let result =
297 &self.pointer[self.last_position + offset..self.last_position + i];
298 self.last_position += i + 1; if !result.is_empty() {
300 return Some(result);
301 }
302 }
303 }
304 }
305 '\\' => {
306 self.escaped = true;
307 continue;
308 }
309 '[' => {
310 if !self.single_quoted && !self.dual_quoted && !self.escaped {
311 let result =
312 &self.pointer[self.last_position + offset..self.last_position + i];
313 self.last_position += i + 1;
314 if !result.is_empty() {
315 return Some(result);
316 }
317 }
318 }
319 ']' => {
320 if !self.single_quoted && !self.dual_quoted && !self.escaped {
321 offset += 1;
322 }
323 }
324 '.' => {
325 if !self.single_quoted && !self.dual_quoted && !self.escaped {
326 if i == offset {
327 offset += 1;
328 } else {
329 let result =
330 &self.pointer[self.last_position + offset..self.last_position + i];
331 self.last_position += i + 1;
332 if !result.is_empty() {
333 return Some(result);
334 }
335 }
336 }
337 }
338 _ => (),
339 }
340 self.escaped = false;
341 }
342 if self.last_position + offset < self.pointer.len() {
343 let result = &self.pointer[self.last_position + offset..];
344 self.last_position = self.pointer.len();
345 return Some(result);
346 }
347 None
348 }
349}
350
351#[inline]
354#[must_use]
355pub fn dotted_pointer<'a>(value: &'a Value, pointer: &str) -> Option<&'a Value> {
356 if pointer.is_empty() {
357 return Some(value);
358 }
359
360 PointerMachina::new(pointer).map(|mat| mat.replace("~1", "/").replace("~0", "~")).try_fold(
361 value,
362 |target, token| match target {
363 Value::Object(map) => map.get(&token),
364 Value::Array(list) => parse_index(&token).and_then(|x| list.get(x)),
365 _ => None,
366 },
367 )
368}
369
370#[inline]
372fn parse_index(s: &str) -> Option<usize> {
373 if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) {
374 return None;
375 }
376 s.parse().ok()
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382
383 use serde_json::json;
384 use std::collections::HashMap;
385
386 #[test]
387 fn test_dotted_pointer() {
388 let data = r#"{
389 "foo": {
390 "bar": {
391 "goo": {
392 "moo": {
393 "cows": [
394 {
395 "name": "betsy",
396 "age" : 2,
397 "temperament": "calm"
398 },
399 {
400 "name": "elsie",
401 "age": 3,
402 "temperament": "calm"
403 },
404 {
405 "name": "veal",
406 "age": 1,
407 "temperament": "ornery"
408 }
409 ]
410 }
411 }
412 },
413 "http://example.com/": {
414 "goo": {
415 "moo": {
416 "cows": [
417 {
418 "name": "betsy",
419 "age" : 2,
420 "temperament": "calm"
421 },
422 {
423 "name": "elsie",
424 "age": 3,
425 "temperament": "calm"
426 },
427 {
428 "name": "veal",
429 "age": 1,
430 "temperament": "ornery"
431 }
432 ]
433 }
434 }
435 }
436 }
437 }"#;
438
439 let value = serde_json::from_str(data).unwrap();
440
441 assert_eq!(dotted_pointer(&value, ""), Some(&value));
442 assert_eq!(dotted_pointer(&value, "foo"), value.pointer("/foo"));
443 assert_eq!(dotted_pointer(&value, "foo.bar.goo"), value.pointer("/foo/bar/goo"));
444 assert_eq!(dotted_pointer(&value, "skrr"), value.pointer("/skrr"));
445 assert_eq!(
446 dotted_pointer(&value, r#"foo["bar"].baz"#),
447 value.pointer(r#"/foo["bar"]/baz"#)
448 );
449 assert_eq!(
450 dotted_pointer(&value, r#"foo["bar"].baz["qux"].blub"#),
451 value.pointer(r#"/foo["bar"]/baz["qux"]/blub"#)
452 );
453 }
454
455 #[test]
456 fn can_extend_context() {
457 let mut target = Context::new();
458 target.insert("a", &1);
459 target.insert("b", &2);
460 let mut source = Context::new();
461 source.insert("b", &3);
462 source.insert("c", &4);
463 target.extend(source);
464 assert_eq!(*target.data.get("a").unwrap(), to_value(1).unwrap());
465 assert_eq!(*target.data.get("b").unwrap(), to_value(3).unwrap());
466 assert_eq!(*target.data.get("c").unwrap(), to_value(4).unwrap());
467 }
468
469 #[test]
470 fn can_create_context_from_value() {
471 let obj = json!({
472 "name": "bob",
473 "age": 25
474 });
475 let context_from_value = Context::from_value(obj).unwrap();
476 let mut context = Context::new();
477 context.insert("name", "bob");
478 context.insert("age", &25);
479 assert_eq!(context_from_value, context);
480 }
481
482 #[test]
483 fn can_create_context_from_impl_serialize() {
484 let mut map = HashMap::new();
485 map.insert("name", "bob");
486 map.insert("last_name", "something");
487 let context_from_serialize = Context::from_serialize(&map).unwrap();
488 let mut context = Context::new();
489 context.insert("name", "bob");
490 context.insert("last_name", "something");
491 assert_eq!(context_from_serialize, context);
492 }
493
494 #[test]
495 fn can_remove_a_key() {
496 let mut context = Context::new();
497 context.insert("name", "foo");
498 context.insert("bio", "Hi, I'm foo.");
499
500 let mut expected = Context::new();
501 expected.insert("name", "foo");
502 assert_eq!(context.remove("bio"), Some(to_value("Hi, I'm foo.").unwrap()));
503 assert_eq!(context.get("bio"), None);
504 assert_eq!(context, expected);
505 }
506
507 #[test]
508 fn remove_return_none_with_unknown_index() {
509 let mut context = Context::new();
510 assert_eq!(context.remove("unknown"), None);
511 }
512}