handlebars/json/
path.rs

1use std::iter::Peekable;
2
3use pest::iterators::Pair;
4use pest::Parser;
5
6use crate::error::RenderError;
7use crate::grammar::{HandlebarsParser, Rule};
8use crate::RenderErrorReason;
9
10#[derive(PartialEq, Eq, Clone, Debug)]
11pub enum PathSeg {
12    Named(String),
13    Ruled(Rule),
14}
15
16/// Represents the Json path in templates.
17///
18/// It can be either a local variable like `@first`, `../@index`,
19/// or a normal relative path like `a/b/c`.
20#[derive(PartialEq, Eq, Clone, Debug)]
21pub enum Path {
22    Relative((Vec<PathSeg>, String)),
23    Local((usize, String, String)),
24}
25
26impl Path {
27    pub(crate) fn new(raw: &str, segs: Vec<PathSeg>) -> Path {
28        if let Some((level, name)) = get_local_path_and_level(&segs) {
29            Path::Local((level, name, raw.to_owned()))
30        } else {
31            Path::Relative((segs, raw.to_owned()))
32        }
33    }
34
35    pub fn parse(raw: &str) -> Result<Path, RenderError> {
36        HandlebarsParser::parse(Rule::path, raw)
37            .map(|p| {
38                let parsed = p.flatten();
39                let segs = parse_json_path_from_iter(&mut parsed.peekable(), raw.len());
40                Ok(Path::new(raw, segs))
41            })
42            .map_err(|_| RenderErrorReason::InvalidJsonPath(raw.to_owned()))?
43    }
44
45    pub(crate) fn raw(&self) -> &str {
46        match self {
47            Path::Relative((_, ref raw)) => raw,
48            Path::Local((_, _, ref raw)) => raw,
49        }
50    }
51
52    pub(crate) fn current() -> Path {
53        Path::Relative((Vec::with_capacity(0), "".to_owned()))
54    }
55
56    // for test only
57    pub(crate) fn with_named_paths(name_segs: &[&str]) -> Path {
58        let segs = name_segs
59            .iter()
60            .map(|n| PathSeg::Named((*n).to_string()))
61            .collect();
62        Path::Relative((segs, name_segs.join("/")))
63    }
64
65    // for test only
66    pub(crate) fn segs(&self) -> Option<&[PathSeg]> {
67        match self {
68            Path::Relative((segs, _)) => Some(segs),
69            _ => None,
70        }
71    }
72}
73
74fn get_local_path_and_level(paths: &[PathSeg]) -> Option<(usize, String)> {
75    paths.first().and_then(|seg| {
76        if seg == &PathSeg::Ruled(Rule::path_local) {
77            let mut level = 0;
78            while paths.get(level + 1)? == &PathSeg::Ruled(Rule::path_up) {
79                level += 1;
80            }
81            if let Some(PathSeg::Named(name)) = paths.get(level + 1) {
82                Some((level, name.clone()))
83            } else {
84                None
85            }
86        } else {
87            None
88        }
89    })
90}
91
92pub(crate) fn parse_json_path_from_iter<'a, I>(it: &mut Peekable<I>, limit: usize) -> Vec<PathSeg>
93where
94    I: Iterator<Item = Pair<'a, Rule>>,
95{
96    let mut path_stack = Vec::with_capacity(5);
97    while let Some(n) = it.peek() {
98        let span = n.as_span();
99        if span.end() > limit {
100            break;
101        }
102
103        match n.as_rule() {
104            Rule::path_root => {
105                path_stack.push(PathSeg::Ruled(Rule::path_root));
106            }
107            Rule::path_local => {
108                path_stack.push(PathSeg::Ruled(Rule::path_local));
109            }
110            Rule::path_up => {
111                path_stack.push(PathSeg::Ruled(Rule::path_up));
112            }
113            Rule::path_id | Rule::path_raw_id => {
114                let name = n.as_str();
115                if name != "this" {
116                    path_stack.push(PathSeg::Named(name.to_string()));
117                }
118            }
119            _ => {}
120        }
121
122        it.next();
123    }
124
125    path_stack
126}
127
128pub(crate) fn merge_json_path(path_stack: &mut Vec<String>, relative_path: &[PathSeg]) {
129    for seg in relative_path {
130        match seg {
131            PathSeg::Named(ref s) => {
132                path_stack.push(s.to_owned());
133            }
134            PathSeg::Ruled(Rule::path_root) => {}
135            PathSeg::Ruled(Rule::path_up) => {}
136            _ => {}
137        }
138    }
139}