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