1use 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#[derive(Debug, Error)]
26pub struct ParseError {
27 input: String,
28 inner: InnerParseError,
29}
30
31impl ParseError {
32 #[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 #[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: SyntaxErrorKind,
143 rev_idx: usize,
145 len: usize,
147}
148
149#[derive(Debug, PartialEq, Eq, Clone)]
150pub(crate) enum SyntaxErrorKind {
151 DisallowedLeadingWhitespace,
153 DisallowedTrailingWhitespace,
154 MissingRootIdentifier,
155 InvalidUnescapedCharacter,
157 InvalidEscapeSequence,
158 UnpairedHighSurrogate,
159 UnpairedLowSurrogate,
160 InvalidHexDigitInUnicodeEscape,
161 MissingClosingSingleQuote,
162 MissingClosingDoubleQuote,
163 InvalidSegmentStart,
165 InvalidSegmentAfterTwoPeriods,
166 EmptySelector,
167 InvalidSelector,
168 MissingSelectorSeparator,
169 MissingClosingBracket,
170 InvalidNameShorthandAfterOnePeriod,
171 NegativeZeroInteger,
173 LeadingZeros,
174 NumberParseError(JsonFloatParseError),
175 IndexParseError(JsonIntParseError),
177 SliceStartParseError(JsonIntParseError),
179 SliceEndParseError(JsonIntParseError),
180 SliceStepParseError(JsonIntParseError),
181 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 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 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(); 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 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 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 #[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}