Skip to main content

jsonpath_rust/
parser.rs

1#![allow(clippy::empty_docs)]
2pub mod errors;
3mod macros;
4pub mod model;
5mod tests;
6
7use crate::parser::errors::JsonPathError;
8use crate::parser::model::{
9    Comparable, Comparison, Filter, FilterAtom, FnArg, JpQuery, Literal, Segment, Selector,
10    SingularQuery, SingularQuerySegment, Test, TestFunction,
11};
12
13use pest::iterators::Pair;
14use pest::Parser;
15
16#[derive(Parser)]
17#[grammar = "parser/grammar/json_path_9535.pest"]
18pub(super) struct JSPathParser;
19// const MAX_VAL: i64 = 9007199254740991; // Maximum safe integer value in JavaScript
20// const MIN_VAL: i64 = -9007199254740991; // Minimum safe integer value in JavaScript
21
22pub type Parsed<T> = Result<T, JsonPathError>;
23
24/// The maximum nesting depth of parentheses and brackets accepted in a query.
25///
26/// Parsing is recursive (both in the underlying PEG parser and in the AST
27/// construction that follows), so a query that nests grouping constructs
28/// unboundedly would recurse until the stack overflows and the process aborts.
29/// Real-world queries nest only a handful of levels, so this limit is generous
30/// while still keeping stack usage bounded.
31const MAX_NESTING_DEPTH: usize = 128;
32
33/// Parses a string into a [JsonPath].
34///
35/// # Errors
36///
37/// Returns a variant of [crate::JsonPathParserError] if the parsing operation failed.
38pub fn parse_json_path(jp_str: &str) -> Parsed<JpQuery> {
39    check_nesting_depth(jp_str)?;
40    JSPathParser::parse(Rule::main, jp_str)
41        .map_err(Box::new)?
42        .next()
43        .ok_or(JsonPathError::UnexpectedPestOutput)
44        .and_then(next_down)
45        .and_then(jp_query)
46}
47
48/// Rejects queries whose parentheses/brackets nest deeper than
49/// [`MAX_NESTING_DEPTH`] before they reach the recursive parser.
50///
51/// Grouping (`(...)` in filters and `[...]` selections/filters) is the only
52/// source of parser recursion, so scanning the raw input for the deepest run of
53/// still-open `(`/`[` bounds the recursion depth without first having to build
54/// the parse tree. Brackets and parentheses that appear inside a string literal
55/// are data rather than grouping tokens, so string contents are skipped.
56fn check_nesting_depth(jp_str: &str) -> Parsed<()> {
57    let mut depth: usize = 0;
58    let mut string_delim: Option<char> = None;
59    let mut escaped = false;
60
61    for c in jp_str.chars() {
62        if let Some(delim) = string_delim {
63            if escaped {
64                escaped = false;
65            } else if c == '\\' {
66                escaped = true;
67            } else if c == delim {
68                string_delim = None;
69            }
70            continue;
71        }
72
73        match c {
74            '\'' | '"' => string_delim = Some(c),
75            '(' | '[' => {
76                depth += 1;
77                if depth > MAX_NESTING_DEPTH {
78                    return Err(JsonPathError::MaxNestingDepthExceeded(MAX_NESTING_DEPTH));
79                }
80            }
81            ')' | ']' => depth = depth.saturating_sub(1),
82            _ => {}
83        }
84    }
85
86    Ok(())
87}
88
89pub fn jp_query(rule: Pair<Rule>) -> Parsed<JpQuery> {
90    Ok(JpQuery::new(segments(next_down(rule)?)?))
91}
92pub fn rel_query(rule: Pair<Rule>) -> Parsed<Vec<Segment>> {
93    segments(next_down(rule)?)
94}
95
96pub fn segments(rule: Pair<Rule>) -> Parsed<Vec<Segment>> {
97    let mut segments = vec![];
98    for r in rule.into_inner() {
99        segments.push(segment(next_down(r)?)?);
100    }
101    Ok(segments)
102}
103
104pub fn child_segment(rule: Pair<Rule>) -> Parsed<Segment> {
105    match rule.as_rule() {
106        Rule::wildcard_selector => Ok(Segment::Selector(Selector::Wildcard)),
107        Rule::member_name_shorthand => Ok(Segment::name(rule.as_str().trim())),
108        Rule::bracketed_selection => {
109            let mut selectors = vec![];
110            for r in rule.into_inner() {
111                selectors.push(selector(r)?);
112            }
113            if selectors.len() == 1 {
114                Ok(Segment::Selector(
115                    selectors
116                        .into_iter()
117                        .next()
118                        .ok_or(JsonPathError::empty("selector"))?,
119                ))
120            } else {
121                Ok(Segment::Selectors(selectors))
122            }
123        }
124        _ => Err(rule.into()),
125    }
126}
127
128pub fn segment(child: Pair<Rule>) -> Parsed<Segment> {
129    match child.as_rule() {
130        Rule::child_segment => {
131            let val = child.as_str().strip_prefix(".").unwrap_or_default();
132            if val != val.trim_start() {
133                Err(JsonPathError::InvalidJsonPath(format!(
134                    "Invalid child segment `{}`",
135                    child.as_str()
136                )))
137            } else {
138                child_segment(next_down(child)?)
139            }
140        }
141        Rule::descendant_segment => {
142            if child
143                .as_str()
144                .chars()
145                .nth(2)
146                .ok_or(JsonPathError::empty(child.as_str()))?
147                .is_whitespace()
148            {
149                Err(JsonPathError::InvalidJsonPath(format!(
150                    "Invalid descendant segment `{}`",
151                    child.as_str()
152                )))
153            } else {
154                Ok(Segment::Descendant(Box::new(child_segment(next_down(
155                    child,
156                )?)?)))
157            }
158        }
159        _ => Err(child.into()),
160    }
161}
162
163pub fn selector(rule: Pair<Rule>) -> Parsed<Selector> {
164    let child = next_down(rule)?;
165    match child.as_rule() {
166        Rule::name_selector => Ok(Selector::Name(unquote_and_unescape(validate_js_str(
167            child.as_str().trim(),
168        )?)?)),
169        Rule::wildcard_selector => Ok(Selector::Wildcard),
170        Rule::index_selector => Ok(Selector::Index(
171            child
172                .as_str()
173                .trim()
174                .parse::<i64>()
175                .map_err(|e| (e, "wrong integer"))?,
176        )),
177        Rule::slice_selector => {
178            let (start, end, step) = slice_selector(child)?;
179            Ok(Selector::Slice(start, end, step))
180        }
181        Rule::filter_selector => Ok(Selector::Filter(logical_expr(next_down(child)?)?)),
182        _ => Err(child.into()),
183    }
184}
185
186pub fn function_expr(rule: Pair<Rule>) -> Parsed<TestFunction> {
187    let fn_str = rule.as_str();
188    let mut elems = rule.into_inner();
189    let name = elems
190        .next()
191        .map(|e| e.as_str())
192        .ok_or(JsonPathError::empty("function expression"))?;
193
194    // Check if the function name is valid namely nothing between the name and the opening parenthesis
195    if fn_str
196        .chars()
197        .nth(name.len())
198        .map(|c| c != '(')
199        .unwrap_or_default()
200    {
201        Err(JsonPathError::InvalidJsonPath(format!(
202            "Invalid function expression `{}`",
203            fn_str
204        )))
205    } else {
206        let mut args = vec![];
207        for arg in elems {
208            let next = next_down(arg)?;
209            match next.as_rule() {
210                Rule::literal => args.push(FnArg::Literal(literal(next)?)),
211                Rule::test => args.push(FnArg::Test(Box::new(test(next)?))),
212                Rule::logical_expr => args.push(FnArg::Filter(logical_expr(next)?)),
213
214                _ => return Err(next.into()),
215            }
216        }
217
218        TestFunction::try_new(name, args)
219    }
220}
221
222pub fn test(rule: Pair<Rule>) -> Parsed<Test> {
223    let child = next_down(rule)?;
224    match child.as_rule() {
225        Rule::jp_query => Ok(Test::AbsQuery(jp_query(child)?)),
226        Rule::rel_query => Ok(Test::RelQuery(rel_query(child)?)),
227        Rule::function_expr => Ok(Test::Function(Box::new(function_expr(child)?))),
228        _ => Err(child.into()),
229    }
230}
231
232pub fn logical_expr(rule: Pair<Rule>) -> Parsed<Filter> {
233    let mut ors = vec![];
234    for r in rule.into_inner() {
235        ors.push(logical_expr_and(r)?);
236    }
237    if ors.len() == 1 {
238        Ok(ors
239            .into_iter()
240            .next()
241            .ok_or(JsonPathError::empty("logical expression"))?)
242    } else {
243        Ok(Filter::Or(ors))
244    }
245}
246
247pub fn logical_expr_and(rule: Pair<Rule>) -> Parsed<Filter> {
248    let mut ands = vec![];
249    for r in rule.into_inner() {
250        ands.push(Filter::Atom(filter_atom(r)?));
251    }
252    if ands.len() == 1 {
253        Ok(ands
254            .into_iter()
255            .next()
256            .ok_or(JsonPathError::empty("logical expression"))?)
257    } else {
258        Ok(Filter::And(ands))
259    }
260}
261
262pub fn singular_query_segments(rule: Pair<Rule>) -> Parsed<Vec<SingularQuerySegment>> {
263    let mut segments = vec![];
264    for r in rule.into_inner() {
265        match r.as_rule() {
266            Rule::name_segment => {
267                let name = next_down(r)?;
268                segments.push(SingularQuerySegment::Name(match name.as_rule() {
269                    Rule::name_selector => {
270                        unquote_and_unescape(validate_js_str(name.as_str().trim())?)?
271                    }
272                    _ => name.as_str().trim().to_string(),
273                }));
274            }
275            Rule::index_segment => {
276                segments.push(SingularQuerySegment::Index(
277                    next_down(r)?
278                        .as_str()
279                        .trim()
280                        .parse::<i64>()
281                        .map_err(|e| (e, "int"))?,
282                ));
283            }
284            _ => return Err(r.into()),
285        }
286    }
287    Ok(segments)
288}
289
290pub fn slice_selector(rule: Pair<Rule>) -> Parsed<(Option<i64>, Option<i64>, Option<i64>)> {
291    let mut start = None;
292    let mut end = None;
293    let mut step = None;
294    let get_int = |r: Pair<Rule>| r.as_str().trim().parse::<i64>().map_err(|e| (e, "int"));
295
296    for r in rule.into_inner() {
297        match r.as_rule() {
298            Rule::start => start = Some(get_int(r)?),
299            Rule::end => end = Some(get_int(r)?),
300            Rule::step => {
301                step = {
302                    if let Some(int) = r.into_inner().next() {
303                        Some(get_int(int)?)
304                    } else {
305                        None
306                    }
307                }
308            }
309
310            _ => return Err(r.into()),
311        }
312    }
313    Ok((start, end, step))
314}
315
316pub fn singular_query(rule: Pair<Rule>) -> Parsed<SingularQuery> {
317    let query = next_down(rule)?;
318    let segments = singular_query_segments(next_down(query.clone())?)?;
319    match query.as_rule() {
320        Rule::rel_singular_query => Ok(SingularQuery::Current(segments)),
321        Rule::abs_singular_query => Ok(SingularQuery::Root(segments)),
322        _ => Err(query.into()),
323    }
324}
325
326pub fn comp_expr(rule: Pair<Rule>) -> Parsed<Comparison> {
327    let mut children = rule.into_inner();
328
329    let lhs = comparable(children.next().ok_or(JsonPathError::empty("comparison"))?)?;
330    let op = children
331        .next()
332        .ok_or(JsonPathError::empty("comparison"))?
333        .as_str();
334    let rhs = comparable(children.next().ok_or(JsonPathError::empty("comparison"))?)?;
335
336    Comparison::try_new(op, lhs, rhs)
337}
338
339/// Validates a JSONPath string literal according to RFC 9535
340/// Control characters (U+0000 through U+001F and U+007F) are not allowed unescaped
341/// in string literals, whether single-quoted or double-quoted
342fn validate_js_str(s: &str) -> Parsed<&str> {
343    for (i, c) in s.chars().enumerate() {
344        if c <= '\u{001F}' {
345            return Err(JsonPathError::InvalidJsonPath(format!(
346                "Invalid control character U+{:04X} at position {} in string literal",
347                c as u32, i
348            )));
349        }
350    }
351
352    Ok(s)
353}
354
355/// Converts a string literal to the string it denotes, per RFC 9535, section 2.3.1.2:
356/// the quotes are removed and every escape sequence is replaced by the character it names.
357fn unquote_and_unescape(literal: &str) -> Parsed<String> {
358    let invalid =
359        || JsonPathError::InvalidJsonPath(format!("Invalid string literal `{}`", literal));
360
361    let body = literal
362        .strip_prefix('"')
363        .and_then(|s| s.strip_suffix('"'))
364        .or_else(|| {
365            literal
366                .strip_prefix('\'')
367                .and_then(|s| s.strip_suffix('\''))
368        })
369        .ok_or_else(invalid)?;
370
371    if !body.contains('\\') {
372        return Ok(body.to_string());
373    }
374
375    let mut unescaped = String::with_capacity(body.len());
376    let mut chars = body.chars();
377    while let Some(ch) = chars.next() {
378        if ch != '\\' {
379            unescaped.push(ch);
380            continue;
381        }
382        match chars.next().ok_or_else(invalid)? {
383            'b' => unescaped.push('\u{0008}'),
384            'f' => unescaped.push('\u{000C}'),
385            'n' => unescaped.push('\n'),
386            'r' => unescaped.push('\r'),
387            't' => unescaped.push('\t'),
388            '/' => unescaped.push('/'),
389            '\\' => unescaped.push('\\'),
390            '"' => unescaped.push('"'),
391            '\'' => unescaped.push('\''),
392            'u' => unescaped.push(unescape_unicode(&mut chars).ok_or_else(invalid)?),
393            _ => return Err(invalid()),
394        }
395    }
396    Ok(unescaped)
397}
398
399/// Reads the `XXXX` of a `\uXXXX` escape, joining a surrogate pair into one scalar value.
400fn unescape_unicode(chars: &mut std::str::Chars) -> Option<char> {
401    fn hex4(chars: &mut std::str::Chars) -> Option<u32> {
402        let mut code = 0;
403        for _ in 0..4 {
404            code = code * 16 + chars.next()?.to_digit(16)?;
405        }
406        Some(code)
407    }
408
409    match hex4(chars)? {
410        high @ 0xD800..=0xDBFF => {
411            if chars.next()? != '\\' || chars.next()? != 'u' {
412                return None;
413            }
414            match hex4(chars)? {
415                low @ 0xDC00..=0xDFFF => {
416                    char::from_u32(0x10000 + ((high - 0xD800) << 10) + (low - 0xDC00))
417                }
418                _ => None,
419            }
420        }
421        0xDC00..=0xDFFF => None,
422        code => char::from_u32(code),
423    }
424}
425
426pub fn literal(rule: Pair<Rule>) -> Parsed<Literal> {
427    fn parse_number(num: &str) -> Parsed<Literal> {
428        let num = num.trim();
429
430        if num.contains('.') || num.contains('e') || num.contains('E') {
431            Ok(Literal::Float(num.parse::<f64>().map_err(|e| (e, num))?))
432        } else {
433            Ok(Literal::Int(
434                num.trim().parse::<i64>().map_err(|e| (e, num))?,
435            ))
436        }
437    }
438
439    fn parse_string(string: &str) -> Parsed<Literal> {
440        Ok(Literal::String(unquote_and_unescape(validate_js_str(
441            string.trim(),
442        )?)?))
443    }
444
445    let first = next_down(rule)?;
446
447    match first.as_rule() {
448        Rule::string => parse_string(first.as_str()),
449        Rule::number => parse_number(first.as_str()),
450        Rule::bool => Ok(Literal::Bool(first.as_str().parse::<bool>()?)),
451        Rule::null => Ok(Literal::Null),
452
453        _ => Err(first.into()),
454    }
455}
456
457pub fn filter_atom(pair: Pair<Rule>) -> Parsed<FilterAtom> {
458    let rule = next_down(pair)?;
459
460    match rule.as_rule() {
461        Rule::paren_expr => {
462            let mut not = false;
463            let mut logic_expr = None;
464            for r in rule.into_inner() {
465                match r.as_rule() {
466                    Rule::not_op => not = true,
467                    Rule::logical_expr => logic_expr = Some(logical_expr(r)?),
468                    _ => (),
469                }
470            }
471
472            logic_expr
473                .map(|expr| FilterAtom::filter(expr, not))
474                .ok_or("Logical expression is absent".into())
475        }
476        Rule::comp_expr => Ok(FilterAtom::cmp(Box::new(comp_expr(rule)?))),
477        Rule::test_expr => {
478            let mut not = false;
479            let mut test_expr = None;
480            for r in rule.into_inner() {
481                match r.as_rule() {
482                    Rule::not_op => not = true,
483                    Rule::test => test_expr = Some(test(r)?),
484                    _ => (),
485                }
486            }
487
488            test_expr
489                .map(|expr| FilterAtom::test(expr, not))
490                .ok_or("Logical expression is absent".into())
491        }
492        _ => Err(rule.into()),
493    }
494}
495
496pub fn comparable(rule: Pair<Rule>) -> Parsed<Comparable> {
497    let rule = next_down(rule)?;
498    match rule.as_rule() {
499        Rule::literal => Ok(Comparable::Literal(literal(rule)?)),
500        Rule::singular_query => Ok(Comparable::SingularQuery(singular_query(rule)?)),
501        Rule::function_expr => {
502            let tf = function_expr(rule)?;
503            if tf.is_comparable() {
504                Ok(Comparable::Function(tf))
505            } else {
506                Err(JsonPathError::InvalidJsonPath(format!(
507                    "Function {} is not comparable",
508                    tf.to_string()
509                )))
510            }
511        }
512        _ => Err(rule.into()),
513    }
514}
515
516fn next_down(rule: Pair<Rule>) -> Parsed<Pair<Rule>> {
517    let rule_as_str = rule.as_str().to_string();
518    rule.into_inner()
519        .next()
520        .ok_or(JsonPathError::InvalidJsonPath(rule_as_str))
521}