Skip to main content

fakecloud_stepfunctions/
jsonpath.rs

1//! JSONPath evaluation for Amazon States Language paths.
2//!
3//! Step Functions evaluates `InputPath`, `OutputPath`, `ItemsPath`, the `.$`
4//! fields of `Parameters` / `ResultSelector` / `ItemSelector`, Choice
5//! `Variable`s and intrinsic arguments with Jayway JsonPath. This module
6//! implements the same syntax and the same result shape:
7//!
8//! - dot and bracket child access: `$.a.b`, `$['a']`, `$["a"]`, `$['a','b']`
9//! - array indexes, including negative ones and unions: `[0]`, `[-1]`, `[0,2]`
10//! - wildcards `.*` / `[*]`, slices `[1:3]`, `[:2]`, `[-2:]`, deep scan `..x`
11//! - filters `[?(@.price < 10 && @.tag == 'x')]`, `[?(@.isbn)]`
12//!
13//! A *definite* path (only single names and single indexes) yields one value
14//! and fails when nothing is found, which Step Functions surfaces as a
15//! `States.Runtime` error. Any other path is *indefinite* and yields a JSON
16//! array of every match (possibly empty).
17
18use serde_json::{Number, Value};
19
20#[derive(Debug, Clone, PartialEq)]
21enum Segment {
22    /// One or more property names (`.a`, `['a']`, `['a','b']`).
23    Names(Vec<String>),
24    /// One or more indexes (`[0]`, `[-1]`, `[0,2]`).
25    Indexes(Vec<i64>),
26    Wildcard,
27    Slice(Option<i64>, Option<i64>),
28    /// `..name`, `..*` or `..[...]`: the inner segment applied at every depth.
29    DeepScan(Box<Segment>),
30    Filter(FilterExpr),
31}
32
33#[derive(Debug, Clone, PartialEq)]
34enum FilterExpr {
35    Or(Box<FilterExpr>, Box<FilterExpr>),
36    And(Box<FilterExpr>, Box<FilterExpr>),
37    Not(Box<FilterExpr>),
38    Exists(Operand),
39    Compare(Operand, CmpOp, Operand),
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
43enum CmpOp {
44    Eq,
45    Ne,
46    Lt,
47    Le,
48    Gt,
49    Ge,
50}
51
52#[derive(Debug, Clone, PartialEq)]
53enum Operand {
54    /// `@...` relative to the current item.
55    Current(Vec<Segment>),
56    /// `$...` relative to the document root.
57    Root(Vec<Segment>),
58    Literal(Value),
59}
60
61/// A parsed JSONPath.
62#[derive(Debug, Clone, PartialEq)]
63pub struct JsonPath {
64    segments: Vec<Segment>,
65}
66
67/// One step of a reference path.
68#[derive(Debug, Clone, PartialEq)]
69pub enum Step {
70    Name(String),
71    Index(i64),
72}
73
74/// Why a path could not be evaluated.
75#[derive(Debug, Clone, PartialEq)]
76pub enum PathError {
77    /// Syntactically invalid path.
78    Invalid(String),
79    /// A definite path matched nothing. Carries the normalized path
80    /// (`$['a'][0]`) as Jayway reports it.
81    NoResults(String),
82    /// A definite path asked an array for a property: the property (`['b']`)
83    /// and the normalized path to the array (`$['a']`).
84    MissingProperty { property: String, parent: String },
85}
86
87impl JsonPath {
88    /// Parse a path that must start with `$`.
89    pub fn parse(path: &str) -> Result<Self, PathError> {
90        let chars: Vec<char> = path.trim().chars().collect();
91        if chars.first() != Some(&'$') {
92            return Err(PathError::Invalid(format!(
93                "Path must start with '$': {path}"
94            )));
95        }
96        let mut p = Parser { chars, pos: 1 };
97        let segments = p.segments(false)?;
98        if p.pos != p.chars.len() {
99            return Err(PathError::Invalid(format!(
100                "Unexpected character at position {} in path {path}",
101                p.pos
102            )));
103        }
104        Ok(Self { segments })
105    }
106
107    /// True when the path can yield at most one value.
108    pub fn is_definite(&self) -> bool {
109        self.segments.iter().all(|s| match s {
110            Segment::Names(n) => n.len() == 1,
111            Segment::Indexes(i) => i.len() == 1,
112            _ => false,
113        })
114    }
115
116    /// For a definite (reference) path, the chain of property names and
117    /// indexes it walks. `None` for an indefinite path.
118    pub fn reference_steps(&self) -> Option<Vec<Step>> {
119        if !self.is_definite() {
120            return None;
121        }
122        Some(
123            self.segments
124                .iter()
125                .map(|s| match s {
126                    Segment::Names(n) => Step::Name(n[0].clone()),
127                    Segment::Indexes(i) => Step::Index(i[0]),
128                    _ => unreachable!("definite paths only hold names and indexes"),
129                })
130                .collect(),
131        )
132    }
133
134    /// Why a definite path matched nothing: the first array it asked for a
135    /// property, as Jayway reports it, or else no results.
136    fn definite_miss(&self, root: &Value) -> PathError {
137        let mut current = root;
138        let mut parent = String::from("$");
139        for segment in &self.segments {
140            let next = match segment {
141                // As recorded, a property an object lacks is no results
142                // (`$.missing` on `{}`: `No results for path: $['missing']`),
143                // while an array has no properties at all (`$.missing` on
144                // `[{}]`: `Property ['missing'] not found in path $`).
145                Segment::Names(names) => match current {
146                    Value::Object(object) => match object.get(&names[0]) {
147                        Some(next) => next,
148                        None => break,
149                    },
150                    Value::Array(_) => {
151                        let mut property = String::new();
152                        render_segment(segment, &mut property);
153                        return PathError::MissingProperty { property, parent };
154                    }
155                    _ => break,
156                },
157                Segment::Indexes(indexes) => {
158                    let Value::Array(items) = current else {
159                        break;
160                    };
161                    let index = indexes[0];
162                    let at = if index < 0 {
163                        items.len() as i64 + index
164                    } else {
165                        index
166                    };
167                    match usize::try_from(at).ok().and_then(|at| items.get(at)) {
168                        Some(next) => next,
169                        None => break,
170                    }
171                }
172                _ => break,
173            };
174            render_segment(segment, &mut parent);
175            current = next;
176        }
177        PathError::NoResults(self.normalized())
178    }
179
180    /// Jayway-style normalized rendering, used in error messages.
181    pub fn normalized(&self) -> String {
182        let mut out = String::from("$");
183        for s in &self.segments {
184            render_segment(s, &mut out);
185        }
186        out
187    }
188
189    /// Evaluate against `root`. Definite paths return the single match or
190    /// [`PathError::NoResults`]; indefinite paths return an array of matches.
191    pub fn evaluate(&self, root: &Value) -> Result<Value, PathError> {
192        let results = select(&self.segments, root, root);
193        if self.is_definite() {
194            results
195                .into_iter()
196                .next()
197                .cloned()
198                .ok_or_else(|| self.definite_miss(root))
199        } else {
200            Ok(Value::Array(results.into_iter().cloned().collect()))
201        }
202    }
203}
204
205/// Parse and evaluate in one step.
206pub fn evaluate(root: &Value, path: &str) -> Result<Value, PathError> {
207    JsonPath::parse(path)?.evaluate(root)
208}
209
210fn render_segment(s: &Segment, out: &mut String) {
211    match s {
212        Segment::Names(names) => {
213            let inner: Vec<String> = names.iter().map(|n| format!("'{n}'")).collect();
214            out.push('[');
215            out.push_str(&inner.join(","));
216            out.push(']');
217        }
218        Segment::Indexes(idx) => {
219            let inner: Vec<String> = idx.iter().map(|i| i.to_string()).collect();
220            out.push('[');
221            out.push_str(&inner.join(","));
222            out.push(']');
223        }
224        Segment::Wildcard => out.push_str("[*]"),
225        Segment::Slice(a, b) => {
226            out.push('[');
227            if let Some(a) = a {
228                out.push_str(&a.to_string());
229            }
230            out.push(':');
231            if let Some(b) = b {
232                out.push_str(&b.to_string());
233            }
234            out.push(']');
235        }
236        Segment::DeepScan(inner) => {
237            out.push_str("..");
238            render_segment(inner, out);
239        }
240        Segment::Filter(_) => out.push_str("[?]"),
241    }
242}
243
244fn select<'a>(segments: &[Segment], node: &'a Value, root: &'a Value) -> Vec<&'a Value> {
245    let mut current = vec![node];
246    for seg in segments {
247        let mut next = Vec::new();
248        for v in current {
249            apply_segment(seg, v, root, &mut next);
250        }
251        current = next;
252    }
253    current
254}
255
256fn apply_segment<'a>(seg: &Segment, v: &'a Value, root: &'a Value, out: &mut Vec<&'a Value>) {
257    match seg {
258        Segment::Names(names) => {
259            if let Value::Object(o) = v {
260                for n in names {
261                    if let Some(child) = o.get(n) {
262                        out.push(child);
263                    }
264                }
265            }
266        }
267        Segment::Indexes(idx) => {
268            if let Value::Array(a) = v {
269                for &i in idx {
270                    if let Some(child) = resolve_index(a, i) {
271                        out.push(child);
272                    }
273                }
274            }
275        }
276        Segment::Wildcard => match v {
277            Value::Array(a) => out.extend(a.iter()),
278            Value::Object(o) => out.extend(o.values()),
279            _ => {}
280        },
281        Segment::Slice(start, end) => {
282            if let Value::Array(a) = v {
283                let len = a.len() as i64;
284                let norm = |x: i64| if x < 0 { (len + x).max(0) } else { x.min(len) };
285                let s = start.map(norm).unwrap_or(0);
286                let e = end.map(norm).unwrap_or(len);
287                if s < e {
288                    out.extend(a[s as usize..e as usize].iter());
289                }
290            }
291        }
292        Segment::DeepScan(inner) => deep_scan(inner, v, root, out),
293        Segment::Filter(expr) => {
294            let items: Vec<&Value> = match v {
295                Value::Array(a) => a.iter().collect(),
296                Value::Object(_) => vec![v],
297                _ => Vec::new(),
298            };
299            for item in items {
300                if eval_filter(expr, item, root) {
301                    out.push(item);
302                }
303            }
304        }
305    }
306}
307
308fn deep_scan<'a>(inner: &Segment, v: &'a Value, root: &'a Value, out: &mut Vec<&'a Value>) {
309    apply_segment(inner, v, root, out);
310    match v {
311        Value::Array(a) => a.iter().for_each(|c| deep_scan(inner, c, root, out)),
312        Value::Object(o) => o.values().for_each(|c| deep_scan(inner, c, root, out)),
313        _ => {}
314    }
315}
316
317fn resolve_index(a: &[Value], i: i64) -> Option<&Value> {
318    let idx = if i < 0 { a.len() as i64 + i } else { i };
319    if idx < 0 {
320        None
321    } else {
322        a.get(idx as usize)
323    }
324}
325
326fn operand_value<'a>(op: &'a Operand, item: &'a Value, root: &'a Value) -> Option<Value> {
327    match op {
328        Operand::Literal(v) => Some(v.clone()),
329        Operand::Current(segs) => select(segs, item, root).first().map(|v| (*v).clone()),
330        Operand::Root(segs) => select(segs, root, root).first().map(|v| (*v).clone()),
331    }
332}
333
334fn eval_filter(expr: &FilterExpr, item: &Value, root: &Value) -> bool {
335    match expr {
336        FilterExpr::Or(a, b) => eval_filter(a, item, root) || eval_filter(b, item, root),
337        FilterExpr::And(a, b) => eval_filter(a, item, root) && eval_filter(b, item, root),
338        FilterExpr::Not(a) => !eval_filter(a, item, root),
339        FilterExpr::Exists(op) => operand_value(op, item, root).is_some(),
340        FilterExpr::Compare(l, op, r) => {
341            let (Some(l), Some(r)) = (operand_value(l, item, root), operand_value(r, item, root))
342            else {
343                return false;
344            };
345            compare(&l, *op, &r)
346        }
347    }
348}
349
350fn compare(l: &Value, op: CmpOp, r: &Value) -> bool {
351    use std::cmp::Ordering;
352    let ord: Option<Ordering> = match (l, r) {
353        (Value::Number(a), Value::Number(b)) => a
354            .as_f64()
355            .zip(b.as_f64())
356            .and_then(|(a, b)| a.partial_cmp(&b)),
357        (Value::String(a), Value::String(b)) => Some(a.cmp(b)),
358        _ => None,
359    };
360    match op {
361        CmpOp::Eq => ord.map(|o| o == Ordering::Equal).unwrap_or(l == r),
362        CmpOp::Ne => ord.map(|o| o != Ordering::Equal).unwrap_or(l != r),
363        CmpOp::Lt => ord == Some(Ordering::Less),
364        CmpOp::Le => matches!(ord, Some(Ordering::Less | Ordering::Equal)),
365        CmpOp::Gt => ord == Some(Ordering::Greater),
366        CmpOp::Ge => matches!(ord, Some(Ordering::Greater | Ordering::Equal)),
367    }
368}
369
370struct Parser {
371    chars: Vec<char>,
372    pos: usize,
373}
374
375impl Parser {
376    fn peek(&self) -> Option<char> {
377        self.chars.get(self.pos).copied()
378    }
379
380    fn peek_at(&self, off: usize) -> Option<char> {
381        self.chars.get(self.pos + off).copied()
382    }
383
384    fn err<T>(&self, msg: &str) -> Result<T, PathError> {
385        Err(PathError::Invalid(format!(
386            "{msg} at position {} in path {}",
387            self.pos,
388            self.chars.iter().collect::<String>()
389        )))
390    }
391
392    fn skip_ws(&mut self) {
393        while self.peek().is_some_and(char::is_whitespace) {
394            self.pos += 1;
395        }
396    }
397
398    /// Parse segments until the end of input, or (inside a filter) until a
399    /// character that cannot continue a path.
400    fn segments(&mut self, in_filter: bool) -> Result<Vec<Segment>, PathError> {
401        let mut out = Vec::new();
402        loop {
403            match self.peek() {
404                Some('.') if self.peek_at(1) == Some('.') => {
405                    self.pos += 2;
406                    let inner = match self.peek() {
407                        Some('[') => self.bracket()?,
408                        Some('*') => {
409                            self.pos += 1;
410                            Segment::Wildcard
411                        }
412                        _ => Segment::Names(vec![self.dot_name()?]),
413                    };
414                    out.push(Segment::DeepScan(Box::new(inner)));
415                }
416                Some('.') => {
417                    self.pos += 1;
418                    if self.peek() == Some('*') {
419                        self.pos += 1;
420                        out.push(Segment::Wildcard);
421                    } else {
422                        out.push(Segment::Names(vec![self.dot_name()?]));
423                    }
424                }
425                Some('[') => {
426                    let seg = self.bracket()?;
427                    out.push(seg);
428                }
429                None => break,
430                Some(_) if in_filter => break,
431                Some(_) => return self.err("Unexpected character"),
432            }
433        }
434        Ok(out)
435    }
436
437    fn dot_name(&mut self) -> Result<String, PathError> {
438        let start = self.pos;
439        while let Some(c) = self.peek() {
440            if c == '.' || c == '[' || c.is_whitespace() || "]()=!<>&|,'\"".contains(c) {
441                break;
442            }
443            self.pos += 1;
444        }
445        if self.pos == start {
446            return self.err("Expected a property name");
447        }
448        Ok(self.chars[start..self.pos].iter().collect())
449    }
450
451    fn quoted(&mut self) -> Result<String, PathError> {
452        let quote = self.peek().unwrap_or('\'');
453        self.pos += 1;
454        let mut s = String::new();
455        loop {
456            match self.peek() {
457                None => return self.err("Unterminated quoted string"),
458                Some('\\') => {
459                    self.pos += 1;
460                    match self.peek() {
461                        Some(c) => {
462                            s.push(c);
463                            self.pos += 1;
464                        }
465                        None => return self.err("Unterminated escape"),
466                    }
467                }
468                Some(c) if c == quote => {
469                    self.pos += 1;
470                    return Ok(s);
471                }
472                Some(c) => {
473                    s.push(c);
474                    self.pos += 1;
475                }
476            }
477        }
478    }
479
480    fn integer(&mut self) -> Option<i64> {
481        let start = self.pos;
482        if self.peek() == Some('-') {
483            self.pos += 1;
484        }
485        while self.peek().is_some_and(|c| c.is_ascii_digit()) {
486            self.pos += 1;
487        }
488        let s: String = self.chars[start..self.pos].iter().collect();
489        match s.parse() {
490            Ok(n) => Some(n),
491            Err(_) => {
492                self.pos = start;
493                None
494            }
495        }
496    }
497
498    fn bracket(&mut self) -> Result<Segment, PathError> {
499        self.pos += 1; // '['
500        self.skip_ws();
501        let seg = match self.peek() {
502            Some('*') => {
503                self.pos += 1;
504                Segment::Wildcard
505            }
506            Some('?') => {
507                self.pos += 1;
508                self.skip_ws();
509                if self.peek() != Some('(') {
510                    return self.err("Expected '(' after '?'");
511                }
512                self.pos += 1;
513                let expr = self.filter_or()?;
514                self.skip_ws();
515                if self.peek() != Some(')') {
516                    return self.err("Expected ')' closing filter");
517                }
518                self.pos += 1;
519                Segment::Filter(expr)
520            }
521            Some('\'') | Some('"') => {
522                let mut names = vec![self.quoted()?];
523                loop {
524                    self.skip_ws();
525                    if self.peek() != Some(',') {
526                        break;
527                    }
528                    self.pos += 1;
529                    self.skip_ws();
530                    if !matches!(self.peek(), Some('\'') | Some('"')) {
531                        return self.err("Expected a quoted property name");
532                    }
533                    names.push(self.quoted()?);
534                }
535                Segment::Names(names)
536            }
537            Some(':') => {
538                self.pos += 1;
539                self.skip_ws();
540                let end = self.integer();
541                Segment::Slice(None, end)
542            }
543            Some(c) if c == '-' || c.is_ascii_digit() => {
544                let first = match self.integer() {
545                    Some(n) => n,
546                    None => return self.err("Invalid array index"),
547                };
548                self.skip_ws();
549                if self.peek() == Some(':') {
550                    self.pos += 1;
551                    self.skip_ws();
552                    let end = self.integer();
553                    Segment::Slice(Some(first), end)
554                } else {
555                    let mut idx = vec![first];
556                    loop {
557                        self.skip_ws();
558                        if self.peek() != Some(',') {
559                            break;
560                        }
561                        self.pos += 1;
562                        self.skip_ws();
563                        match self.integer() {
564                            Some(n) => idx.push(n),
565                            None => return self.err("Invalid array index"),
566                        }
567                    }
568                    Segment::Indexes(idx)
569                }
570            }
571            _ => return self.err("Invalid bracket expression"),
572        };
573        self.skip_ws();
574        if self.peek() != Some(']') {
575            return self.err("Expected ']'");
576        }
577        self.pos += 1;
578        Ok(seg)
579    }
580
581    fn filter_or(&mut self) -> Result<FilterExpr, PathError> {
582        let mut left = self.filter_and()?;
583        loop {
584            self.skip_ws();
585            if self.peek() == Some('|') && self.peek_at(1) == Some('|') {
586                self.pos += 2;
587                let right = self.filter_and()?;
588                left = FilterExpr::Or(Box::new(left), Box::new(right));
589            } else {
590                return Ok(left);
591            }
592        }
593    }
594
595    fn filter_and(&mut self) -> Result<FilterExpr, PathError> {
596        let mut left = self.filter_unary()?;
597        loop {
598            self.skip_ws();
599            if self.peek() == Some('&') && self.peek_at(1) == Some('&') {
600                self.pos += 2;
601                let right = self.filter_unary()?;
602                left = FilterExpr::And(Box::new(left), Box::new(right));
603            } else {
604                return Ok(left);
605            }
606        }
607    }
608
609    fn filter_unary(&mut self) -> Result<FilterExpr, PathError> {
610        self.skip_ws();
611        if self.peek() == Some('!') && self.peek_at(1) != Some('=') {
612            self.pos += 1;
613            return Ok(FilterExpr::Not(Box::new(self.filter_unary()?)));
614        }
615        if self.peek() == Some('(') {
616            self.pos += 1;
617            let inner = self.filter_or()?;
618            self.skip_ws();
619            if self.peek() != Some(')') {
620                return self.err("Expected ')'");
621            }
622            self.pos += 1;
623            return Ok(inner);
624        }
625        let left = self.operand()?;
626        self.skip_ws();
627        let op = match (self.peek(), self.peek_at(1)) {
628            (Some('='), Some('=')) => Some((CmpOp::Eq, 2)),
629            (Some('!'), Some('=')) => Some((CmpOp::Ne, 2)),
630            (Some('<'), Some('=')) => Some((CmpOp::Le, 2)),
631            (Some('>'), Some('=')) => Some((CmpOp::Ge, 2)),
632            (Some('<'), _) => Some((CmpOp::Lt, 1)),
633            (Some('>'), _) => Some((CmpOp::Gt, 1)),
634            _ => None,
635        };
636        match op {
637            Some((op, len)) => {
638                self.pos += len;
639                let right = self.operand()?;
640                Ok(FilterExpr::Compare(left, op, right))
641            }
642            None => match left {
643                Operand::Literal(_) => self.err("A filter needs a path or a comparison"),
644                path => Ok(FilterExpr::Exists(path)),
645            },
646        }
647    }
648
649    fn operand(&mut self) -> Result<Operand, PathError> {
650        self.skip_ws();
651        match self.peek() {
652            Some('@') => {
653                self.pos += 1;
654                Ok(Operand::Current(self.segments(true)?))
655            }
656            Some('$') => {
657                self.pos += 1;
658                Ok(Operand::Root(self.segments(true)?))
659            }
660            Some('\'') | Some('"') => Ok(Operand::Literal(Value::String(self.quoted()?))),
661            Some(_) => {
662                let start = self.pos;
663                while self
664                    .peek()
665                    .is_some_and(|c| c.is_ascii_alphanumeric() || "+-.".contains(c))
666                {
667                    self.pos += 1;
668                }
669                let word: String = self.chars[start..self.pos].iter().collect();
670                let lit = match word.as_str() {
671                    "true" => Value::Bool(true),
672                    "false" => Value::Bool(false),
673                    "null" => Value::Null,
674                    w => {
675                        if let Ok(i) = w.parse::<i64>() {
676                            Value::Number(Number::from(i))
677                        } else if let Some(n) = w.parse::<f64>().ok().and_then(Number::from_f64) {
678                            Value::Number(n)
679                        } else {
680                            return self.err("Invalid filter literal");
681                        }
682                    }
683                };
684                Ok(Operand::Literal(lit))
685            }
686            None => self.err("Unexpected end of filter"),
687        }
688    }
689}
690
691#[cfg(test)]
692mod tests {
693    use super::*;
694    use serde_json::json;
695
696    fn doc() -> Value {
697        json!({
698            "store": {
699                "book": [
700                    {"category": "ref", "author": "Nigel", "price": 8.95},
701                    {"category": "fiction", "author": "Evelyn", "price": 12.99, "isbn": "x"},
702                    {"category": "fiction", "author": "Herman", "price": 8.99, "isbn": "y"}
703                ],
704                "bicycle": {"color": "red", "price": 19.95}
705            },
706            "weird key": 1,
707            "n": null
708        })
709    }
710
711    #[test]
712    fn definite_paths() {
713        let d = doc();
714        assert_eq!(evaluate(&d, "$").unwrap(), d);
715        assert_eq!(evaluate(&d, "$.store.bicycle.color").unwrap(), json!("red"));
716        assert_eq!(
717            evaluate(&d, "$['store']['bicycle']['color']").unwrap(),
718            json!("red")
719        );
720        assert_eq!(evaluate(&d, "$[\"weird key\"]").unwrap(), json!(1));
721        assert_eq!(
722            evaluate(&d, "$.store.book[0].author").unwrap(),
723            json!("Nigel")
724        );
725        assert_eq!(
726            evaluate(&d, "$.store.book[-1].author").unwrap(),
727            json!("Herman")
728        );
729        // A present null is a result, not a miss.
730        assert_eq!(evaluate(&d, "$.n").unwrap(), Value::Null);
731    }
732
733    /// Recorded: an array root has no properties either.
734    #[test]
735    fn a_property_of_an_array_is_missing() {
736        assert_eq!(
737            evaluate(&json!([{}]), "$.missing").unwrap_err(),
738            PathError::MissingProperty {
739                property: "['missing']".into(),
740                parent: "$".into()
741            }
742        );
743    }
744
745    #[test]
746    fn definite_miss_is_no_results() {
747        let d = doc();
748        assert_eq!(
749            evaluate(&d, "$.store.missing").unwrap_err(),
750            PathError::NoResults("$['store']['missing']".into())
751        );
752        assert!(matches!(
753            evaluate(&d, "$.store.book[9]"),
754            Err(PathError::NoResults(_))
755        ));
756        assert!(matches!(
757            evaluate(&d, "$.store.bicycle.color.x"),
758            Err(PathError::NoResults(_))
759        ));
760    }
761
762    #[test]
763    fn indefinite_paths_return_arrays() {
764        let d = doc();
765        assert_eq!(
766            evaluate(&d, "$.store.book[*].author").unwrap(),
767            json!(["Nigel", "Evelyn", "Herman"])
768        );
769        assert_eq!(
770            evaluate(&d, "$.store.book[0,2].price").unwrap(),
771            json!([8.95, 8.99])
772        );
773        assert_eq!(
774            evaluate(&d, "$.store.book[1:].author").unwrap(),
775            json!(["Evelyn", "Herman"])
776        );
777        assert_eq!(
778            evaluate(&d, "$.store.book[:1].author").unwrap(),
779            json!(["Nigel"])
780        );
781        assert_eq!(
782            evaluate(&d, "$.store.book[-2:].author").unwrap(),
783            json!(["Evelyn", "Herman"])
784        );
785        assert_eq!(evaluate(&d, "$..color").unwrap(), json!(["red"]));
786        assert_eq!(
787            evaluate(&d, "$.store..price")
788                .unwrap()
789                .as_array()
790                .unwrap()
791                .len(),
792            4
793        );
794        // No match on an indefinite path is an empty array, not an error.
795        assert_eq!(evaluate(&d, "$.store.book[*].nope").unwrap(), json!([]));
796        assert_eq!(
797            evaluate(&d, "$['store']['book','bicycle']")
798                .unwrap()
799                .as_array()
800                .unwrap()
801                .len(),
802            2
803        );
804    }
805
806    #[test]
807    fn filters() {
808        let d = doc();
809        assert_eq!(
810            evaluate(&d, "$.store.book[?(@.price < 10)].author").unwrap(),
811            json!(["Nigel", "Herman"])
812        );
813        assert_eq!(
814            evaluate(&d, "$.store.book[?(@.isbn)].author").unwrap(),
815            json!(["Evelyn", "Herman"])
816        );
817        assert_eq!(
818            evaluate(
819                &d,
820                "$.store.book[?(@.category == 'fiction' && @.price > 10)].author"
821            )
822            .unwrap(),
823            json!(["Evelyn"])
824        );
825        assert_eq!(
826            evaluate(
827                &d,
828                "$.store.book[?(@.author == \"Nigel\" || @.price >= 12.99)].author"
829            )
830            .unwrap(),
831            json!(["Nigel", "Evelyn"])
832        );
833        assert_eq!(
834            evaluate(&d, "$.store.book[?(@.price > $.store.bicycle.price)]").unwrap(),
835            json!([])
836        );
837        assert_eq!(
838            evaluate(&d, "$.store.book[?(!(@.isbn))].author").unwrap(),
839            json!(["Nigel"])
840        );
841    }
842
843    #[test]
844    fn invalid_paths() {
845        for p in [
846            "", "foo", "$.", "$[", "$.a[", "$.x[é]", "$[]", "$.a]", "$['a",
847        ] {
848            assert!(
849                matches!(JsonPath::parse(p), Err(PathError::Invalid(_))),
850                "{p}"
851            );
852        }
853    }
854
855    #[test]
856    fn definiteness() {
857        assert!(JsonPath::parse("$.a[0]['b']").unwrap().is_definite());
858        assert!(JsonPath::parse("$.a[-1]").unwrap().is_definite());
859        assert!(!JsonPath::parse("$.a[*]").unwrap().is_definite());
860        assert!(!JsonPath::parse("$..a").unwrap().is_definite());
861        assert!(!JsonPath::parse("$.a[0,1]").unwrap().is_definite());
862        assert!(!JsonPath::parse("$.a[0:1]").unwrap().is_definite());
863    }
864}