mdq/query/
error.rs

1use pest::Span;
2use std::fmt::{Display, Formatter};
3
4/// An error representing an invalid selector query.
5///
6/// <div class="warning">
7/// This struct's <code>source()</code> is not part of the public contract, and may change at any time without that change being
8/// marked as a breaking change.
9/// </div>
10#[derive(Clone, Debug, PartialEq, Eq, Hash)]
11pub struct ParseError {
12    pub(crate) inner: InnerParseError,
13}
14
15impl ParseError {
16    /// Creates a new ParseError from an [InnerParseError].
17    ///
18    /// This is intentionally not a [From] impl, because we want to keep it `pub(crate)`.
19    pub(crate) fn new(inner: InnerParseError) -> Self {
20        Self { inner }
21    }
22}
23
24impl Display for ParseError {
25    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
26        Display::fmt(&self.inner, f)
27    }
28}
29
30impl std::error::Error for ParseError {
31    /// This method gets the error's source, if available. **Not part of the public API contract.**
32    ///
33    /// Please see the warning on [this struct's main documentation](ParseError).
34    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
35        self.inner.source()
36    }
37}
38
39#[derive(Clone, Debug, PartialEq, Eq, Hash)]
40pub(crate) enum InnerParseError {
41    Pest(crate::query::Error),
42    Other(DetachedSpan, String),
43}
44
45impl Display for InnerParseError {
46    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
47        match &self {
48            InnerParseError::Pest(error) => Display::fmt(error, f),
49            InnerParseError::Other(_, message) => Display::fmt(message, f),
50        }
51    }
52}
53
54impl std::error::Error for InnerParseError {
55    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
56        match self {
57            InnerParseError::Pest(err) => Some(err),
58            InnerParseError::Other(_, _) => None,
59        }
60    }
61}
62
63impl ParseError {
64    /// Gets a string suitable for displaying to a user, given the original query string.
65    ///
66    /// ```
67    /// use mdq::select::Selector;
68    /// let query_text = "$ ! invalid query string ! $";
69    /// let parse_error = Selector::try_from(query_text).expect_err("expected an error");
70    /// let expected_error = r" --> 1:1
71    ///   |
72    /// 1 | $ ! invalid query string ! $
73    ///   | ^---
74    ///   |
75    ///   = expected valid query";
76    /// assert_eq!(parse_error.to_string(query_text), expected_error);
77    /// ```
78    pub fn to_string(&self, query_text: &str) -> String {
79        match &self.inner {
80            InnerParseError::Pest(e) => format!("{e}"),
81            InnerParseError::Other(span, message) => match Span::new(query_text, span.start, span.end) {
82                None => message.to_string(),
83                Some(span) => {
84                    let pest_err = crate::query::Error::new_from_span(span, message.to_string());
85                    pest_err.to_string()
86                }
87            },
88        }
89    }
90}
91
92impl From<crate::query::Error> for InnerParseError {
93    fn from(err: crate::query::Error) -> Self {
94        Self::Pest(err)
95    }
96}
97
98/// Like a [pest::Span], but without a reference to the underlying `&str`, and thus cheaply Copyable.
99#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
100pub struct DetachedSpan {
101    pub start: usize,
102    pub end: usize,
103}
104
105impl From<pest::Span<'_>> for DetachedSpan {
106    fn from(value: pest::Span) -> Self {
107        Self {
108            start: value.start(),
109            end: value.end(),
110        }
111    }
112}
113
114impl From<&crate::query::Pair<'_>> for DetachedSpan {
115    fn from(value: &crate::query::Pair<'_>) -> Self {
116        value.as_span().into()
117    }
118}