handlebars/
context.rs

1use std::collections::{HashMap, VecDeque};
2
3use serde::Serialize;
4use serde_json::value::{to_value, Map, Value as Json};
5
6use crate::block::{BlockContext, BlockParamHolder};
7use crate::error::{RenderError, RenderErrorReason};
8use crate::grammar::Rule;
9use crate::json::path::*;
10use crate::json::value::ScopedJson;
11use crate::util::extend;
12
13pub type Object = HashMap<String, Json>;
14
15/// The context wrap data you render on your templates.
16///
17#[derive(Debug, Clone)]
18pub struct Context {
19    data: Json,
20}
21
22#[derive(Debug)]
23enum ResolvedPath<'a> {
24    // FIXME: change to borrowed when possible
25    // full path
26    AbsolutePath(Vec<String>),
27    // relative path and path root
28    RelativePath(Vec<String>),
29    // relative path against block param value
30    BlockParamValue(Vec<String>, &'a Json),
31    // relative path against derived value,
32    LocalValue(Vec<String>, &'a Json),
33}
34
35fn parse_json_visitor<'a>(
36    relative_path: &[PathSeg],
37    block_contexts: &'a VecDeque<BlockContext<'_>>,
38    always_for_absolute_path: bool,
39) -> ResolvedPath<'a> {
40    let mut path_context_depth: i64 = 0;
41    let mut with_block_param = None;
42    let mut from_root = false;
43
44    // peek relative_path for block param, @root and  "../../"
45    for path_seg in relative_path {
46        match path_seg {
47            PathSeg::Named(the_path) => {
48                if let Some((holder, base_path)) = get_in_block_params(block_contexts, the_path) {
49                    with_block_param = Some((holder, base_path));
50                }
51                break;
52            }
53            PathSeg::Ruled(the_rule) => match the_rule {
54                Rule::path_root => {
55                    from_root = true;
56                    break;
57                }
58                Rule::path_up => path_context_depth += 1,
59                _ => break,
60            },
61        }
62    }
63
64    let mut path_stack = Vec::with_capacity(relative_path.len() + 5);
65    match with_block_param {
66        Some((BlockParamHolder::Value(ref value), _)) => {
67            merge_json_path(&mut path_stack, &relative_path[1..]);
68            ResolvedPath::BlockParamValue(path_stack, value)
69        }
70        Some((BlockParamHolder::Path(ref paths), base_path)) => {
71            extend(&mut path_stack, base_path);
72            if !paths.is_empty() {
73                extend(&mut path_stack, paths);
74            }
75            merge_json_path(&mut path_stack, &relative_path[1..]);
76
77            ResolvedPath::AbsolutePath(path_stack)
78        }
79        None => {
80            if path_context_depth > 0 {
81                let blk = block_contexts
82                    .get(path_context_depth as usize)
83                    .or_else(|| block_contexts.front());
84
85                if let Some(base_value) = blk.and_then(|blk| blk.base_value()) {
86                    merge_json_path(&mut path_stack, relative_path);
87                    ResolvedPath::LocalValue(path_stack, base_value)
88                } else {
89                    if let Some(base_path) = blk.map(|blk| blk.base_path()) {
90                        extend(&mut path_stack, base_path);
91                    }
92                    merge_json_path(&mut path_stack, relative_path);
93                    ResolvedPath::AbsolutePath(path_stack)
94                }
95            } else if from_root {
96                merge_json_path(&mut path_stack, relative_path);
97                ResolvedPath::AbsolutePath(path_stack)
98            } else if always_for_absolute_path {
99                if let Some(base_value) = block_contexts.front().and_then(|blk| blk.base_value()) {
100                    merge_json_path(&mut path_stack, relative_path);
101                    ResolvedPath::LocalValue(path_stack, base_value)
102                } else {
103                    if let Some(base_path) = block_contexts.front().map(|blk| blk.base_path()) {
104                        extend(&mut path_stack, base_path);
105                    }
106                    merge_json_path(&mut path_stack, relative_path);
107                    ResolvedPath::AbsolutePath(path_stack)
108                }
109            } else {
110                merge_json_path(&mut path_stack, relative_path);
111                ResolvedPath::RelativePath(path_stack)
112            }
113        }
114    }
115}
116
117fn get_data<'a>(d: Option<&'a Json>, p: &str) -> Result<Option<&'a Json>, RenderError> {
118    let result = match d {
119        Some(Json::Array(l)) => p
120            .parse::<usize>()
121            .map(|idx_u| l.get(idx_u))
122            .map_err(|_| RenderErrorReason::InvalidJsonIndex(p.to_owned()))?,
123        Some(Json::Object(m)) => m.get(p),
124        Some(_) => None,
125        None => None,
126    };
127    Ok(result)
128}
129
130fn get_in_block_params<'a>(
131    block_contexts: &'a VecDeque<BlockContext<'_>>,
132    p: &str,
133) -> Option<(&'a BlockParamHolder, &'a Vec<String>)> {
134    for bc in block_contexts {
135        let v = bc.get_block_param(p);
136        if v.is_some() {
137            return v.map(|v| (v, bc.base_path()));
138        }
139    }
140
141    None
142}
143
144pub(crate) fn merge_json(base: &Json, addition: &HashMap<&str, &Json>) -> Json {
145    let mut base_map = match base {
146        Json::Object(ref m) => m.clone(),
147        _ => Map::new(),
148    };
149
150    for (k, v) in addition.iter() {
151        base_map.insert(k.to_string(), (*v).clone());
152    }
153
154    Json::Object(base_map)
155}
156
157impl Context {
158    /// Create a context with null data
159    pub fn null() -> Context {
160        Context { data: Json::Null }
161    }
162
163    /// Create a context with given data
164    pub fn wraps<T: Serialize>(e: T) -> Result<Context, RenderError> {
165        to_value(e)
166            .map_err(|e| RenderErrorReason::SerdeError(e).into())
167            .map(|d| Context { data: d })
168    }
169
170    /// Navigate the context with relative path and block scopes
171    pub(crate) fn navigate<'rc>(
172        &'rc self,
173        relative_path: &[PathSeg],
174        block_contexts: &VecDeque<BlockContext<'_>>,
175    ) -> Result<ScopedJson<'rc>, RenderError> {
176        // always use absolute at the moment until we get base_value lifetime issue fixed
177        let resolved_visitor = parse_json_visitor(relative_path, block_contexts, true);
178
179        match resolved_visitor {
180            ResolvedPath::AbsolutePath(paths) => {
181                let mut ptr = Some(self.data());
182                for p in paths.iter() {
183                    ptr = get_data(ptr, p)?;
184                }
185
186                Ok(ptr
187                    .map(|v| ScopedJson::Context(v, paths))
188                    .unwrap_or_else(|| ScopedJson::Missing))
189            }
190            ResolvedPath::RelativePath(_paths) => {
191                // relative path is disabled for now
192                unreachable!()
193                // let mut ptr = block_contexts.front().and_then(|blk| blk.base_value());
194                // for p in paths.iter() {
195                //     ptr = get_data(ptr, p)?;
196                // }
197
198                // Ok(ptr
199                //     .map(|v| ScopedJson::Context(v, paths))
200                //     .unwrap_or_else(|| ScopedJson::Missing))
201            }
202            ResolvedPath::BlockParamValue(paths, value)
203            | ResolvedPath::LocalValue(paths, value) => {
204                let mut ptr = Some(value);
205                for p in paths.iter() {
206                    ptr = get_data(ptr, p)?;
207                }
208                Ok(ptr
209                    .map(|v| ScopedJson::Derived(v.clone()))
210                    .unwrap_or_else(|| ScopedJson::Missing))
211            }
212        }
213    }
214
215    /// Return the Json data wrapped in context
216    pub fn data(&self) -> &Json {
217        &self.data
218    }
219
220    /// Return the mutable reference to Json data wrapped in context
221    pub fn data_mut(&mut self) -> &mut Json {
222        &mut self.data
223    }
224}
225
226impl From<Json> for Context {
227    fn from(data: Json) -> Context {
228        Context { data }
229    }
230}
231
232#[cfg(test)]
233mod test {
234    use crate::block::{BlockContext, BlockParams};
235    use crate::context::{self, Context};
236    use crate::error::RenderError;
237    use crate::json::path::Path;
238    use crate::json::value::{self, ScopedJson};
239    use serde_json::value::Map;
240    use std::collections::{HashMap, VecDeque};
241
242    fn navigate_from_root<'reg, 'rc>(
243        ctx: &'rc Context,
244        path: &str,
245    ) -> Result<ScopedJson<'rc>, RenderError> {
246        let relative_path = Path::parse(path).unwrap();
247        ctx.navigate(relative_path.segs().unwrap(), &VecDeque::new())
248    }
249
250    #[derive(Serialize)]
251    struct Address {
252        city: String,
253        country: String,
254    }
255
256    #[derive(Serialize)]
257    struct Person {
258        name: String,
259        age: i16,
260        addr: Address,
261        titles: Vec<String>,
262    }
263
264    #[test]
265    fn test_render() {
266        let v = "hello";
267        let ctx = Context::wraps(&v.to_string()).unwrap();
268        assert_eq!(
269            navigate_from_root(&ctx, "this").unwrap().render(),
270            v.to_string()
271        );
272    }
273
274    #[test]
275    fn test_navigation() {
276        let addr = Address {
277            city: "Beijing".to_string(),
278            country: "China".to_string(),
279        };
280
281        let person = Person {
282            name: "Ning Sun".to_string(),
283            age: 27,
284            addr,
285            titles: vec!["programmer".to_string(), "cartographer".to_string()],
286        };
287
288        let ctx = Context::wraps(&person).unwrap();
289        assert_eq!(
290            navigate_from_root(&ctx, "./addr/country").unwrap().render(),
291            "China".to_string()
292        );
293        assert_eq!(
294            navigate_from_root(&ctx, "addr.[country]").unwrap().render(),
295            "China".to_string()
296        );
297
298        let v = true;
299        let ctx2 = Context::wraps(&v).unwrap();
300        assert_eq!(
301            navigate_from_root(&ctx2, "this").unwrap().render(),
302            "true".to_string()
303        );
304
305        assert_eq!(
306            navigate_from_root(&ctx, "titles.[0]").unwrap().render(),
307            "programmer".to_string()
308        );
309
310        assert_eq!(
311            navigate_from_root(&ctx, "age").unwrap().render(),
312            "27".to_string()
313        );
314    }
315
316    #[test]
317    fn test_this() {
318        let mut map_with_this = Map::new();
319        map_with_this.insert("this".to_string(), value::to_json("hello"));
320        map_with_this.insert("age".to_string(), value::to_json(5usize));
321        let ctx1 = Context::wraps(&map_with_this).unwrap();
322
323        let mut map_without_this = Map::new();
324        map_without_this.insert("age".to_string(), value::to_json(4usize));
325        let ctx2 = Context::wraps(&map_without_this).unwrap();
326
327        assert_eq!(
328            navigate_from_root(&ctx1, "this").unwrap().render(),
329            "[object]".to_owned()
330        );
331        assert_eq!(
332            navigate_from_root(&ctx2, "age").unwrap().render(),
333            "4".to_owned()
334        );
335    }
336
337    #[test]
338    fn test_merge_json() {
339        let map = json!({ "age": 4 });
340        let s = "hello".to_owned();
341        let mut hash = HashMap::new();
342        let v = value::to_json("h1");
343        hash.insert("tag", &v);
344
345        let ctx_a1 = Context::wraps(&context::merge_json(&map, &hash)).unwrap();
346        assert_eq!(
347            navigate_from_root(&ctx_a1, "age").unwrap().render(),
348            "4".to_owned()
349        );
350        assert_eq!(
351            navigate_from_root(&ctx_a1, "tag").unwrap().render(),
352            "h1".to_owned()
353        );
354
355        let ctx_a2 = Context::wraps(&context::merge_json(&value::to_json(s), &hash)).unwrap();
356        assert_eq!(
357            navigate_from_root(&ctx_a2, "this").unwrap().render(),
358            "[object]".to_owned()
359        );
360        assert_eq!(
361            navigate_from_root(&ctx_a2, "tag").unwrap().render(),
362            "h1".to_owned()
363        );
364    }
365
366    #[test]
367    fn test_key_name_with_this() {
368        let m = json!({
369            "this_name": "the_value"
370        });
371        let ctx = Context::wraps(&m).unwrap();
372        assert_eq!(
373            navigate_from_root(&ctx, "this_name").unwrap().render(),
374            "the_value".to_string()
375        );
376    }
377
378    use serde::ser::Error as SerdeError;
379    use serde::{Serialize, Serializer};
380
381    struct UnserializableType {}
382
383    impl Serialize for UnserializableType {
384        fn serialize<S>(&self, _: S) -> Result<S::Ok, S::Error>
385        where
386            S: Serializer,
387        {
388            Err(SerdeError::custom("test"))
389        }
390    }
391
392    #[test]
393    fn test_serialize_error() {
394        let d = UnserializableType {};
395        assert!(Context::wraps(&d).is_err());
396    }
397
398    #[test]
399    fn test_root() {
400        let m = json!({
401            "a" : {
402                "b" : {
403                    "c" : {
404                        "d" : 1
405                    }
406                }
407            },
408            "b": 2
409        });
410        let ctx = Context::wraps(&m).unwrap();
411        let mut block = BlockContext::new();
412        *block.base_path_mut() = ["a".to_owned(), "b".to_owned()].to_vec();
413
414        let mut blocks = VecDeque::new();
415        blocks.push_front(block);
416
417        assert_eq!(
418            ctx.navigate(&Path::parse("@root/b").unwrap().segs().unwrap(), &blocks)
419                .unwrap()
420                .render(),
421            "2".to_string()
422        );
423    }
424
425    #[test]
426    fn test_block_params() {
427        let m = json!([{
428            "a": [1, 2]
429        }, {
430            "b": [2, 3]
431        }]);
432
433        let ctx = Context::wraps(&m).unwrap();
434        let mut block_params = BlockParams::new();
435        block_params
436            .add_path("z", ["0".to_owned(), "a".to_owned()].to_vec())
437            .unwrap();
438        block_params.add_value("t", json!("good")).unwrap();
439
440        let mut block = BlockContext::new();
441        block.set_block_params(block_params);
442
443        let mut blocks = VecDeque::new();
444        blocks.push_front(block);
445
446        assert_eq!(
447            ctx.navigate(&Path::parse("z.[1]").unwrap().segs().unwrap(), &blocks)
448                .unwrap()
449                .render(),
450            "2".to_string()
451        );
452        assert_eq!(
453            ctx.navigate(&Path::parse("t").unwrap().segs().unwrap(), &blocks)
454                .unwrap()
455                .render(),
456            "good".to_string()
457        );
458    }
459}