handlebars/
context.rs

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