fionn_gron/
query.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Query mode for filtering gron output.
3//!
4//! Supports JSONPath-like queries to filter gron output to specific paths.
5//!
6//! ## Syntax
7//!
8//! - `.field` - Match exact field
9//! - `["field"]` - Match field (for special characters)
10//! - `[0]` - Match array index
11//! - `[*]` - Match all array elements (wildcard)
12//! - `..field` - Recursive descent (match anywhere)
13//!
14//! ## Examples
15//!
16//! ```text
17//! .users[*].name      - All user names
18//! ..error             - All fields named "error" at any depth
19//! .data[0].id         - First data item's ID
20//! ["field.name"]      - Field with dots in name
21//! ```
22
23use std::fmt;
24
25/// A compiled query for path matching.
26#[derive(Debug, Clone)]
27pub struct Query {
28    /// The parsed segments
29    segments: Vec<QuerySegment>,
30    /// Whether query contains recursive descent
31    has_recursive: bool,
32    /// Whether query contains wildcards
33    has_wildcard: bool,
34    /// Original query string for display
35    original: String,
36}
37
38/// A segment in a query path.
39#[derive(Debug, Clone, PartialEq, Eq)]
40pub enum QuerySegment {
41    /// Match exact field name: `.field` or `["field"]`
42    Field(String),
43    /// Match exact array index: `[0]`
44    Index(usize),
45    /// Match any array element: `[*]`
46    Wildcard,
47    /// Recursive descent to find field anywhere: `..field`
48    Recursive(String),
49}
50
51/// Query parsing error.
52#[derive(Debug, Clone)]
53pub enum QueryError {
54    /// Empty query string
55    Empty,
56    /// Unexpected character
57    UnexpectedChar(char, usize),
58    /// Unclosed bracket
59    UnclosedBracket,
60    /// Unclosed quote
61    UnclosedQuote,
62    /// Invalid index
63    InvalidIndex(String),
64    /// Expected field name
65    ExpectedField,
66}
67
68impl fmt::Display for QueryError {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            Self::Empty => write!(f, "empty query"),
72            Self::UnexpectedChar(c, pos) => {
73                write!(f, "unexpected character '{c}' at position {pos}")
74            }
75            Self::UnclosedBracket => write!(f, "unclosed bracket"),
76            Self::UnclosedQuote => write!(f, "unclosed quote"),
77            Self::InvalidIndex(s) => write!(f, "invalid index: {s}"),
78            Self::ExpectedField => write!(f, "expected field name"),
79        }
80    }
81}
82
83impl std::error::Error for QueryError {}
84
85impl Query {
86    /// Parse a query string.
87    ///
88    /// # Errors
89    /// Returns an error if the query syntax is invalid.
90    pub fn parse(query: &str) -> Result<Self, QueryError> {
91        if query.is_empty() {
92            return Err(QueryError::Empty);
93        }
94
95        let mut segments = Vec::new();
96        let bytes = query.as_bytes();
97        let mut i = 0;
98
99        // Skip leading root identifier if present (e.g., "json" in "json.field")
100        // We match against paths that already have the root stripped
101        if bytes[0] != b'.' && bytes[0] != b'[' {
102            // Find end of root identifier
103            while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
104                i += 1;
105            }
106        }
107
108        while i < bytes.len() {
109            match bytes[i] {
110                b'.' => {
111                    i += 1;
112                    if i >= bytes.len() {
113                        return Err(QueryError::ExpectedField);
114                    }
115
116                    // Check for recursive descent (..)
117                    if bytes[i] == b'.' {
118                        i += 1;
119                        let (field, consumed) = parse_identifier(&bytes[i..])?;
120                        segments.push(QuerySegment::Recursive(field));
121                        i += consumed;
122                    } else {
123                        let (field, consumed) = parse_identifier(&bytes[i..])?;
124                        segments.push(QuerySegment::Field(field));
125                        i += consumed;
126                    }
127                }
128                b'[' => {
129                    i += 1;
130                    if i >= bytes.len() {
131                        return Err(QueryError::UnclosedBracket);
132                    }
133
134                    match bytes[i] {
135                        b'*' => {
136                            // Wildcard [*]
137                            i += 1;
138                            if i >= bytes.len() || bytes[i] != b']' {
139                                return Err(QueryError::UnclosedBracket);
140                            }
141                            i += 1;
142                            segments.push(QuerySegment::Wildcard);
143                        }
144                        b'"' => {
145                            // Quoted field ["field"]
146                            i += 1;
147                            let (field, consumed) = parse_quoted_string(&bytes[i..])?;
148                            i += consumed;
149                            if i >= bytes.len() || bytes[i] != b']' {
150                                return Err(QueryError::UnclosedBracket);
151                            }
152                            i += 1;
153                            segments.push(QuerySegment::Field(field));
154                        }
155                        b'0'..=b'9' => {
156                            // Numeric index [0]
157                            let start = i;
158                            while i < bytes.len() && bytes[i].is_ascii_digit() {
159                                i += 1;
160                            }
161                            let index_str =
162                                std::str::from_utf8(&bytes[start..i]).map_err(|_| {
163                                    QueryError::InvalidIndex("invalid utf8".to_string())
164                                })?;
165                            let index: usize = index_str
166                                .parse()
167                                .map_err(|_| QueryError::InvalidIndex(index_str.to_string()))?;
168
169                            if i >= bytes.len() || bytes[i] != b']' {
170                                return Err(QueryError::UnclosedBracket);
171                            }
172                            i += 1;
173                            segments.push(QuerySegment::Index(index));
174                        }
175                        c => {
176                            return Err(QueryError::UnexpectedChar(c as char, i));
177                        }
178                    }
179                }
180                c => {
181                    return Err(QueryError::UnexpectedChar(c as char, i));
182                }
183            }
184        }
185
186        let has_recursive = segments
187            .iter()
188            .any(|s| matches!(s, QuerySegment::Recursive(_)));
189        let has_wildcard = segments.iter().any(|s| matches!(s, QuerySegment::Wildcard));
190
191        Ok(Self {
192            segments,
193            has_recursive,
194            has_wildcard,
195            original: query.to_string(),
196        })
197    }
198
199    /// Check if a gron path matches this query.
200    ///
201    /// The path should be in gron format: `json.users[0].name`
202    #[must_use]
203    pub fn matches(&self, path: &str) -> bool {
204        let path_segments = parse_path_segments(path);
205        self.matches_segments(&path_segments, 0, 0)
206    }
207
208    /// Check if a path could potentially match (for early termination).
209    ///
210    /// Returns:
211    /// - `Matches` if path matches query
212    /// - `Partial` if path doesn't match but descendants might
213    /// - `NoMatch` if path and descendants cannot match
214    #[must_use]
215    pub fn match_potential(&self, path: &str) -> MatchPotential {
216        let path_segments = parse_path_segments(path);
217        self.check_potential(&path_segments, 0, 0)
218    }
219
220    /// Get the original query string.
221    #[must_use]
222    pub fn original(&self) -> &str {
223        &self.original
224    }
225
226    /// Check if query has recursive descent.
227    #[must_use]
228    pub const fn has_recursive(&self) -> bool {
229        self.has_recursive
230    }
231
232    /// Check if query has wildcards.
233    #[must_use]
234    pub const fn has_wildcard(&self) -> bool {
235        self.has_wildcard
236    }
237
238    fn matches_segments(
239        &self,
240        path_segments: &[PathSegment<'_>],
241        query_idx: usize,
242        path_idx: usize,
243    ) -> bool {
244        // If we've matched all query segments, success
245        if query_idx >= self.segments.len() {
246            return true;
247        }
248
249        // If we've exhausted path segments but have more query, fail
250        if path_idx >= path_segments.len() {
251            return false;
252        }
253
254        let query_seg = &self.segments[query_idx];
255        let path_seg = &path_segments[path_idx];
256
257        match query_seg {
258            QuerySegment::Field(expected) => {
259                if let PathSegment::Field(actual) = path_seg
260                    && *actual == expected
261                {
262                    return self.matches_segments(path_segments, query_idx + 1, path_idx + 1);
263                }
264                false
265            }
266            QuerySegment::Index(expected) => {
267                if let PathSegment::Index(actual) = path_seg
268                    && actual == expected
269                {
270                    return self.matches_segments(path_segments, query_idx + 1, path_idx + 1);
271                }
272                false
273            }
274            QuerySegment::Wildcard => {
275                // Wildcard matches any single segment
276                self.matches_segments(path_segments, query_idx + 1, path_idx + 1)
277            }
278            QuerySegment::Recursive(expected) => {
279                // Try matching at current position and all subsequent positions
280                for i in path_idx..path_segments.len() {
281                    if let PathSegment::Field(actual) = &path_segments[i]
282                        && *actual == expected
283                        && self.matches_segments(path_segments, query_idx + 1, i + 1)
284                    {
285                        return true;
286                    }
287                }
288                false
289            }
290        }
291    }
292
293    fn check_potential(
294        &self,
295        path_segments: &[PathSegment<'_>],
296        query_idx: usize,
297        path_idx: usize,
298    ) -> MatchPotential {
299        // If we've matched all query segments, it's a match
300        if query_idx >= self.segments.len() {
301            return MatchPotential::Matches;
302        }
303
304        // If path is exhausted but query remains, descendants might match
305        if path_idx >= path_segments.len() {
306            return MatchPotential::Partial;
307        }
308
309        let query_seg = &self.segments[query_idx];
310        let path_seg = &path_segments[path_idx];
311
312        match query_seg {
313            QuerySegment::Field(expected) => {
314                if let PathSegment::Field(actual) = path_seg
315                    && *actual == expected
316                {
317                    return self.check_potential(path_segments, query_idx + 1, path_idx + 1);
318                }
319                // Field mismatch - no match possible
320                MatchPotential::NoMatch
321            }
322            QuerySegment::Index(expected) => {
323                if let PathSegment::Index(actual) = path_seg
324                    && actual == expected
325                {
326                    return self.check_potential(path_segments, query_idx + 1, path_idx + 1);
327                }
328                MatchPotential::NoMatch
329            }
330            QuerySegment::Wildcard => {
331                // Wildcard matches any segment, continue
332                self.check_potential(path_segments, query_idx + 1, path_idx + 1)
333            }
334            QuerySegment::Recursive(_) => {
335                // Recursive can match anywhere, so always partial until matched
336                MatchPotential::Partial
337            }
338        }
339    }
340}
341
342/// Result of checking match potential.
343#[derive(Debug, Clone, Copy, PartialEq, Eq)]
344pub enum MatchPotential {
345    /// This path matches the query
346    Matches,
347    /// This path doesn't match but descendants might
348    Partial,
349    /// This path and all descendants cannot match
350    NoMatch,
351}
352
353/// Parsed segment from a gron path.
354#[derive(Debug, Clone, PartialEq, Eq)]
355enum PathSegment<'a> {
356    Field(&'a str),
357    Index(usize),
358}
359
360/// Parse a gron path into segments.
361fn parse_path_segments(path: &str) -> Vec<PathSegment<'_>> {
362    let mut segments = Vec::new();
363    let bytes = path.as_bytes();
364    let mut i = 0;
365
366    // Skip root identifier
367    while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
368        i += 1;
369    }
370
371    while i < bytes.len() {
372        match bytes[i] {
373            b'.' => {
374                i += 1;
375                let start = i;
376                while i < bytes.len() && bytes[i] != b'.' && bytes[i] != b'[' {
377                    i += 1;
378                }
379                if i > start {
380                    let field = unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) };
381                    segments.push(PathSegment::Field(field));
382                }
383            }
384            b'[' => {
385                i += 1;
386                if i < bytes.len() && bytes[i] == b'"' {
387                    // Quoted field
388                    i += 1;
389                    let start = i;
390                    while i < bytes.len() && bytes[i] != b'"' {
391                        if bytes[i] == b'\\' && i + 1 < bytes.len() {
392                            i += 2;
393                        } else {
394                            i += 1;
395                        }
396                    }
397                    let field = unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) };
398                    segments.push(PathSegment::Field(field));
399                    i += 1; // Skip closing quote
400                } else {
401                    // Numeric index
402                    let start = i;
403                    while i < bytes.len() && bytes[i].is_ascii_digit() {
404                        i += 1;
405                    }
406                    if i > start {
407                        let index_str = unsafe { std::str::from_utf8_unchecked(&bytes[start..i]) };
408                        if let Ok(index) = index_str.parse() {
409                            segments.push(PathSegment::Index(index));
410                        }
411                    }
412                }
413                // Skip closing bracket (common to both branches)
414                if i < bytes.len() && bytes[i] == b']' {
415                    i += 1;
416                }
417            }
418            _ => {
419                i += 1;
420            }
421        }
422    }
423
424    segments
425}
426
427/// Parse an identifier from bytes.
428fn parse_identifier(bytes: &[u8]) -> Result<(String, usize), QueryError> {
429    let mut i = 0;
430    while i < bytes.len() {
431        let b = bytes[i];
432        if b == b'.' || b == b'[' {
433            break;
434        }
435        if !b.is_ascii_alphanumeric() && b != b'_' && b != b'-' {
436            break;
437        }
438        i += 1;
439    }
440    if i == 0 {
441        return Err(QueryError::ExpectedField);
442    }
443    let field = std::str::from_utf8(&bytes[..i])
444        .map_err(|_| QueryError::ExpectedField)?
445        .to_string();
446    Ok((field, i))
447}
448
449/// Parse a quoted string.
450fn parse_quoted_string(bytes: &[u8]) -> Result<(String, usize), QueryError> {
451    let mut result = String::new();
452    let mut i = 0;
453
454    while i < bytes.len() {
455        match bytes[i] {
456            b'"' => {
457                return Ok((result, i + 1));
458            }
459            b'\\' if i + 1 < bytes.len() => {
460                i += 1;
461                match bytes[i] {
462                    b'"' => result.push('"'),
463                    b'\\' => result.push('\\'),
464                    b'n' => result.push('\n'),
465                    b't' => result.push('\t'),
466                    other => {
467                        result.push('\\');
468                        result.push(other as char);
469                    }
470                }
471                i += 1;
472            }
473            b => {
474                result.push(b as char);
475                i += 1;
476            }
477        }
478    }
479
480    Err(QueryError::UnclosedQuote)
481}
482
483#[cfg(test)]
484mod tests {
485    use super::*;
486
487    // =========================================================================
488    // Query Parsing Tests
489    // =========================================================================
490
491    #[test]
492    fn test_parse_simple_field() {
493        let query = Query::parse(".name").unwrap();
494        assert_eq!(query.segments.len(), 1);
495        assert_eq!(query.segments[0], QuerySegment::Field("name".to_string()));
496    }
497
498    #[test]
499    fn test_parse_nested_fields() {
500        let query = Query::parse(".users.name").unwrap();
501        assert_eq!(query.segments.len(), 2);
502        assert_eq!(query.segments[0], QuerySegment::Field("users".to_string()));
503        assert_eq!(query.segments[1], QuerySegment::Field("name".to_string()));
504    }
505
506    #[test]
507    fn test_parse_array_index() {
508        let query = Query::parse(".items[0]").unwrap();
509        assert_eq!(query.segments.len(), 2);
510        assert_eq!(query.segments[0], QuerySegment::Field("items".to_string()));
511        assert_eq!(query.segments[1], QuerySegment::Index(0));
512    }
513
514    #[test]
515    fn test_parse_wildcard() {
516        let query = Query::parse(".users[*].name").unwrap();
517        assert_eq!(query.segments.len(), 3);
518        assert_eq!(query.segments[0], QuerySegment::Field("users".to_string()));
519        assert_eq!(query.segments[1], QuerySegment::Wildcard);
520        assert_eq!(query.segments[2], QuerySegment::Field("name".to_string()));
521        assert!(query.has_wildcard());
522    }
523
524    #[test]
525    fn test_parse_recursive() {
526        let query = Query::parse("..error").unwrap();
527        assert_eq!(query.segments.len(), 1);
528        assert_eq!(
529            query.segments[0],
530            QuerySegment::Recursive("error".to_string())
531        );
532        assert!(query.has_recursive());
533    }
534
535    #[test]
536    fn test_parse_bracket_notation() {
537        let query = Query::parse("[\"field.name\"]").unwrap();
538        assert_eq!(query.segments.len(), 1);
539        assert_eq!(
540            query.segments[0],
541            QuerySegment::Field("field.name".to_string())
542        );
543    }
544
545    #[test]
546    fn test_parse_with_root() {
547        let query = Query::parse("json.users[0]").unwrap();
548        assert_eq!(query.segments.len(), 2);
549        assert_eq!(query.segments[0], QuerySegment::Field("users".to_string()));
550        assert_eq!(query.segments[1], QuerySegment::Index(0));
551    }
552
553    // =========================================================================
554    // Query Matching Tests
555    // =========================================================================
556
557    #[test]
558    fn test_matches_simple() {
559        let query = Query::parse(".name").unwrap();
560        assert!(query.matches("json.name"));
561        assert!(!query.matches("json.age"));
562        assert!(!query.matches("json.user.name"));
563    }
564
565    #[test]
566    fn test_matches_nested() {
567        let query = Query::parse(".user.name").unwrap();
568        assert!(query.matches("json.user.name"));
569        assert!(!query.matches("json.user"));
570        assert!(!query.matches("json.user.age"));
571    }
572
573    #[test]
574    fn test_matches_array_index() {
575        let query = Query::parse(".items[0]").unwrap();
576        assert!(query.matches("json.items[0]"));
577        assert!(!query.matches("json.items[1]"));
578        assert!(!query.matches("json.items"));
579    }
580
581    #[test]
582    fn test_matches_wildcard() {
583        let query = Query::parse(".users[*].name").unwrap();
584        assert!(query.matches("json.users[0].name"));
585        assert!(query.matches("json.users[99].name"));
586        assert!(!query.matches("json.users[0].age"));
587        assert!(!query.matches("json.users[0]"));
588    }
589
590    #[test]
591    fn test_matches_recursive() {
592        let query = Query::parse("..error").unwrap();
593        assert!(query.matches("json.error"));
594        assert!(query.matches("json.nested.error"));
595        assert!(query.matches("json.deeply.nested.path.error"));
596        assert!(!query.matches("json.message"));
597    }
598
599    #[test]
600    fn test_match_potential_exact() {
601        let query = Query::parse(".users.name").unwrap();
602
603        assert_eq!(
604            query.match_potential("json.users.name"),
605            MatchPotential::Matches
606        );
607        assert_eq!(query.match_potential("json.users"), MatchPotential::Partial);
608        assert_eq!(query.match_potential("json.items"), MatchPotential::NoMatch);
609    }
610
611    #[test]
612    fn test_match_potential_wildcard() {
613        let query = Query::parse(".users[*].name").unwrap();
614
615        assert_eq!(
616            query.match_potential("json.users[0].name"),
617            MatchPotential::Matches
618        );
619        assert_eq!(
620            query.match_potential("json.users[0]"),
621            MatchPotential::Partial
622        );
623        assert_eq!(query.match_potential("json.users"), MatchPotential::Partial);
624    }
625
626    #[test]
627    fn test_complex_query() {
628        let query = Query::parse(".data[*].users[0].email").unwrap();
629
630        assert!(query.matches("json.data[5].users[0].email"));
631        assert!(!query.matches("json.data[5].users[1].email"));
632        assert!(!query.matches("json.data[5].users[0].name"));
633    }
634
635    // =========================================================================
636    // Error Tests
637    // =========================================================================
638
639    #[test]
640    fn test_parse_error_empty() {
641        assert!(matches!(Query::parse(""), Err(QueryError::Empty)));
642    }
643
644    #[test]
645    fn test_parse_error_unclosed_bracket() {
646        assert!(matches!(
647            Query::parse("[0"),
648            Err(QueryError::UnclosedBracket)
649        ));
650    }
651
652    #[test]
653    fn test_parse_error_unclosed_quote() {
654        assert!(matches!(
655            Query::parse("[\"field"),
656            Err(QueryError::UnclosedQuote)
657        ));
658    }
659
660    // =========================================================================
661    // QueryError Display Tests
662    // =========================================================================
663
664    #[test]
665    fn test_query_error_display_empty() {
666        let err = QueryError::Empty;
667        assert_eq!(err.to_string(), "empty query");
668    }
669
670    #[test]
671    fn test_query_error_display_unexpected_char() {
672        let err = QueryError::UnexpectedChar('$', 5);
673        let msg = err.to_string();
674        assert!(msg.contains("unexpected character"));
675        assert!(msg.contains('$'));
676        assert!(msg.contains('5'));
677    }
678
679    #[test]
680    fn test_query_error_display_unclosed_bracket() {
681        let err = QueryError::UnclosedBracket;
682        assert_eq!(err.to_string(), "unclosed bracket");
683    }
684
685    #[test]
686    fn test_query_error_display_unclosed_quote() {
687        let err = QueryError::UnclosedQuote;
688        assert_eq!(err.to_string(), "unclosed quote");
689    }
690
691    #[test]
692    fn test_query_error_display_invalid_index() {
693        let err = QueryError::InvalidIndex("abc".to_string());
694        let msg = err.to_string();
695        assert!(msg.contains("invalid index"));
696        assert!(msg.contains("abc"));
697    }
698
699    #[test]
700    fn test_query_error_display_expected_field() {
701        let err = QueryError::ExpectedField;
702        assert_eq!(err.to_string(), "expected field name");
703    }
704
705    #[test]
706    fn test_query_error_is_std_error() {
707        let err: Box<dyn std::error::Error> = Box::new(QueryError::Empty);
708        assert!(!err.to_string().is_empty());
709    }
710
711    // =========================================================================
712    // Query Accessor Tests
713    // =========================================================================
714
715    #[test]
716    fn test_query_original() {
717        let query = Query::parse(".users[0].name").unwrap();
718        assert_eq!(query.original(), ".users[0].name");
719    }
720
721    #[test]
722    fn test_query_has_recursive_false() {
723        let query = Query::parse(".users.name").unwrap();
724        assert!(!query.has_recursive());
725    }
726
727    #[test]
728    fn test_query_has_recursive_true() {
729        let query = Query::parse("..name").unwrap();
730        assert!(query.has_recursive());
731    }
732
733    #[test]
734    fn test_query_has_wildcard_false() {
735        let query = Query::parse(".users[0]").unwrap();
736        assert!(!query.has_wildcard());
737    }
738
739    #[test]
740    fn test_query_has_wildcard_true() {
741        let query = Query::parse(".users[*]").unwrap();
742        assert!(query.has_wildcard());
743    }
744
745    // =========================================================================
746    // Parse Error Cases
747    // =========================================================================
748
749    #[test]
750    fn test_parse_error_expected_field_after_dot() {
751        let result = Query::parse(".");
752        assert!(matches!(result, Err(QueryError::ExpectedField)));
753    }
754
755    #[test]
756    fn test_parse_error_expected_field_after_recursive() {
757        let result = Query::parse("..");
758        assert!(matches!(result, Err(QueryError::ExpectedField)));
759    }
760
761    #[test]
762    fn test_parse_error_unexpected_char_in_bracket() {
763        let result = Query::parse("[abc]");
764        assert!(matches!(result, Err(QueryError::UnexpectedChar(_, _))));
765    }
766
767    #[test]
768    fn test_parse_error_unexpected_char_outside() {
769        // Test unexpected char in various positions
770        // After a valid field, @ causes unexpected char error
771        let result = Query::parse(".field@more");
772        assert!(matches!(result, Err(QueryError::UnexpectedChar('@', _))));
773    }
774
775    #[test]
776    fn test_parse_wildcard_unclosed() {
777        let result = Query::parse("[*");
778        assert!(matches!(result, Err(QueryError::UnclosedBracket)));
779    }
780
781    #[test]
782    fn test_parse_quoted_field_no_close_bracket() {
783        let result = Query::parse("[\"field\"");
784        assert!(matches!(result, Err(QueryError::UnclosedBracket)));
785    }
786
787    #[test]
788    fn test_parse_numeric_index_no_close_bracket() {
789        let result = Query::parse("[123");
790        assert!(matches!(result, Err(QueryError::UnclosedBracket)));
791    }
792
793    #[test]
794    fn test_parse_empty_bracket() {
795        let result = Query::parse("[");
796        assert!(matches!(result, Err(QueryError::UnclosedBracket)));
797    }
798
799    // =========================================================================
800    // QuerySegment Tests
801    // =========================================================================
802
803    #[test]
804    fn test_query_segment_equality() {
805        assert_eq!(
806            QuerySegment::Field("a".to_string()),
807            QuerySegment::Field("a".to_string())
808        );
809        assert_ne!(
810            QuerySegment::Field("a".to_string()),
811            QuerySegment::Field("b".to_string())
812        );
813        assert_eq!(QuerySegment::Index(0), QuerySegment::Index(0));
814        assert_ne!(QuerySegment::Index(0), QuerySegment::Index(1));
815        assert_eq!(QuerySegment::Wildcard, QuerySegment::Wildcard);
816        assert_eq!(
817            QuerySegment::Recursive("x".to_string()),
818            QuerySegment::Recursive("x".to_string())
819        );
820    }
821
822    #[test]
823    fn test_query_segment_clone() {
824        let seg = QuerySegment::Field("test".to_string());
825        let cloned = seg.clone();
826        assert_eq!(seg, cloned);
827    }
828
829    #[test]
830    fn test_query_segment_debug() {
831        let seg = QuerySegment::Wildcard;
832        let debug = format!("{seg:?}");
833        assert!(debug.contains("Wildcard"));
834    }
835
836    // =========================================================================
837    // MatchPotential Tests
838    // =========================================================================
839
840    #[test]
841    fn test_match_potential_equality() {
842        assert_eq!(MatchPotential::Matches, MatchPotential::Matches);
843        assert_eq!(MatchPotential::Partial, MatchPotential::Partial);
844        assert_eq!(MatchPotential::NoMatch, MatchPotential::NoMatch);
845        assert_ne!(MatchPotential::Matches, MatchPotential::NoMatch);
846    }
847
848    #[test]
849    fn test_match_potential_clone_copy() {
850        let mp = MatchPotential::Partial;
851        let cloned = mp;
852        let copied = mp;
853        assert_eq!(mp, cloned);
854        assert_eq!(mp, copied);
855    }
856
857    #[test]
858    fn test_match_potential_debug() {
859        let mp = MatchPotential::Matches;
860        let debug = format!("{mp:?}");
861        assert!(debug.contains("Matches"));
862    }
863
864    // =========================================================================
865    // Query Clone and Debug
866    // =========================================================================
867
868    #[test]
869    fn test_query_clone() {
870        let query = Query::parse(".users[*].name").unwrap();
871        let cloned = query.clone();
872        assert_eq!(query.original(), cloned.original());
873        assert_eq!(query.has_recursive(), cloned.has_recursive());
874        assert_eq!(query.has_wildcard(), cloned.has_wildcard());
875    }
876
877    #[test]
878    fn test_query_debug() {
879        let query = Query::parse(".name").unwrap();
880        let debug = format!("{query:?}");
881        assert!(debug.contains("Query"));
882    }
883
884    // =========================================================================
885    // Quoted String Parsing Tests
886    // =========================================================================
887
888    #[test]
889    fn test_parse_quoted_escape_quote() {
890        let query = Query::parse("[\"field\\\"name\"]").unwrap();
891        assert_eq!(
892            query.segments[0],
893            QuerySegment::Field("field\"name".to_string())
894        );
895    }
896
897    #[test]
898    fn test_parse_quoted_escape_backslash() {
899        let query = Query::parse("[\"path\\\\to\"]").unwrap();
900        assert_eq!(
901            query.segments[0],
902            QuerySegment::Field("path\\to".to_string())
903        );
904    }
905
906    #[test]
907    fn test_parse_quoted_escape_n() {
908        let query = Query::parse("[\"line\\nbreak\"]").unwrap();
909        assert_eq!(
910            query.segments[0],
911            QuerySegment::Field("line\nbreak".to_string())
912        );
913    }
914
915    #[test]
916    fn test_parse_quoted_escape_t() {
917        let query = Query::parse("[\"tab\\there\"]").unwrap();
918        assert_eq!(
919            query.segments[0],
920            QuerySegment::Field("tab\there".to_string())
921        );
922    }
923
924    #[test]
925    fn test_parse_quoted_unknown_escape() {
926        let query = Query::parse("[\"test\\xvalue\"]").unwrap();
927        // Unknown escapes are preserved as-is
928        assert_eq!(
929            query.segments[0],
930            QuerySegment::Field("test\\xvalue".to_string())
931        );
932    }
933
934    // =========================================================================
935    // Path Segment Parsing Tests
936    // =========================================================================
937
938    #[test]
939    fn test_matches_with_quoted_path_field() {
940        let query = Query::parse("[\"special-field\"]").unwrap();
941        // Path with quoted field in gron format
942        assert!(query.matches("json[\"special-field\"]"));
943    }
944
945    #[test]
946    fn test_matches_path_with_escape() {
947        let query = Query::parse(".field").unwrap();
948        // Test path parsing with escape in quoted field
949        assert!(query.matches("json.field"));
950    }
951
952    // =========================================================================
953    // Recursive Matching Tests
954    // =========================================================================
955
956    #[test]
957    fn test_recursive_no_match_if_field_not_found() {
958        let query = Query::parse("..notfound").unwrap();
959        assert!(!query.matches("json.a.b.c"));
960    }
961
962    #[test]
963    fn test_recursive_match_at_various_depths() {
964        let query = Query::parse("..target").unwrap();
965        assert!(query.matches("json.target"));
966        assert!(query.matches("json.a.target"));
967        assert!(query.matches("json.a.b.target"));
968        assert!(query.matches("json.a.b.c.d.target"));
969    }
970
971    #[test]
972    fn test_recursive_with_continuation() {
973        let query = Query::parse("..error.message").unwrap();
974        assert!(query.matches("json.error.message"));
975        assert!(query.matches("json.nested.error.message"));
976        assert!(!query.matches("json.nested.error"));
977    }
978
979    // =========================================================================
980    // Match Potential Edge Cases
981    // =========================================================================
982
983    #[test]
984    fn test_match_potential_recursive() {
985        let query = Query::parse("..field").unwrap();
986        // Recursive always returns Partial until matched
987        assert_eq!(query.match_potential("json.other"), MatchPotential::Partial);
988    }
989
990    #[test]
991    fn test_match_potential_index_mismatch() {
992        let query = Query::parse(".items[0]").unwrap();
993        assert_eq!(
994            query.match_potential("json.items[1]"),
995            MatchPotential::NoMatch
996        );
997    }
998
999    #[test]
1000    fn test_match_potential_field_index_type_mismatch() {
1001        let query = Query::parse(".items.name").unwrap();
1002        // Path has index where query expects field
1003        assert_eq!(
1004            query.match_potential("json.items[0]"),
1005            MatchPotential::NoMatch
1006        );
1007    }
1008
1009    // =========================================================================
1010    // Large Index Tests
1011    // =========================================================================
1012
1013    #[test]
1014    fn test_parse_large_index() {
1015        let query = Query::parse("[999999]").unwrap();
1016        assert_eq!(query.segments[0], QuerySegment::Index(999_999));
1017    }
1018
1019    #[test]
1020    fn test_matches_large_index() {
1021        let query = Query::parse(".items[12345]").unwrap();
1022        assert!(query.matches("json.items[12345]"));
1023        assert!(!query.matches("json.items[12346]"));
1024    }
1025
1026    // =========================================================================
1027    // Multiple Wildcards and Recursive
1028    // =========================================================================
1029
1030    #[test]
1031    fn test_multiple_wildcards() {
1032        let query = Query::parse("[*][*]").unwrap();
1033        assert!(query.matches("json[0][0]"));
1034        assert!(query.matches("json[5][10]"));
1035    }
1036
1037    #[test]
1038    fn test_wildcard_then_field() {
1039        let query = Query::parse("[*].name").unwrap();
1040        assert!(query.matches("json[0].name"));
1041        assert!(!query.matches("json[0].age"));
1042    }
1043
1044    // =========================================================================
1045    // Path Parsing Edge Cases
1046    // =========================================================================
1047
1048    #[test]
1049    fn test_parse_path_empty_field() {
1050        let segments = parse_path_segments("json..field");
1051        // Empty field between dots
1052        assert!(!segments.is_empty());
1053    }
1054
1055    #[test]
1056    fn test_parse_path_only_root() {
1057        let segments = parse_path_segments("json");
1058        assert!(segments.is_empty());
1059    }
1060
1061    #[test]
1062    fn test_parse_path_trailing_dot() {
1063        let segments = parse_path_segments("json.field.");
1064        // Trailing dot produces no extra segment
1065        assert_eq!(segments.len(), 1);
1066    }
1067
1068    // =========================================================================
1069    // Identifier with Special Characters
1070    // =========================================================================
1071
1072    #[test]
1073    fn test_parse_identifier_with_underscore() {
1074        let query = Query::parse(".my_field").unwrap();
1075        assert_eq!(
1076            query.segments[0],
1077            QuerySegment::Field("my_field".to_string())
1078        );
1079    }
1080
1081    #[test]
1082    fn test_parse_identifier_with_hyphen() {
1083        let query = Query::parse(".my-field").unwrap();
1084        assert_eq!(
1085            query.segments[0],
1086            QuerySegment::Field("my-field".to_string())
1087        );
1088    }
1089
1090    #[test]
1091    fn test_parse_identifier_with_numbers() {
1092        let query = Query::parse(".field123").unwrap();
1093        assert_eq!(
1094            query.segments[0],
1095            QuerySegment::Field("field123".to_string())
1096        );
1097    }
1098}