rsonpath_syntax/
error.rs

1//! Error types for the crate.
2//!
3//! The main error type is [`ParseError`], which contains
4//! all syntax errors encountered during parsing.
5use crate::{
6    num::error::{JsonFloatParseError, JsonIntParseError},
7    str::{self, EscapeMode},
8};
9#[cfg(feature = "color")]
10use std::error::Error;
11use std::fmt::{self, Display};
12use thiserror::Error;
13
14#[cfg(feature = "color")]
15use colored::OwoColorsErrorStyle as ErrorStyleImpl;
16#[cfg(not(feature = "color"))]
17use plain::PlainErrorStyle as ErrorStyleImpl;
18
19#[derive(Debug)]
20pub(crate) struct ParseErrorBuilder {
21    syntax_errors: Vec<SyntaxError>,
22}
23
24/// Errors raised by the query parser.
25#[derive(Debug, Error)]
26pub struct ParseError {
27    input: String,
28    inner: InnerParseError,
29}
30
31impl ParseError {
32    /// Returns whether the error was caused by exceeding the parser's nesting limit.
33    #[inline]
34    #[must_use]
35    pub fn is_nesting_limit_exceeded(&self) -> bool {
36        matches!(self.inner, InnerParseError::RecursionLimit(_))
37    }
38}
39
40#[derive(Debug)]
41enum InnerParseError {
42    Syntax(Vec<SyntaxError>),
43    RecursionLimit(usize),
44}
45
46impl ParseErrorBuilder {
47    pub(crate) fn new() -> Self {
48        Self { syntax_errors: vec![] }
49    }
50
51    pub(crate) fn add(&mut self, syntax_error: SyntaxError) {
52        self.syntax_errors.push(syntax_error)
53    }
54
55    pub(crate) fn add_many(&mut self, mut syntax_errors: Vec<SyntaxError>) {
56        self.syntax_errors.append(&mut syntax_errors)
57    }
58
59    pub(crate) fn is_empty(&self) -> bool {
60        self.syntax_errors.is_empty()
61    }
62
63    pub(crate) fn build(self, str: String) -> ParseError {
64        ParseError {
65            input: str,
66            inner: InnerParseError::Syntax(self.syntax_errors),
67        }
68    }
69
70    pub(crate) fn recursion_limit_exceeded(str: String, recursion_limit: usize) -> ParseError {
71        ParseError {
72            input: str,
73            inner: InnerParseError::RecursionLimit(recursion_limit),
74        }
75    }
76}
77
78impl Display for ParseError {
79    #[inline]
80    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81        fmt_parse_error(self, &ErrorStyleImpl::empty(), f)
82    }
83}
84
85#[cfg(feature = "color")]
86impl ParseError {
87    /// Turn the error into a version with colored display.
88    #[inline(always)]
89    #[must_use]
90    #[cfg_attr(docsrs, doc(cfg(feature = "color")))]
91    pub fn colored(self) -> impl Error {
92        colored::ColoredParseError(self)
93    }
94}
95
96#[inline(always)]
97fn fmt_parse_error(error: &ParseError, style: &ErrorStyleImpl, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98    match &error.inner {
99        InnerParseError::Syntax(syntax_errors) => {
100            let mut suggestion = Suggestion::new();
101            for syntax_error in syntax_errors {
102                writeln!(
103                    f,
104                    "{}",
105                    syntax_error.display(&error.input, &mut suggestion, style.clone())
106                )?;
107            }
108
109            if let Some(suggestion) = suggestion.build(&error.input) {
110                writeln!(
111                    f,
112                    "{} did you mean `{}` ?",
113                    style.note_prefix(&"suggestion:"),
114                    style.suggestion(&suggestion)
115                )?;
116            }
117        }
118        InnerParseError::RecursionLimit(limit) => {
119            writeln!(
120                f,
121                "{} {}",
122                style.error_prefix(&"error:"),
123                style.error_message(&"nesting level exceeded")
124            )?;
125            writeln!(f)?;
126            writeln!(f, "  {}", error.input)?;
127            writeln!(
128                f,
129                "{} the parser limits nesting to {}; this applies to filter logical expressions",
130                style.note_prefix(&"note:"),
131                limit
132            )?;
133        }
134    }
135
136    Ok(())
137}
138
139#[derive(Debug, PartialEq, Eq, Clone)]
140pub(crate) struct SyntaxError {
141    /// Kind of the error.
142    kind: SyntaxErrorKind,
143    /// The byte index at which the error occurred, counting from the end of the input.
144    rev_idx: usize,
145    /// The number of characters that the parser recognized as invalid.
146    len: usize,
147}
148
149#[derive(Debug, PartialEq, Eq, Clone)]
150pub(crate) enum SyntaxErrorKind {
151    // Top-level errors.
152    DisallowedLeadingWhitespace,
153    DisallowedTrailingWhitespace,
154    MissingRootIdentifier,
155    // String/name parsing errors.
156    InvalidUnescapedCharacter,
157    InvalidEscapeSequence,
158    UnpairedHighSurrogate,
159    UnpairedLowSurrogate,
160    InvalidHexDigitInUnicodeEscape,
161    MissingClosingSingleQuote,
162    MissingClosingDoubleQuote,
163    // Segment errors.
164    InvalidSegmentStart,
165    InvalidSegmentAfterTwoPeriods,
166    EmptySelector,
167    InvalidSelector,
168    MissingSelectorSeparator,
169    MissingClosingBracket,
170    InvalidNameShorthandAfterOnePeriod,
171    // Number parsing errors.
172    NegativeZeroInteger,
173    LeadingZeros,
174    NumberParseError(JsonFloatParseError),
175    // Index selector.
176    IndexParseError(JsonIntParseError),
177    // Slice selector.
178    SliceStartParseError(JsonIntParseError),
179    SliceEndParseError(JsonIntParseError),
180    SliceStepParseError(JsonIntParseError),
181    // Filter selector.
182    MissingClosingParenthesis,
183    InvalidNegation,
184    MissingComparisonOperator,
185    InvalidComparisonOperator,
186    InvalidComparable,
187    NonSingularQueryInComparison,
188    InvalidFilter,
189}
190
191impl SyntaxError {
192    pub(crate) fn new(kind: SyntaxErrorKind, rev_idx: usize, len: usize) -> Self {
193        Self { kind, rev_idx, len }
194    }
195    /*
196        This creates friendly displayable errors.
197        Every error displays the entire query up to the point of the error.
198        An error consists of
199        - The toplevel error name/message.
200        - A list of lines of the input, each with an optional underline message.
201        - A list of notes/suggestions at the end.
202    */
203    fn display(&self, input: &str, suggestion: &mut Suggestion, style: ErrorStyleImpl) -> DisplayableSyntaxError {
204        let start_idx = input.len() - self.rev_idx;
205        let end_idx = start_idx + self.len - 1;
206        let mut builder = DisplayableSyntaxErrorBuilder::new();
207
208        for (i, c) in input.char_indices() {
209            let width = tweaked_width(c);
210            if i < start_idx {
211                builder.add_non_underline(width);
212            } else if i <= end_idx {
213                builder.add_underline(width);
214            }
215            builder.add_char(c);
216        }
217        if end_idx >= input.len() {
218            builder.add_underline(1);
219        }
220        builder.add_underline_message(self.kind.underline_message());
221
222        self.generate_notes(&mut builder, suggestion, input);
223
224        return builder.finish(self.kind.toplevel_message(), start_idx, end_idx, style);
225
226        fn tweaked_width(c: char) -> usize {
227            use unicode_width::UnicodeWidthChar;
228            match c {
229                '\t' => 4,
230                _ => c.width().unwrap_or(0),
231            }
232        }
233    }
234
235    fn generate_notes(&self, builder: &mut DisplayableSyntaxErrorBuilder, suggestion: &mut Suggestion, input: &str) {
236        let start_idx = input.len() - self.rev_idx;
237        let end_idx = start_idx + self.len - 1;
238        let (prefix, error, suffix) = self.split_error(input);
239        // Kind-specific notes and suggestion building.
240        match self.kind {
241            SyntaxErrorKind::DisallowedLeadingWhitespace | SyntaxErrorKind::DisallowedTrailingWhitespace => {
242                suggestion.remove(start_idx, error.len());
243            }
244            SyntaxErrorKind::InvalidUnescapedCharacter => {
245                if error == "\"" {
246                    suggestion.replace(start_idx, 1, r#"\""#);
247                } else if error == "'" {
248                    suggestion.replace(start_idx, 1, r"\'");
249                } else {
250                    let escaped = str::escape(error, EscapeMode::DoubleQuoted);
251                    suggestion.replace(start_idx, error.len(), escaped);
252                }
253            }
254            SyntaxErrorKind::InvalidEscapeSequence => {
255                if error == r"\U" && suffix.len() >= 4 && suffix[..4].chars().all(|x| x.is_ascii_hexdigit()) {
256                    builder.add_note("unicode escape sequences must use a lowercase 'u'");
257                    suggestion.replace(start_idx, 2, r"\u");
258                } else if error == r#"\""# {
259                    builder.add_note("double quotes may only be escaped within double-quoted name selectors");
260                    suggestion.replace(start_idx, 2, r#"""#);
261                } else if error == r"\'" {
262                    builder.add_note("single quotes may only be escaped within single-quoted name selectors");
263                    suggestion.replace(start_idx, 2, r#"'"#);
264                } else {
265                    builder.add_note(r#"the only valid escape sequences are \n, \r, \t, \f, \b, \\, \/, \' (in single quoted names), \" (in double quoted names), and \uXXXX where X are hex digits"#);
266                    suggestion.invalidate()
267                }
268            }
269            SyntaxErrorKind::UnpairedHighSurrogate => {
270                builder.add_note(
271                    "a UTF-16 high surrogate has to be followed by a low surrogate to encode a valid Unicode character",
272                );
273                builder.add_note("for more information about UTF-16 surrogate pairs see https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF");
274                suggestion.invalidate();
275            }
276            SyntaxErrorKind::UnpairedLowSurrogate => {
277                builder.add_note(
278                    "a UTF-16 low surrogate has to be preceded by a high surrogate to encode a valid Unicode character",
279                );
280                builder.add_note("for more information about UTF-16 surrogate pairs see https://en.wikipedia.org/wiki/UTF-16#Code_points_from_U+010000_to_U+10FFFF");
281                suggestion.invalidate();
282            }
283            SyntaxErrorKind::InvalidHexDigitInUnicodeEscape => {
284                builder.add_note("valid hex digits are 0 through 9 and A through F (case-insensitive)");
285                suggestion.invalidate();
286            }
287            SyntaxErrorKind::MissingClosingSingleQuote => suggestion.insert(end_idx, "'"),
288            SyntaxErrorKind::MissingClosingDoubleQuote => suggestion.insert(end_idx, "\""),
289            SyntaxErrorKind::MissingRootIdentifier => suggestion.insert(start_idx, "$"),
290            SyntaxErrorKind::InvalidSegmentStart => {
291                builder.add_note("valid segments are: member name shorthands like `.name`/`..name`; or child/descendant bracketed selections like `[<segments>]`/`..[<segments>]`");
292                suggestion.invalidate();
293            }
294            SyntaxErrorKind::InvalidSegmentAfterTwoPeriods => {
295                if error.starts_with('.') {
296                    let nerror = error.trim_start_matches('.');
297                    let number_of_periods = error.len() - nerror.len();
298                    suggestion.remove(start_idx, number_of_periods);
299                } else {
300                    suggestion.invalidate();
301                }
302                builder.add_note("valid segments are either member name shorthands `name`, or bracketed selections like `['name']` or `[42]`");
303            }
304            SyntaxErrorKind::InvalidNameShorthandAfterOnePeriod => {
305                if error.starts_with('[') {
306                    suggestion.remove(start_idx - 1, 1);
307                } else {
308                    suggestion.invalidate();
309                }
310            }
311            SyntaxErrorKind::MissingSelectorSeparator => {
312                let prefix_whitespace_len = prefix.len() - prefix.trim_end_matches(' ').len(); // FIXME
313                suggestion.insert(start_idx - prefix_whitespace_len, ",");
314            }
315            SyntaxErrorKind::MissingClosingBracket => suggestion.insert(end_idx, "]"),
316            SyntaxErrorKind::MissingClosingParenthesis => suggestion.insert(end_idx, ")"),
317            SyntaxErrorKind::NegativeZeroInteger => suggestion.replace(start_idx, error.len(), "0"),
318            SyntaxErrorKind::LeadingZeros => {
319                let is_negative = error.starts_with('-');
320                let replacement = error.trim_start_matches(['-', '0']);
321                let offset = if is_negative { 1 } else { 0 };
322
323                if replacement.is_empty() {
324                    suggestion.replace(start_idx, error.len(), "0");
325                } else {
326                    let remove_len = error.len() - replacement.len() - offset;
327                    suggestion.remove(start_idx + offset, remove_len);
328                }
329            }
330            SyntaxErrorKind::NonSingularQueryInComparison => {
331                suggestion.invalidate();
332                builder.add_note("singular queries use only child segments with single name or index selectors")
333            }
334            SyntaxErrorKind::InvalidSelector
335            | SyntaxErrorKind::IndexParseError(_)
336            | SyntaxErrorKind::SliceStartParseError(_)
337            | SyntaxErrorKind::SliceStepParseError(_)
338            | SyntaxErrorKind::SliceEndParseError(_)
339            | SyntaxErrorKind::NumberParseError(_)
340            | SyntaxErrorKind::EmptySelector
341            | SyntaxErrorKind::InvalidNegation
342            | SyntaxErrorKind::InvalidComparisonOperator
343            | SyntaxErrorKind::InvalidFilter
344            | SyntaxErrorKind::MissingComparisonOperator
345            | SyntaxErrorKind::InvalidComparable => suggestion.invalidate(),
346        }
347
348        // Generic notes.
349        if error.starts_with('$') {
350            builder.add_note("the root identifier '$' must appear exactly once at the start of the query");
351        }
352    }
353
354    fn split_error<'a>(&self, input: &'a str) -> (&'a str, &'a str, &'a str) {
355        let start = input.len() - self.rev_idx;
356        let (prefix, rest) = input.split_at(start);
357        let (error, suffix) = if self.len >= rest.len() {
358            (rest, "")
359        } else {
360            rest.split_at(self.len)
361        };
362        (prefix, error, suffix)
363    }
364}
365
366struct DisplayableSyntaxErrorBuilder {
367    current_line: String,
368    current_underline_offset: usize,
369    current_underline_len: usize,
370    current_underline_message: Option<String>,
371    lines: Vec<SyntaxErrorLine>,
372    notes: Vec<SyntaxErrorNote>,
373}
374
375impl DisplayableSyntaxErrorBuilder {
376    fn new() -> Self {
377        Self {
378            current_line: String::new(),
379            lines: vec![],
380            current_underline_offset: 0,
381            current_underline_len: 0,
382            current_underline_message: None,
383            notes: vec![],
384        }
385    }
386
387    fn add_non_underline(&mut self, width: usize) {
388        if self.current_underline_len == 0 {
389            self.current_underline_offset += width;
390        }
391    }
392
393    fn add_underline(&mut self, width: usize) {
394        self.current_underline_len += width;
395    }
396
397    fn add_underline_message<S: AsRef<str>>(&mut self, message: S) {
398        self.current_underline_message = Some(message.as_ref().to_string());
399    }
400
401    fn add_note<S: AsRef<str>>(&mut self, message: S) {
402        self.notes.push(SyntaxErrorNote {
403            message: message.as_ref().to_string(),
404        })
405    }
406
407    fn add_char(&mut self, c: char) {
408        if c == '\n' {
409            self.finish_line();
410        } else {
411            self.current_line.push(c);
412        }
413    }
414
415    fn finish_line(&mut self) {
416        let underline = self.finish_underline();
417        let mut line = String::new();
418        std::mem::swap(&mut line, &mut self.current_line);
419        self.lines.push(SyntaxErrorLine { line, underline })
420    }
421
422    fn finish_underline(&mut self) -> Option<SyntaxErrorUnderline> {
423        let res = (self.current_underline_len > 0).then(|| SyntaxErrorUnderline {
424            start_pos: self.current_underline_offset,
425            len: self.current_underline_len,
426            message: self.current_underline_message.take(),
427        });
428
429        self.current_underline_offset = 0;
430        self.current_underline_len = 0;
431        res
432    }
433
434    fn finish(
435        mut self,
436        toplevel_message: String,
437        start_idx: usize,
438        end_idx: usize,
439        style: ErrorStyleImpl,
440    ) -> DisplayableSyntaxError {
441        self.finish_line();
442        DisplayableSyntaxError {
443            toplevel_message,
444            start_idx,
445            end_idx,
446            lines: self.lines,
447            notes: self.notes,
448            style,
449        }
450    }
451}
452
453#[derive(Debug)]
454pub(crate) enum InternalParseError<'a> {
455    SyntaxError(SyntaxError, &'a str),
456    SyntaxErrors(Vec<SyntaxError>, &'a str),
457    RecursionLimitExceeded,
458    NomError(nom::error::Error<&'a str>),
459}
460
461impl<'a> nom::error::ParseError<&'a str> for InternalParseError<'a> {
462    fn from_error_kind(input: &'a str, kind: nom::error::ErrorKind) -> Self {
463        Self::NomError(nom::error::Error::from_error_kind(input, kind))
464    }
465
466    fn append(input: &'a str, kind: nom::error::ErrorKind, other: Self) -> Self {
467        match other {
468            Self::NomError(e) => Self::NomError(nom::error::Error::append(input, kind, e)),
469            _ => other,
470        }
471    }
472}
473
474struct DisplayableSyntaxError {
475    toplevel_message: String,
476    start_idx: usize,
477    end_idx: usize,
478    lines: Vec<SyntaxErrorLine>,
479    notes: Vec<SyntaxErrorNote>,
480    style: ErrorStyleImpl,
481}
482
483struct SyntaxErrorNote {
484    message: String,
485}
486
487struct SyntaxErrorLine {
488    line: String,
489    underline: Option<SyntaxErrorUnderline>,
490}
491
492struct SyntaxErrorUnderline {
493    start_pos: usize,
494    len: usize,
495    message: Option<String>,
496}
497
498enum Suggestion {
499    Valid(Vec<SuggestionDiff>),
500    Invalid,
501}
502
503#[derive(Debug)]
504enum SuggestionDiff {
505    Insert(usize, String),
506    Remove(usize, usize),
507    Replace(usize, usize, String),
508}
509
510impl SuggestionDiff {
511    fn start_idx(&self) -> usize {
512        match self {
513            Self::Remove(idx, _) | Self::Replace(idx, _, _) | Self::Insert(idx, _) => *idx,
514        }
515    }
516}
517
518#[cfg(test)]
519mod tests {
520    use super::*;
521
522    #[test]
523    fn foo() {
524        let input = "$..['abc' 'def']....abc..['\n']";
525        let mut suggestion = Suggestion::new();
526        suggestion.insert(9, ",");
527        suggestion.remove(18, 2);
528        suggestion.replace(27, 1, "\\n");
529
530        let result = suggestion.build(input).unwrap();
531        assert_eq!(result, "$..['abc', 'def']..abc..['\\n']");
532    }
533}
534
535impl Suggestion {
536    fn new() -> Self {
537        Self::Valid(vec![])
538    }
539
540    fn insert<S: AsRef<str>>(&mut self, at: usize, str: S) {
541        self.push(SuggestionDiff::Insert(at, str.as_ref().to_string()))
542    }
543
544    fn remove(&mut self, at: usize, len: usize) {
545        self.push(SuggestionDiff::Remove(at, len))
546    }
547
548    fn replace<S: AsRef<str>>(&mut self, at: usize, remove_len: usize, str: S) {
549        self.push(SuggestionDiff::Replace(at, remove_len, str.as_ref().to_string()))
550    }
551
552    fn push(&mut self, diff: SuggestionDiff) {
553        match self {
554            Self::Valid(diffs) => diffs.push(diff),
555            Self::Invalid => (),
556        }
557    }
558
559    fn invalidate(&mut self) {
560        *self = Self::Invalid
561    }
562
563    fn build(self, input: &str) -> Option<String> {
564        match self {
565            Self::Invalid => None,
566            Self::Valid(mut diffs) => {
567                let mut result = String::new();
568                let mut input_chars = input.char_indices();
569                let mut next = input_chars.next();
570                diffs.sort_by_key(SuggestionDiff::start_idx);
571                diffs.reverse();
572
573                while let Some((i, c)) = next {
574                    if let Some(x) = diffs.last() {
575                        if x.start_idx() == i {
576                            let x = diffs.pop().expect("unreachable, last is Some");
577                            match x {
578                                SuggestionDiff::Insert(_, str) => {
579                                    result.push_str(&str);
580                                }
581                                SuggestionDiff::Remove(_, len) => {
582                                    let end_idx = i + len;
583                                    while let Some((i, _)) = next {
584                                        if i >= end_idx {
585                                            break;
586                                        }
587                                        next = input_chars.next();
588                                    }
589                                }
590                                SuggestionDiff::Replace(_, len, str) => {
591                                    result.push_str(&str);
592                                    let end_idx = i + len;
593                                    while let Some((i, _)) = next {
594                                        if i >= end_idx {
595                                            break;
596                                        }
597                                        next = input_chars.next();
598                                    }
599                                }
600                            }
601                            continue;
602                        }
603                    }
604                    next = input_chars.next();
605                    result.push(c);
606                }
607
608                // Any diffs that remain should be inserts at the end.
609                // Verify that and apply them.
610                while let Some(diff) = diffs.pop() {
611                    match diff {
612                        SuggestionDiff::Insert(at, str) if at == input.len() => result.push_str(&str),
613                        _ => panic!("invalid suggestion diff beyond bounds of input: {diff:?}"),
614                    }
615                }
616
617                Some(result)
618            }
619        }
620    }
621}
622
623impl Display for DisplayableSyntaxError {
624    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
625        writeln!(
626            f,
627            "{} {}",
628            self.style.error_prefix(&"error:"),
629            self.style.error_message(&self.toplevel_message)
630        )?;
631        writeln!(f)?;
632        let multiline = self.lines.len() > 1;
633
634        for (i, line) in self.lines.iter().enumerate() {
635            if multiline {
636                writeln!(
637                    f,
638                    " {: >3} {} {}",
639                    self.style.line_numbers(&(i + 1)),
640                    self.style.line_numbers(&"|"),
641                    line.line
642                )?;
643            } else {
644                writeln!(f, "  {}", line.line)?;
645            }
646
647            if let Some(underline) = &line.underline {
648                if multiline {
649                    write!(f, "     {} ", self.style.line_numbers(&"|"))?;
650                } else {
651                    write!(f, "  ")?;
652                }
653
654                for _ in 0..underline.start_pos {
655                    write!(f, " ")?;
656                }
657                for _ in 0..underline.len {
658                    write!(f, "{}", self.style.error_underline(&"^"))?;
659                }
660                if let Some(msg) = &underline.message {
661                    writeln!(f, " {}", self.style.error_underline_message(msg))?;
662                } else {
663                    writeln!(f)?;
664                }
665            }
666        }
667
668        if multiline {
669            write!(f, " ")?;
670        }
671        if self.start_idx == self.end_idx {
672            writeln!(
673                f,
674                "  {} {}{}",
675                self.style.error_position_hint(&"(byte"),
676                self.style.error_position_hint(&self.start_idx),
677                self.style.error_position_hint(&")")
678            )?;
679        } else {
680            writeln!(
681                f,
682                "  {} {}{}{}{}",
683                self.style.error_position_hint(&"(bytes"),
684                self.style.error_position_hint(&self.start_idx),
685                self.style.error_position_hint(&"-"),
686                self.style.error_position_hint(&self.end_idx),
687                self.style.error_position_hint(&")")
688            )?;
689        }
690
691        writeln!(f)?;
692
693        if !self.notes.is_empty() {
694            let mut first = true;
695            for note in &self.notes {
696                if !first {
697                    writeln!(f)?;
698                };
699                write!(f, "{} {note}", self.style.note_prefix(&"note:"))?;
700                first = false;
701            }
702        }
703
704        Ok(())
705    }
706}
707
708impl Display for SyntaxErrorNote {
709    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
710        write!(f, "{}", self.message)
711    }
712}
713
714impl SyntaxErrorKind {
715    #[inline]
716    fn toplevel_message(&self) -> String {
717        match self {
718            Self::DisallowedLeadingWhitespace => "query starting with whitespace".to_string(),
719            Self::DisallowedTrailingWhitespace => "query ending with whitespace".to_string(),
720            Self::InvalidUnescapedCharacter => "invalid unescaped control character".to_string(),
721            Self::InvalidEscapeSequence => "invalid escape sequence".to_string(),
722            Self::UnpairedHighSurrogate => "invalid unicode escape sequence - unpaired high surrogate".to_string(),
723            Self::UnpairedLowSurrogate => "invalid unicode escape sequence - unpaired low surrogate".to_string(),
724            Self::InvalidHexDigitInUnicodeEscape => "invalid unicode escape sequence - invalid hex digit".to_string(),
725            Self::MissingClosingDoubleQuote => "double-quoted name selector is not closed".to_string(),
726            Self::MissingClosingSingleQuote => "single-quoted name selector is not closed".to_string(),
727            Self::MissingRootIdentifier => "query not starting with the root identifier '$'".to_string(),
728            Self::InvalidSegmentStart => "invalid segment syntax".to_string(),
729            Self::InvalidSegmentAfterTwoPeriods => "invalid descendant segment syntax".to_string(),
730            Self::InvalidNameShorthandAfterOnePeriod => "invalid short member name syntax".to_string(),
731            Self::InvalidSelector => "invalid selector syntax".to_string(),
732            Self::EmptySelector => "invalid selector - empty".to_string(),
733            Self::MissingSelectorSeparator => "selectors not separated with commas".to_string(),
734            Self::MissingClosingBracket => "bracketed selection is not closed".to_string(),
735            Self::NegativeZeroInteger => "negative zero used as an integer".to_string(),
736            Self::LeadingZeros => "integer with leading zeros".to_string(),
737            Self::IndexParseError(_) => "invalid index value".to_string(),
738            Self::SliceStartParseError(_) => "invalid slice start".to_string(),
739            Self::SliceEndParseError(_) => "invalid slice end".to_string(),
740            Self::SliceStepParseError(_) => "invalid slice step value".to_string(),
741            Self::NumberParseError(_) => "invalid number format".to_string(),
742            Self::MissingClosingParenthesis => "missing closing parenthesis in filter expression".to_string(),
743            Self::InvalidNegation => "invalid use of logical negation".to_string(),
744            Self::MissingComparisonOperator => "missing comparison operator".to_string(),
745            Self::InvalidComparisonOperator => "invalid comparison operator".to_string(),
746            Self::InvalidComparable => "invalid right-hand side of comparison".to_string(),
747            Self::NonSingularQueryInComparison => "non-singular query used in comparison".to_string(),
748            Self::InvalidFilter => "invalid filter expression syntax".to_string(),
749        }
750    }
751
752    #[inline]
753    fn underline_message(&self) -> String {
754        match self {
755            Self::DisallowedLeadingWhitespace => "leading whitespace is disallowed".to_string(),
756            Self::DisallowedTrailingWhitespace => "trailing whitespace is disallowed".to_string(),
757            Self::InvalidUnescapedCharacter => "this character must be escaped".to_string(),
758            Self::InvalidEscapeSequence => "not a valid escape sequence".to_string(),
759            Self::UnpairedHighSurrogate => "this high surrogate is unpaired".to_string(),
760            Self::UnpairedLowSurrogate => "this low surrogate is unpaired".to_string(),
761            Self::InvalidHexDigitInUnicodeEscape => "not a hex digit".to_string(),
762            Self::MissingClosingDoubleQuote => "expected a double quote '\"'".to_string(),
763            Self::MissingClosingSingleQuote => "expected a single quote `'`".to_string(),
764            Self::MissingRootIdentifier => "the '$' character missing before here".to_string(),
765            Self::InvalidSegmentStart => "not a valid segment syntax".to_string(),
766            Self::InvalidSegmentAfterTwoPeriods => "not a valid descendant segment syntax".to_string(),
767            Self::InvalidNameShorthandAfterOnePeriod => "not a valid name shorthand".to_string(),
768            Self::InvalidSelector => "not a valid selector".to_string(),
769            Self::EmptySelector => "expected a selector here, but found nothing".to_string(),
770            Self::MissingSelectorSeparator => "expected a comma separator before this character".to_string(),
771            Self::MissingClosingBracket => "expected a closing bracket ']'".to_string(),
772            Self::NegativeZeroInteger => "negative zero is not allowed".to_string(),
773            Self::LeadingZeros => "leading zeros are not allowed".to_string(),
774            Self::IndexParseError(inner) => format!("this index value is invalid; {inner}"),
775            Self::SliceStartParseError(inner) => format!("this start index is invalid; {inner}"),
776            Self::SliceEndParseError(inner) => format!("this end index is invalid; {inner}"),
777            Self::SliceStepParseError(inner) => format!("this step value is invalid; {inner}"),
778            Self::NumberParseError(inner) => format!("this number is invalid; {inner}"),
779            Self::MissingClosingParenthesis => "expected a closing parenthesis `(`".to_string(),
780            Self::InvalidNegation => {
781                "this negation is ambiguous; try adding parenthesis around the expression you want to negate"
782                    .to_string()
783            }
784            Self::InvalidComparable => "expected a literal or a filter query here".to_string(),
785            Self::NonSingularQueryInComparison => "this query is not singular".to_string(),
786            Self::MissingComparisonOperator => "expected a comparison operator here".to_string(),
787            Self::InvalidComparisonOperator => "not a valid comparison operator".to_string(),
788            Self::InvalidFilter => "not a valid filter expression".to_string(),
789        }
790    }
791}
792
793#[cfg(feature = "color")]
794mod colored {
795    use super::{fmt_parse_error, ParseError};
796    use std::fmt::{self, Display};
797    use thiserror::Error;
798
799    #[derive(Debug, Error)]
800    pub(super) struct ColoredParseError(pub(super) ParseError);
801
802    impl Display for ColoredParseError {
803        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
804            fmt_parse_error(&self.0, &OwoColorsErrorStyle::colored(), f)
805        }
806    }
807
808    #[derive(Clone)]
809    pub(super) struct OwoColorsErrorStyle {
810        error_prefix: owo_colors::Style,
811        error_message: owo_colors::Style,
812        error_position_hint: owo_colors::Style,
813        error_underline: owo_colors::Style,
814        error_underline_message: owo_colors::Style,
815        line_numbers: owo_colors::Style,
816        note_prefix: owo_colors::Style,
817        suggestion: owo_colors::Style,
818    }
819
820    impl OwoColorsErrorStyle {
821        pub(super) fn colored() -> Self {
822            let error_color = owo_colors::Style::new().bright_red();
823            let error_message = owo_colors::Style::new().bold();
824            let error_position_hint = owo_colors::Style::new().dimmed();
825            let line_color = owo_colors::Style::new().cyan();
826            let note_color = owo_colors::Style::new().bright_cyan();
827            let suggestion_color = owo_colors::Style::new().bright_cyan().bold();
828
829            Self {
830                error_prefix: error_color,
831                error_message,
832                error_position_hint,
833                error_underline: error_color,
834                error_underline_message: error_color,
835                line_numbers: line_color,
836                note_prefix: note_color,
837                suggestion: suggestion_color,
838            }
839        }
840
841        pub(crate) fn empty() -> Self {
842            let empty_style = owo_colors::Style::new();
843            Self {
844                error_prefix: empty_style,
845                error_message: empty_style,
846                error_position_hint: empty_style,
847                error_underline: empty_style,
848                error_underline_message: empty_style,
849                line_numbers: empty_style,
850                note_prefix: empty_style,
851                suggestion: empty_style,
852            }
853        }
854
855        pub(crate) fn error_prefix<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
856            use owo_colors::OwoColorize;
857            target.style(self.error_prefix)
858        }
859
860        pub(crate) fn error_message<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
861            use owo_colors::OwoColorize;
862            target.style(self.error_message)
863        }
864
865        pub(crate) fn error_position_hint<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
866            use owo_colors::OwoColorize;
867            target.style(self.error_position_hint)
868        }
869
870        pub(crate) fn error_underline<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
871            use owo_colors::OwoColorize;
872            target.style(self.error_underline)
873        }
874
875        pub(crate) fn error_underline_message<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
876            use owo_colors::OwoColorize;
877            target.style(self.error_underline_message)
878        }
879
880        pub(crate) fn line_numbers<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
881            use owo_colors::OwoColorize;
882            target.style(self.line_numbers)
883        }
884
885        pub(crate) fn note_prefix<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
886            use owo_colors::OwoColorize;
887            target.style(self.note_prefix)
888        }
889
890        pub(crate) fn suggestion<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
891            use owo_colors::OwoColorize;
892            target.style(self.suggestion)
893        }
894    }
895}
896
897#[cfg(not(feature = "color"))]
898mod plain {
899    use std::fmt::Display;
900
901    #[derive(Clone)]
902    pub(super) struct PlainErrorStyle;
903
904    impl PlainErrorStyle {
905        pub(crate) fn empty() -> Self {
906            Self
907        }
908
909        // We want to keep the same function signature as for the colored version, so `&self` must be here.
910        // We could use a trait, but returning `impl trait` in traits would bump MSRV to 1.75.
911        #[allow(clippy::unused_self)]
912        pub(crate) fn error_prefix<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
913            target
914        }
915
916        #[allow(clippy::unused_self)]
917        pub(crate) fn error_message<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
918            target
919        }
920
921        #[allow(clippy::unused_self)]
922        pub(crate) fn error_position_hint<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
923            target
924        }
925
926        #[allow(clippy::unused_self)]
927        pub(crate) fn error_underline<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
928            target
929        }
930
931        #[allow(clippy::unused_self)]
932        pub(crate) fn error_underline_message<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
933            target
934        }
935
936        #[allow(clippy::unused_self)]
937        pub(crate) fn line_numbers<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
938            target
939        }
940
941        #[allow(clippy::unused_self)]
942        pub(crate) fn note_prefix<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
943            target
944        }
945
946        #[allow(clippy::unused_self)]
947        pub(crate) fn suggestion<'a, D: Display>(&self, target: &'a D) -> impl Display + 'a {
948            target
949        }
950    }
951}