1use std::fmt;
38use std::str::FromStr;
39
40use crate::{JsonError, JsonErrorKind};
41
42const MAX_ARRAY_INDEX: u64 = 9_007_199_254_740_991;
43
44#[derive(Debug, Clone, PartialEq, Eq, Hash)]
46pub struct JsonPath {
47 segments: Box<[Segment]>,
48}
49
50impl JsonPath {
51 #[must_use]
53 pub fn builder() -> JsonPathBuilder {
54 JsonPathBuilder::new()
55 }
56
57 #[must_use]
59 pub fn from_segments(segments: impl Into<Vec<Segment>>) -> Self {
60 Self {
61 segments: segments.into().into_iter().map(normalize_segment).collect(),
62 }
63 }
64
65 #[must_use]
67 pub fn segments(&self) -> &[Segment] {
68 &self.segments
69 }
70
71 #[must_use]
73 pub fn is_singular(&self) -> bool {
74 self.segments
75 .iter()
76 .all(|s| matches!(s, Segment::Member(_) | Segment::Index(_)))
77 }
78
79 #[must_use]
81 pub fn matches_path(&self, path: &[PathElement]) -> bool {
82 self.match_count(path) > 0
83 }
84
85 #[must_use]
91 pub fn match_count(&self, path: &[PathElement]) -> usize {
92 count_matches_from(&self.segments, 0, path, 0)
93 }
94}
95
96impl FromStr for JsonPath {
97 type Err = JsonError;
98
99 fn from_str(s: &str) -> Result<Self, Self::Err> {
100 Parser::new(s).parse()
101 }
102}
103
104impl fmt::Display for JsonPath {
105 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
106 f.write_str("$")?;
107 for segment in &self.segments {
108 write!(f, "{segment}")?;
109 }
110 Ok(())
111 }
112}
113
114#[derive(Debug, Clone, PartialEq, Eq, Hash)]
116#[non_exhaustive]
117pub enum Segment {
118 Member(Box<str>),
120 Index(usize),
122 Slice(Slice),
124 Wildcard,
126 Union(Box<[Self]>),
128 DescendantMember(Box<str>),
130 DescendantIndex(usize),
132 DescendantSlice(Slice),
134 DescendantWildcard,
136 DescendantUnion(Box<[Self]>),
138}
139
140#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
146pub struct Slice {
147 start: Option<usize>,
148 end: Option<usize>,
149 step: usize,
150}
151
152impl Slice {
153 #[must_use]
155 pub const fn new(start: Option<usize>, end: Option<usize>, step: usize) -> Self {
156 Self { start, end, step }
157 }
158
159 #[must_use]
161 pub const fn start(self) -> Option<usize> {
162 self.start
163 }
164
165 #[must_use]
167 pub const fn end(self) -> Option<usize> {
168 self.end
169 }
170
171 #[must_use]
173 pub const fn step(self) -> usize {
174 self.step
175 }
176
177 #[must_use]
179 pub fn contains(self, index: usize) -> bool {
180 if self.step == 0 {
181 return false;
182 }
183 let start = self.start.unwrap_or(0);
184 if index < start {
185 return false;
186 }
187 if self.end.is_some_and(|end| index >= end) {
188 return false;
189 }
190 (index - start).is_multiple_of(self.step)
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Eq, Hash)]
196pub enum PathElement {
197 Member(Box<str>),
199 Index(usize),
201}
202
203impl fmt::Display for PathElement {
204 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
205 match self {
206 Self::Member(name) if is_dot_member_name(name) => write!(f, ".{name}"),
207 Self::Member(name) => write_quoted_member(f, name),
208 Self::Index(index) => write!(f, "[{index}]"),
209 }
210 }
211}
212
213fn count_matches_from(
214 selector: &[Segment],
215 selector_index: usize,
216 path: &[PathElement],
217 path_index: usize,
218) -> usize {
219 if selector_index == selector.len() {
220 return usize::from(path_index == path.len());
221 }
222
223 match &selector[selector_index] {
224 Segment::Member(expected) => {
225 matches!(path.get(path_index), Some(PathElement::Member(actual)) if actual == expected)
226 .then(|| count_matches_from(selector, selector_index + 1, path, path_index + 1))
227 .unwrap_or(0)
228 }
229 Segment::Index(expected) => {
230 matches!(path.get(path_index), Some(PathElement::Index(actual)) if actual == expected)
231 .then(|| count_matches_from(selector, selector_index + 1, path, path_index + 1))
232 .unwrap_or(0)
233 }
234 Segment::Slice(slice) => {
235 matches!(path.get(path_index), Some(PathElement::Index(actual)) if slice.contains(*actual))
236 .then(|| count_matches_from(selector, selector_index + 1, path, path_index + 1))
237 .unwrap_or(0)
238 }
239 Segment::Wildcard => {
240 if path_index < path.len() {
241 count_matches_from(selector, selector_index + 1, path, path_index + 1)
242 } else {
243 0
244 }
245 }
246 Segment::Union(segments) => {
247 let Some(element) = path.get(path_index) else {
248 return 0;
249 };
250 let child_matches: usize = segments
251 .iter()
252 .map(|segment| child_match_count(segment, element))
253 .sum();
254 child_matches * count_matches_from(selector, selector_index + 1, path, path_index + 1)
255 }
256 Segment::DescendantMember(expected) => {
257 let mut count = 0;
258 for index in path_index..path.len() {
259 if matches!(
260 path.get(index),
261 Some(PathElement::Member(actual)) if actual == expected
262 ) {
263 count += count_matches_from(selector, selector_index + 1, path, index + 1);
264 }
265 }
266 count
267 }
268 Segment::DescendantIndex(expected) => {
269 let mut count = 0;
270 for index in path_index..path.len() {
271 if matches!(path.get(index), Some(PathElement::Index(actual)) if actual == expected)
272 {
273 count += count_matches_from(selector, selector_index + 1, path, index + 1);
274 }
275 }
276 count
277 }
278 Segment::DescendantSlice(slice) => {
279 let mut count = 0;
280 for index in path_index..path.len() {
281 if matches!(path.get(index), Some(PathElement::Index(actual)) if slice.contains(*actual))
282 {
283 count += count_matches_from(selector, selector_index + 1, path, index + 1);
284 }
285 }
286 count
287 }
288 Segment::DescendantWildcard => {
289 let mut count = 0;
290 for index in path_index..path.len() {
291 count += count_matches_from(selector, selector_index + 1, path, index + 1);
292 }
293 count
294 }
295 Segment::DescendantUnion(segments) => {
296 let mut count = 0;
297 for index in path_index..path.len() {
298 let Some(element) = path.get(index) else {
299 continue;
300 };
301 let child_matches: usize = segments
302 .iter()
303 .map(|segment| child_match_count(segment, element))
304 .sum();
305 if child_matches > 0 {
306 count += child_matches
307 * count_matches_from(selector, selector_index + 1, path, index + 1);
308 }
309 }
310 count
311 }
312 }
313}
314
315fn child_match_count(selector: &Segment, element: &PathElement) -> usize {
316 match (selector, element) {
317 (Segment::Member(expected), PathElement::Member(actual)) => usize::from(actual == expected),
318 (Segment::Index(expected), PathElement::Index(actual)) => usize::from(actual == expected),
319 (Segment::Slice(slice), PathElement::Index(actual)) => usize::from(slice.contains(*actual)),
320 (Segment::Wildcard, _) => 1,
321 (Segment::Union(segments), _) => segments
322 .iter()
323 .map(|segment| child_match_count(segment, element))
324 .sum(),
325 _ => 0,
326 }
327}
328
329impl fmt::Display for Segment {
330 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
331 match self {
332 Self::Member(name) if is_dot_member_name(name) => write!(f, ".{name}"),
333 Self::Member(name) => write_quoted_member(f, name),
334 Self::Index(index) => write!(f, "[{index}]"),
335 Self::Slice(slice) => write_slice(f, *slice, false),
336 Self::Wildcard => f.write_str("[*]"),
337 Self::Union(segments) => write_selector_list(f, segments, false),
338 Self::DescendantMember(name) if is_dot_member_name(name) => write!(f, "..{name}"),
339 Self::DescendantMember(name) => {
340 f.write_str("..")?;
341 write_quoted_member(f, name)
342 }
343 Self::DescendantIndex(index) => write!(f, "..[{index}]"),
344 Self::DescendantSlice(slice) => {
345 f.write_str("..")?;
346 write_slice(f, *slice, false)
347 }
348 Self::DescendantWildcard => f.write_str("..*"),
349 Self::DescendantUnion(segments) => write_selector_list(f, segments, true),
350 }
351 }
352}
353
354#[derive(Debug, Clone, Default)]
356pub struct JsonPathBuilder {
357 segments: Vec<Segment>,
358}
359
360impl JsonPathBuilder {
361 #[must_use]
363 pub fn new() -> Self {
364 Self::default()
365 }
366
367 #[must_use]
369 pub fn member(mut self, name: impl Into<Box<str>>) -> Self {
370 self.segments.push(Segment::Member(name.into()));
371 self
372 }
373
374 #[must_use]
376 pub fn index(mut self, index: usize) -> Self {
377 self.segments.push(Segment::Index(index));
378 self
379 }
380
381 #[must_use]
383 pub fn slice(mut self, start: Option<usize>, end: Option<usize>, step: usize) -> Self {
384 self.segments
385 .push(Segment::Slice(Slice::new(start, end, step)));
386 self
387 }
388
389 #[must_use]
391 pub fn wildcard(mut self) -> Self {
392 self.segments.push(Segment::Wildcard);
393 self
394 }
395
396 #[must_use]
398 pub fn union(mut self, segments: impl Into<Vec<Segment>>) -> Self {
399 self.segments
400 .push(Segment::Union(segments.into().into_boxed_slice()));
401 self
402 }
403
404 #[must_use]
406 pub fn descendant_member(mut self, name: impl Into<Box<str>>) -> Self {
407 self.segments.push(Segment::DescendantMember(name.into()));
408 self
409 }
410
411 #[must_use]
413 pub fn descendant_index(mut self, index: usize) -> Self {
414 self.segments.push(Segment::DescendantIndex(index));
415 self
416 }
417
418 #[must_use]
420 pub fn descendant_slice(
421 mut self,
422 start: Option<usize>,
423 end: Option<usize>,
424 step: usize,
425 ) -> Self {
426 self.segments
427 .push(Segment::DescendantSlice(Slice::new(start, end, step)));
428 self
429 }
430
431 #[must_use]
433 pub fn descendant_wildcard(mut self) -> Self {
434 self.segments.push(Segment::DescendantWildcard);
435 self
436 }
437
438 #[must_use]
440 pub fn descendant_union(mut self, segments: impl Into<Vec<Segment>>) -> Self {
441 self.segments
442 .push(Segment::DescendantUnion(segments.into().into_boxed_slice()));
443 self
444 }
445
446 #[must_use]
448 pub fn build(self) -> JsonPath {
449 JsonPath::from_segments(self.segments)
450 }
451}
452
453fn normalize_segment(segment: Segment) -> Segment {
454 match segment {
455 Segment::Union(segments) => {
456 let mut normalized = Vec::new();
457 for segment in segments.into_vec().into_iter().map(normalize_segment) {
458 match segment {
459 Segment::Union(nested) => normalized.extend(nested.into_vec()),
460 segment => normalized.push(segment),
461 }
462 }
463 if normalized.len() == 1 {
464 normalized.remove(0)
465 } else {
466 Segment::Union(normalized.into_boxed_slice())
467 }
468 }
469 Segment::DescendantUnion(segments) => {
470 let mut normalized = Vec::new();
471 for segment in segments.into_vec().into_iter().map(normalize_segment) {
472 match segment {
473 Segment::Union(nested) | Segment::DescendantUnion(nested) => {
474 normalized.extend(nested.into_vec());
475 }
476 segment => normalized.push(segment),
477 }
478 }
479 if normalized.len() == 1 {
480 match normalized.remove(0) {
481 Segment::Member(name) => Segment::DescendantMember(name),
482 Segment::Index(index) => Segment::DescendantIndex(index),
483 Segment::Slice(slice) => Segment::DescendantSlice(slice),
484 Segment::Wildcard => Segment::DescendantWildcard,
485 Segment::Union(segments) => Segment::DescendantUnion(segments),
486 segment @ (Segment::DescendantMember(_)
487 | Segment::DescendantIndex(_)
488 | Segment::DescendantSlice(_)
489 | Segment::DescendantWildcard
490 | Segment::DescendantUnion(_)) => segment,
491 }
492 } else {
493 Segment::DescendantUnion(normalized.into_boxed_slice())
494 }
495 }
496 segment => segment,
497 }
498}
499
500struct Parser<'a> {
501 input: &'a str,
502 pos: usize,
503}
504
505impl<'a> Parser<'a> {
506 fn new(input: &'a str) -> Self {
507 Self { input, pos: 0 }
508 }
509
510 fn parse(mut self) -> Result<JsonPath, JsonError> {
511 if self.input.is_empty() {
512 return Err(JsonError::new(JsonErrorKind::EmptyPath));
513 }
514 if !self.eat('$') {
515 return Err(JsonError::new(JsonErrorKind::MissingRoot));
516 }
517
518 let mut segments = Vec::new();
519 loop {
520 let whitespace_start = self.pos;
521 self.skip_ws();
522 if self.pos != whitespace_start && self.peek().is_none() {
523 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
524 "trailing whitespace",
525 )));
526 }
527 match self.peek() {
528 Some('.') => {
529 self.bump();
530 if self.eat('.') {
531 segments.push(self.parse_descendant()?);
532 } else {
533 segments.push(self.parse_dot_child()?);
534 }
535 }
536 Some('[') => segments.push(self.parse_bracket_child()?),
537 Some(_) => {
538 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
539 "expected selector",
540 )));
541 }
542 None => break,
543 }
544 }
545
546 Ok(JsonPath {
547 segments: segments.into_boxed_slice(),
548 })
549 }
550
551 fn parse_dot_child(&mut self) -> Result<Segment, JsonError> {
552 if self.eat('*') {
553 return Ok(Segment::Wildcard);
554 }
555 Ok(Segment::Member(self.parse_name()?))
556 }
557
558 fn parse_descendant(&mut self) -> Result<Segment, JsonError> {
559 if self.eat('*') {
560 return Ok(Segment::DescendantWildcard);
561 }
562 if self.peek() == Some('[') {
563 return descendant_from_child(self.parse_bracket_child()?);
564 }
565 Ok(Segment::DescendantMember(self.parse_name()?))
566 }
567
568 fn parse_bracket_child(&mut self) -> Result<Segment, JsonError> {
569 self.expect('[')?;
570 self.skip_ws();
571 let mut selectors = Vec::new();
572 selectors.push(self.parse_bracket_selector()?);
573 self.skip_ws();
574 while self.eat(',') {
575 self.skip_ws();
576 selectors.push(self.parse_bracket_selector()?);
577 self.skip_ws();
578 }
579 self.expect(']')?;
580
581 if selectors.len() == 1 {
582 Ok(selectors.remove(0))
583 } else {
584 Ok(Segment::Union(selectors.into_boxed_slice()))
585 }
586 }
587
588 fn parse_bracket_selector(&mut self) -> Result<Segment, JsonError> {
589 match self.peek() {
590 Some('*') => {
591 self.bump();
592 Ok(Segment::Wildcard)
593 }
594 Some('\'' | '"') => {
595 let name = self.parse_quoted()?;
596 Ok(Segment::Member(name))
597 }
598 Some('?') => Err(JsonError::new(JsonErrorKind::UnsupportedJsonPath(
599 "filter selectors",
600 ))),
601 Some(':') => self.parse_slice(None),
602 Some('-') => Err(JsonError::new(JsonErrorKind::UnsupportedJsonPath(
603 "negative array indices and slices",
604 ))),
605 Some(c) if c.is_ascii_digit() => {
606 let first = self.parse_index()?;
607 self.skip_ws();
608 if self.peek() == Some(':') {
609 self.parse_slice(Some(first))
610 } else {
611 Ok(Segment::Index(first))
612 }
613 }
614 _ => Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
615 "invalid bracket selector",
616 ))),
617 }
618 }
619
620 fn parse_slice(&mut self, start: Option<usize>) -> Result<Segment, JsonError> {
621 self.skip_ws();
622 self.expect(':')?;
623 self.skip_ws();
624 let end = self.parse_optional_slice_bound()?;
625 self.skip_ws();
626 let step = if self.eat(':') {
627 self.skip_ws();
628 self.parse_optional_slice_step()?
629 } else {
630 1
631 };
632 Ok(Segment::Slice(Slice::new(start, end, step)))
633 }
634
635 fn parse_optional_slice_bound(&mut self) -> Result<Option<usize>, JsonError> {
636 match self.peek() {
637 Some('-') => Err(JsonError::new(JsonErrorKind::UnsupportedJsonPath(
638 "negative array indices and slices",
639 ))),
640 Some(c) if c.is_ascii_digit() => self.parse_index().map(Some),
641 _ => Ok(None),
642 }
643 }
644
645 fn parse_optional_slice_step(&mut self) -> Result<usize, JsonError> {
646 match self.peek() {
647 Some('-') => Err(JsonError::new(JsonErrorKind::UnsupportedJsonPath(
648 "negative array slices",
649 ))),
650 Some(c) if c.is_ascii_digit() => self.parse_index(),
651 _ => Ok(1),
652 }
653 }
654
655 fn parse_name(&mut self) -> Result<Box<str>, JsonError> {
656 let start = self.pos;
657 match self.peek() {
658 Some(c) if is_name_start(c) => self.bump(),
659 _ => {
660 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
661 "expected member name",
662 )));
663 }
664 };
665 while self.peek().is_some_and(is_name_continue) {
666 self.bump();
667 }
668 Ok(self.input[start..self.pos].into())
669 }
670
671 fn parse_index(&mut self) -> Result<usize, JsonError> {
672 let start = self.pos;
673 while self.peek().is_some_and(|c| c.is_ascii_digit()) {
674 self.bump();
675 }
676 let raw = &self.input[start..self.pos];
677 if raw.len() > 1 && raw.starts_with('0') {
678 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
679 "array index cannot contain leading zeros",
680 )));
681 }
682 let index = raw.parse::<u64>().map_err(|_err| {
683 JsonError::new(JsonErrorKind::InvalidJsonPath("array index overflow"))
684 })?;
685 if index > MAX_ARRAY_INDEX {
686 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
687 "array index exceeds exact integer range",
688 )));
689 }
690 usize::try_from(index)
691 .map_err(|_err| JsonError::new(JsonErrorKind::InvalidJsonPath("array index overflow")))
692 }
693
694 fn parse_quoted(&mut self) -> Result<Box<str>, JsonError> {
695 let quote = self
696 .bump()
697 .ok_or_else(|| JsonError::new(JsonErrorKind::UnexpectedEnd))?;
698 let mut out = String::new();
699 loop {
700 match self.bump() {
701 Some(c) if c == quote => return Ok(out.into_boxed_str()),
702 Some('\\') => {
703 let escaped = self
704 .bump()
705 .ok_or_else(|| JsonError::new(JsonErrorKind::UnexpectedEnd))?;
706 match escaped {
707 '\'' if quote == '\'' => out.push(escaped),
708 '"' if quote == '"' => out.push(escaped),
709 '\\' | '/' => out.push(escaped),
710 'b' => out.push('\u{0008}'),
711 'f' => out.push('\u{000c}'),
712 'n' => out.push('\n'),
713 'r' => out.push('\r'),
714 't' => out.push('\t'),
715 'u' => out.push(self.parse_unicode_escape()?),
716 _ => {
717 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
718 "invalid string escape",
719 )));
720 }
721 }
722 }
723 Some('\u{0000}'..='\u{001f}') => {
724 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
725 "control character in string",
726 )));
727 }
728 Some(c) => out.push(c),
729 None => return Err(JsonError::new(JsonErrorKind::UnexpectedEnd)),
730 }
731 }
732 }
733
734 fn parse_unicode_escape(&mut self) -> Result<char, JsonError> {
735 let code = self.parse_hex_quad()?;
736 if (0xd800..=0xdbff).contains(&code) {
737 if !self.eat('\\') || !self.eat('u') {
738 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
739 "invalid unicode surrogate pair",
740 )));
741 }
742 let low = self.parse_hex_quad()?;
743 if !(0xdc00..=0xdfff).contains(&low) {
744 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
745 "invalid unicode surrogate pair",
746 )));
747 }
748 let scalar = 0x10000 + ((code - 0xd800) << 10) + (low - 0xdc00);
749 return char::from_u32(scalar).ok_or_else(|| {
750 JsonError::new(JsonErrorKind::InvalidJsonPath("invalid unicode scalar"))
751 });
752 }
753 if (0xdc00..=0xdfff).contains(&code) {
754 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
755 "invalid unicode surrogate pair",
756 )));
757 }
758 char::from_u32(code)
759 .ok_or_else(|| JsonError::new(JsonErrorKind::InvalidJsonPath("invalid unicode scalar")))
760 }
761
762 fn parse_hex_quad(&mut self) -> Result<u32, JsonError> {
763 let start = self.pos;
764 for _ in 0..4 {
765 match self.peek() {
766 Some(c) if c.is_ascii_hexdigit() => self.bump(),
767 _ => {
768 return Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
769 "invalid unicode escape",
770 )));
771 }
772 };
773 }
774 u32::from_str_radix(&self.input[start..self.pos], 16).map_err(|_err| {
775 JsonError::new(JsonErrorKind::InvalidJsonPath("invalid unicode escape"))
776 })
777 }
778
779 fn expect(&mut self, expected: char) -> Result<(), JsonError> {
780 if self.eat(expected) {
781 Ok(())
782 } else {
783 Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
784 "unexpected character",
785 )))
786 }
787 }
788
789 fn rest(&self) -> &'a str {
790 self.input.get(self.pos..).unwrap_or("")
791 }
792
793 fn peek(&self) -> Option<char> {
794 self.rest().chars().next()
795 }
796
797 fn bump(&mut self) -> Option<char> {
798 let c = self.peek()?;
799 self.pos += c.len_utf8();
800 Some(c)
801 }
802
803 fn eat(&mut self, expected: char) -> bool {
804 if self.peek() == Some(expected) {
805 self.pos += expected.len_utf8();
806 true
807 } else {
808 false
809 }
810 }
811
812 fn skip_ws(&mut self) {
813 while self
814 .peek()
815 .is_some_and(|c| matches!(c, ' ' | '\n' | '\r' | '\t'))
816 {
817 self.bump();
818 }
819 }
820}
821
822fn is_name_start(c: char) -> bool {
823 c == '_' || c.is_ascii_alphabetic() || !c.is_ascii()
824}
825
826fn is_name_continue(c: char) -> bool {
827 is_name_start(c) || c.is_ascii_digit()
828}
829
830fn is_dot_member_name(name: &str) -> bool {
831 let mut chars = name.chars();
832 chars.next().is_some_and(is_name_start) && chars.all(is_name_continue)
833}
834
835fn descendant_from_child(segment: Segment) -> Result<Segment, JsonError> {
836 match segment {
837 Segment::Member(name) => Ok(Segment::DescendantMember(name)),
838 Segment::Index(index) => Ok(Segment::DescendantIndex(index)),
839 Segment::Slice(slice) => Ok(Segment::DescendantSlice(slice)),
840 Segment::Wildcard => Ok(Segment::DescendantWildcard),
841 Segment::Union(segments) => Ok(Segment::DescendantUnion(segments)),
842 Segment::DescendantMember(_)
843 | Segment::DescendantIndex(_)
844 | Segment::DescendantSlice(_)
845 | Segment::DescendantWildcard
846 | Segment::DescendantUnion(_) => Err(JsonError::new(JsonErrorKind::InvalidJsonPath(
847 "nested descendant selector",
848 ))),
849 }
850}
851
852fn write_quoted_member(f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
853 f.write_str("[\"")?;
854 write_json_string_content(f, name)?;
855 f.write_str("\"]")
856}
857
858fn write_selector_list(
859 f: &mut fmt::Formatter<'_>,
860 segments: &[Segment],
861 descendant: bool,
862) -> fmt::Result {
863 if descendant {
864 f.write_str("..")?;
865 }
866 f.write_str("[")?;
867 for (index, segment) in segments.iter().enumerate() {
868 if index > 0 {
869 f.write_str(",")?;
870 }
871 write_bracket_selector(f, segment)?;
872 }
873 f.write_str("]")
874}
875
876fn write_bracket_selector(f: &mut fmt::Formatter<'_>, segment: &Segment) -> fmt::Result {
877 match segment {
878 Segment::Member(name) => {
879 f.write_str("\"")?;
880 write_json_string_content(f, name)?;
881 f.write_str("\"")
882 }
883 Segment::Index(index) => write!(f, "{index}"),
884 Segment::Slice(slice) => write_slice(f, *slice, true),
885 Segment::Wildcard => f.write_str("*"),
886 Segment::Union(segments) => {
887 for (index, segment) in segments.iter().enumerate() {
888 if index > 0 {
889 f.write_str(",")?;
890 }
891 write_bracket_selector(f, segment)?;
892 }
893 Ok(())
894 }
895 Segment::DescendantMember(_)
896 | Segment::DescendantIndex(_)
897 | Segment::DescendantSlice(_)
898 | Segment::DescendantWildcard
899 | Segment::DescendantUnion(_) => f.write_str("<invalid-descendant-selector>"),
900 }
901}
902
903fn write_slice(f: &mut fmt::Formatter<'_>, slice: Slice, inside_brackets: bool) -> fmt::Result {
904 if !inside_brackets {
905 f.write_str("[")?;
906 }
907 if let Some(start) = slice.start() {
908 write!(f, "{start}")?;
909 }
910 f.write_str(":")?;
911 if let Some(end) = slice.end() {
912 write!(f, "{end}")?;
913 }
914 if slice.step() != 1 {
915 write!(f, ":{}", slice.step())?;
916 }
917 if !inside_brackets {
918 f.write_str("]")?;
919 }
920 Ok(())
921}
922
923fn write_json_string_content(f: &mut fmt::Formatter<'_>, name: &str) -> fmt::Result {
924 for c in name.chars() {
925 match c {
926 '"' => f.write_str("\\\"")?,
927 '\\' => f.write_str("\\\\")?,
928 '\u{0008}' => f.write_str("\\b")?,
929 '\u{000c}' => f.write_str("\\f")?,
930 '\n' => f.write_str("\\n")?,
931 '\r' => f.write_str("\\r")?,
932 '\t' => f.write_str("\\t")?,
933 c if c.is_control() => write!(f, "\\u{:04x}", c as u32)?,
934 c => write!(f, "{c}")?,
935 }
936 }
937 Ok(())
938}
939
940#[cfg(test)]
941mod tests {
942 use super::*;
943
944 #[test]
945 fn parses_supported_selectors() {
946 let cases = [
947 (
948 "$.store.book[*].author",
949 vec![
950 Segment::Member("store".into()),
951 Segment::Member("book".into()),
952 Segment::Wildcard,
953 Segment::Member("author".into()),
954 ],
955 false,
956 ),
957 (
958 "$['weird.key'][12]",
959 vec![Segment::Member("weird.key".into()), Segment::Index(12)],
960 true,
961 ),
962 (
963 "$..author",
964 vec![Segment::DescendantMember("author".into())],
965 false,
966 ),
967 (
968 "$[0,\"name\",*]",
969 vec![Segment::Union(
970 vec![
971 Segment::Index(0),
972 Segment::Member("name".into()),
973 Segment::Wildcard,
974 ]
975 .into_boxed_slice(),
976 )],
977 false,
978 ),
979 (
980 "$[1:5:2]",
981 vec![Segment::Slice(Slice::new(Some(1), Some(5), 2))],
982 false,
983 ),
984 (
985 "$..[0,\"name\",*]",
986 vec![Segment::DescendantUnion(
987 vec![
988 Segment::Index(0),
989 Segment::Member("name".into()),
990 Segment::Wildcard,
991 ]
992 .into_boxed_slice(),
993 )],
994 false,
995 ),
996 (
997 "$..[1:4]",
998 vec![Segment::DescendantSlice(Slice::new(Some(1), Some(4), 1))],
999 false,
1000 ),
1001 ];
1002
1003 for (input, expected, singular) in cases {
1004 let path: JsonPath = input.parse().unwrap();
1005 assert_eq!(path.segments(), expected.as_slice(), "{input}");
1006 assert_eq!(path.is_singular(), singular, "{input}");
1007 }
1008 }
1009
1010 #[test]
1011 fn builder_matches_parser() {
1012 let built = JsonPath::builder()
1013 .member("store")
1014 .member("book")
1015 .wildcard()
1016 .member("author")
1017 .union([
1018 Segment::Member("name".into()),
1019 Segment::Index(0),
1020 Segment::Slice(Slice::new(None, Some(4), 2)),
1021 ])
1022 .descendant_index(1)
1023 .build();
1024 let parsed: JsonPath = "$.store.book[*].author[\"name\",0,:4:2]..[1]"
1025 .parse()
1026 .unwrap();
1027 assert_eq!(built, parsed);
1028 }
1029
1030 #[test]
1031 fn display_roundtrips_basic_paths() {
1032 for input in [
1033 "$",
1034 "$.a[0].b",
1035 "$['weird.key'][*]",
1036 "$..author",
1037 "$[0,'name',*]",
1038 "$[1:5:2]",
1039 "$[:]",
1040 "$[::3]",
1041 "$..[0,'name',*]",
1042 "$..[1:4]",
1043 "$[\"\\uD834\\uDD1E\"]",
1044 ] {
1045 let path: JsonPath = input.parse().unwrap();
1046 let reparsed: JsonPath = path.to_string().parse().unwrap();
1047 assert_eq!(path, reparsed);
1048 }
1049 }
1050
1051 #[test]
1052 fn display_formats_paths_canonically() {
1053 let cases = [
1054 (
1055 JsonPath::builder().member("alpha").build().to_string(),
1056 "$.alpha",
1057 ),
1058 (
1059 JsonPath::builder().member("weird.key").build().to_string(),
1060 "$[\"weird.key\"]",
1061 ),
1062 (
1063 JsonPath::builder()
1064 .union([
1065 Segment::Index(0),
1066 Segment::Member("name".into()),
1067 Segment::Wildcard,
1068 ])
1069 .build()
1070 .to_string(),
1071 "$[0,\"name\",*]",
1072 ),
1073 (
1074 JsonPath::builder()
1075 .descendant_member("alpha")
1076 .build()
1077 .to_string(),
1078 "$..alpha",
1079 ),
1080 (
1081 JsonPath::builder()
1082 .descendant_member("weird.key")
1083 .build()
1084 .to_string(),
1085 "$..[\"weird.key\"]",
1086 ),
1087 (
1088 JsonPath::builder()
1089 .descendant_union([
1090 Segment::Member("a".into()),
1091 Segment::Index(1),
1092 Segment::Wildcard,
1093 ])
1094 .build()
1095 .to_string(),
1096 "$..[\"a\",1,*]",
1097 ),
1098 (
1099 JsonPath::builder().member("\u{1}").build().to_string(),
1100 "$[\"\\u0001\"]",
1101 ),
1102 ];
1103
1104 for (actual, expected) in cases {
1105 assert_eq!(actual, expected);
1106 }
1107
1108 let nested_union = JsonPath::from_segments([Segment::Union(
1109 vec![
1110 Segment::Union(vec![Segment::Index(1), Segment::Index(2)].into_boxed_slice()),
1111 Segment::Index(3),
1112 ]
1113 .into_boxed_slice(),
1114 )]);
1115 assert_eq!(nested_union.to_string(), "$[1,2,3]");
1116 assert_eq!(nested_union, "$[1,2,3]".parse().unwrap());
1117
1118 let nested_descendant_union = JsonPath::from_segments([Segment::DescendantUnion(
1119 vec![
1120 Segment::Union(vec![Segment::Index(1), Segment::Index(2)].into_boxed_slice()),
1121 Segment::Index(3),
1122 ]
1123 .into_boxed_slice(),
1124 )]);
1125 assert_eq!(nested_descendant_union.to_string(), "$..[1,2,3]");
1126 assert_eq!(nested_descendant_union, "$..[1,2,3]".parse().unwrap());
1127
1128 assert_eq!(PathElement::Member("alpha".into()).to_string(), ".alpha");
1129 assert_eq!(
1130 PathElement::Member("weird.key".into()).to_string(),
1131 "[\"weird.key\"]"
1132 );
1133 assert_eq!(PathElement::Index(7).to_string(), "[7]");
1134 }
1135
1136 #[test]
1137 fn typed_builder_covers_all_segment_methods() {
1138 let path = JsonPath::builder()
1139 .index(2)
1140 .slice(Some(1), Some(3), 2)
1141 .descendant_member("x")
1142 .descendant_slice(None, Some(2), 1)
1143 .descendant_wildcard()
1144 .descendant_union([Segment::Member("a".into()), Segment::Index(1)])
1145 .build();
1146
1147 assert_eq!(path.to_string(), "$[2][1:3:2]..x..[:2]..*..[\"a\",1]");
1148 }
1149
1150 #[test]
1151 fn rejects_unsupported_rfc_features_explicitly() {
1152 let cases = [
1153 (
1154 "$[?(@.x)]",
1155 JsonErrorKind::UnsupportedJsonPath("filter selectors"),
1156 ),
1157 (
1158 "$[-1]",
1159 JsonErrorKind::UnsupportedJsonPath("negative array indices and slices"),
1160 ),
1161 (
1162 "$[:-1]",
1163 JsonErrorKind::UnsupportedJsonPath("negative array indices and slices"),
1164 ),
1165 (
1166 "$[1:-1]",
1167 JsonErrorKind::UnsupportedJsonPath("negative array indices and slices"),
1168 ),
1169 (
1170 "$[1:2:-1]",
1171 JsonErrorKind::UnsupportedJsonPath("negative array slices"),
1172 ),
1173 ];
1174
1175 for (input, expected) in cases {
1176 let err = input.parse::<JsonPath>().unwrap_err();
1177 assert_eq!(err.kind(), &expected, "{input}");
1178 }
1179 }
1180
1181 #[test]
1182 fn matches_concrete_paths() {
1183 let path: JsonPath = "$.store.book[*].author".parse().unwrap();
1184 let concrete = [
1185 PathElement::Member("store".into()),
1186 PathElement::Member("book".into()),
1187 PathElement::Index(3),
1188 PathElement::Member("author".into()),
1189 ];
1190 assert!(path.matches_path(&concrete));
1191 assert!(
1192 "$..author"
1193 .parse::<JsonPath>()
1194 .unwrap()
1195 .matches_path(&concrete)
1196 );
1197 assert!(
1198 "$.store.book[1:5:2].author"
1199 .parse::<JsonPath>()
1200 .unwrap()
1201 .matches_path(&concrete)
1202 );
1203 assert!(
1204 "$.store.book[0,3,5].author"
1205 .parse::<JsonPath>()
1206 .unwrap()
1207 .matches_path(&concrete)
1208 );
1209 assert!(
1210 "$.store.book[3].author"
1211 .parse::<JsonPath>()
1212 .unwrap()
1213 .matches_path(&concrete)
1214 );
1215 assert!(
1216 "$..[3].author"
1217 .parse::<JsonPath>()
1218 .unwrap()
1219 .matches_path(&concrete)
1220 );
1221 assert!(
1222 "$..[1:5:2].author"
1223 .parse::<JsonPath>()
1224 .unwrap()
1225 .matches_path(&concrete)
1226 );
1227 assert!(
1228 "$..[\"book\",3].author"
1229 .parse::<JsonPath>()
1230 .unwrap()
1231 .matches_path(&concrete)
1232 );
1233 assert!(
1234 "$.store..*.author"
1235 .parse::<JsonPath>()
1236 .unwrap()
1237 .matches_path(&concrete)
1238 );
1239 assert!(
1240 !"$.store.author"
1241 .parse::<JsonPath>()
1242 .unwrap()
1243 .matches_path(&concrete)
1244 );
1245 assert!(
1246 !"$.store.book[0:3].author"
1247 .parse::<JsonPath>()
1248 .unwrap()
1249 .matches_path(&concrete)
1250 );
1251 }
1252
1253 #[test]
1254 fn slice_matching_is_start_inclusive_end_exclusive() {
1255 let cases = [
1256 ("$[1:4]", 0, false),
1257 ("$[1:4]", 1, true),
1258 ("$[1:4]", 3, true),
1259 ("$[1:4]", 4, false),
1260 ("$[1:6:2]", 1, true),
1261 ("$[1:6:2]", 2, false),
1262 ("$[1:6:2]", 5, true),
1263 ("$[:3]", 0, true),
1264 ("$[:3]", 3, false),
1265 ("$[2:]", 100, true),
1266 ("$[2::3]", 5, true),
1267 ("$[2::3]", 4, false),
1268 ("$[::0]", 0, false),
1269 ("$[::0]", 1, false),
1270 ];
1271
1272 for (selector, index, expected) in cases {
1273 let path: JsonPath = selector.parse().unwrap();
1274 assert_eq!(
1275 path.matches_path(&[PathElement::Index(index)]),
1276 expected,
1277 "{selector} vs [{index}]"
1278 );
1279 }
1280
1281 assert!(
1282 !"$[*]".parse::<JsonPath>().unwrap().matches_path(&[]),
1283 "wildcard must not match a missing path element"
1284 );
1285 }
1286
1287 #[test]
1288 fn selectors_do_not_match_when_tail_or_current_element_differs() {
1289 let cases = [
1290 ("$.a.b", vec![PathElement::Member("a".into())]),
1291 (
1292 "$.a.b",
1293 vec![
1294 PathElement::Member("a".into()),
1295 PathElement::Member("x".into()),
1296 ],
1297 ),
1298 (
1299 "$.a[*].b",
1300 vec![PathElement::Member("a".into()), PathElement::Index(0)],
1301 ),
1302 (
1303 "$.a[*].b",
1304 vec![
1305 PathElement::Member("a".into()),
1306 PathElement::Member("x".into()),
1307 PathElement::Member("c".into()),
1308 ],
1309 ),
1310 (
1311 "$[1:4].id",
1312 vec![PathElement::Index(0), PathElement::Member("id".into())],
1313 ),
1314 (
1315 "$[1].id",
1316 vec![PathElement::Index(2), PathElement::Member("id".into())],
1317 ),
1318 ("$[1:4].id", vec![PathElement::Index(1)]),
1319 (
1320 "$[1,\"a\"].id",
1321 vec![PathElement::Index(2), PathElement::Member("id".into())],
1322 ),
1323 ("$[1,\"a\"].id", vec![PathElement::Member("a".into())]),
1324 ("$..author.id", vec![PathElement::Member("author".into())]),
1325 ("$..*.author", vec![PathElement::Member("author".into())]),
1326 (
1327 "$..[1].id",
1328 vec![PathElement::Index(2), PathElement::Member("id".into())],
1329 ),
1330 ("$..[1].id", vec![PathElement::Index(1)]),
1331 (
1332 "$..[1:4].id",
1333 vec![PathElement::Index(0), PathElement::Member("id".into())],
1334 ),
1335 (
1336 "$..[1,\"a\"].id",
1337 vec![PathElement::Index(2), PathElement::Member("id".into())],
1338 ),
1339 ];
1340
1341 for (selector, concrete) in cases {
1342 assert!(
1343 !selector
1344 .parse::<JsonPath>()
1345 .unwrap()
1346 .matches_path(&concrete),
1347 "{selector} unexpectedly matched {concrete:?}"
1348 );
1349 }
1350 }
1351
1352 #[test]
1353 fn unions_match_each_child_selector_kind() {
1354 let cases = [
1355 (
1356 "$.a[\"foo\",9].id".parse::<JsonPath>().unwrap(),
1357 vec![
1358 PathElement::Member("a".into()),
1359 PathElement::Member("foo".into()),
1360 PathElement::Member("id".into()),
1361 ],
1362 ),
1363 (
1364 "$.a[1:4,9].id".parse::<JsonPath>().unwrap(),
1365 vec![
1366 PathElement::Member("a".into()),
1367 PathElement::Index(2),
1368 PathElement::Member("id".into()),
1369 ],
1370 ),
1371 (
1372 "$.a[*,9].id".parse::<JsonPath>().unwrap(),
1373 vec![
1374 PathElement::Member("a".into()),
1375 PathElement::Member("anything".into()),
1376 PathElement::Member("id".into()),
1377 ],
1378 ),
1379 (
1380 JsonPath::from_segments([
1381 Segment::Member("a".into()),
1382 Segment::Union(
1383 vec![
1384 Segment::Union(
1385 vec![Segment::Member("foo".into()), Segment::Index(4)]
1386 .into_boxed_slice(),
1387 ),
1388 Segment::Index(9),
1389 ]
1390 .into_boxed_slice(),
1391 ),
1392 Segment::Member("id".into()),
1393 ]),
1394 vec![
1395 PathElement::Member("a".into()),
1396 PathElement::Member("foo".into()),
1397 PathElement::Member("id".into()),
1398 ],
1399 ),
1400 ];
1401
1402 for (selector, concrete) in cases {
1403 assert!(
1404 selector.matches_path(&concrete),
1405 "{selector} did not match {concrete:?}"
1406 );
1407 }
1408 }
1409
1410 #[test]
1411 fn parses_json_string_escapes_in_member_names() {
1412 let path: JsonPath = r#"$["\"\\\/\b\f\n\r\t"]"#.parse().unwrap();
1413 assert_eq!(
1414 path.segments(),
1415 &[Segment::Member("\"\\/\u{8}\u{c}\n\r\t".into())]
1416 );
1417
1418 let path: JsonPath = r#"$['\'']"#.parse().unwrap();
1419 assert_eq!(path.segments(), &[Segment::Member("'".into())]);
1420
1421 let path: JsonPath = r#"$["\uD834\uDD1E"]"#.parse().unwrap();
1422 assert_eq!(path.segments(), &[Segment::Member("𝄞".into())]);
1423 }
1424
1425 #[test]
1426 fn rejects_malformed_jsonpath_syntax() {
1427 let cases = [
1428 (
1429 "$[x]",
1430 JsonErrorKind::InvalidJsonPath("invalid bracket selector"),
1431 ),
1432 (
1433 "$[1:x]",
1434 JsonErrorKind::InvalidJsonPath("unexpected character"),
1435 ),
1436 (
1437 "$.0bad",
1438 JsonErrorKind::InvalidJsonPath("expected member name"),
1439 ),
1440 (
1441 r#"$["\uD834x"]"#,
1442 JsonErrorKind::InvalidJsonPath("invalid unicode surrogate pair"),
1443 ),
1444 (
1445 r#"$["\uDD1E"]"#,
1446 JsonErrorKind::InvalidJsonPath("invalid unicode surrogate pair"),
1447 ),
1448 (
1449 r#"$["\u12x4"]"#,
1450 JsonErrorKind::InvalidJsonPath("invalid unicode escape"),
1451 ),
1452 (
1453 "$[::x]",
1454 JsonErrorKind::InvalidJsonPath("unexpected character"),
1455 ),
1456 ];
1457
1458 for (input, expected) in cases {
1459 let err = input.parse::<JsonPath>().unwrap_err();
1460 assert_eq!(err.kind(), &expected, "{input}");
1461 }
1462 }
1463
1464 #[test]
1465 fn deterministic_path_fuzz_roundtrips_and_matches_consistently() {
1466 let mut seed = 0x4d59_5df4_d0f3_3173;
1467 for _ in 0..2048 {
1468 let path = fuzz_path(&mut seed);
1469 let rendered = path.to_string();
1470 let reparsed: JsonPath = rendered.parse().unwrap();
1471 assert_eq!(path, reparsed, "{rendered}");
1472
1473 let concrete = fuzz_value_path(&mut seed);
1474 assert_eq!(
1475 path.matches_path(&concrete),
1476 reparsed.matches_path(&concrete),
1477 "{rendered} vs {concrete:?}"
1478 );
1479 }
1480 }
1481
1482 fn fuzz_path(seed: &mut u64) -> JsonPath {
1483 let mut segments = Vec::new();
1484 let len = (next(seed) % 6) as usize;
1485 for _ in 0..len {
1486 segments.push(match next(seed) % 9 {
1487 0 => Segment::Member(fuzz_member(seed).into()),
1488 1 => Segment::Index((next(seed) % 8) as usize),
1489 2 => Segment::Wildcard,
1490 3 => Segment::Slice(fuzz_slice(seed)),
1491 4 => Segment::Union(fuzz_union(seed)),
1492 5 => Segment::DescendantMember(fuzz_member(seed).into()),
1493 6 => Segment::DescendantIndex((next(seed) % 8) as usize),
1494 7 => Segment::DescendantSlice(fuzz_slice(seed)),
1495 _ => Segment::DescendantUnion(fuzz_union(seed)),
1496 });
1497 }
1498 JsonPath::from_segments(segments)
1499 }
1500
1501 fn fuzz_union(seed: &mut u64) -> Box<[Segment]> {
1502 let len = 1 + (next(seed) % 4) as usize;
1503 (0..len)
1504 .map(|_| match next(seed) % 4 {
1505 0 => Segment::Member(fuzz_member(seed).into()),
1506 1 => Segment::Index((next(seed) % 8) as usize),
1507 2 => Segment::Wildcard,
1508 _ => Segment::Slice(fuzz_slice(seed)),
1509 })
1510 .collect()
1511 }
1512
1513 fn fuzz_slice(seed: &mut u64) -> Slice {
1514 let start = next(seed)
1515 .is_multiple_of(2)
1516 .then(|| (next(seed) % 5) as usize);
1517 let width = (next(seed) % 6) as usize;
1518 let end = start.map(|start| start + width);
1519 let step = 1 + (next(seed) % 4) as usize;
1520 Slice::new(start, end, step)
1521 }
1522
1523 fn fuzz_member(seed: &mut u64) -> &'static str {
1524 match next(seed) % 6 {
1525 0 => "a",
1526 1 => "b",
1527 2 => "book",
1528 3 => "author",
1529 4 => "weird.key",
1530 _ => "line\nbreak",
1531 }
1532 }
1533
1534 fn fuzz_value_path(seed: &mut u64) -> Vec<PathElement> {
1535 let len = (next(seed) % 6) as usize;
1536 (0..len)
1537 .map(|_| {
1538 if next(seed).is_multiple_of(2) {
1539 PathElement::Member(fuzz_member(seed).into())
1540 } else {
1541 PathElement::Index((next(seed) % 8) as usize)
1542 }
1543 })
1544 .collect()
1545 }
1546
1547 fn next(seed: &mut u64) -> u64 {
1548 *seed = seed.wrapping_mul(6364136223846793005).wrapping_add(1);
1549 *seed
1550 }
1551}