nextest_filtering/
errors.rs

1// Copyright (c) The nextest Contributors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::expression::FiltersetKind;
5use miette::{Diagnostic, SourceSpan};
6use std::fmt;
7use thiserror::Error;
8
9/// A set of errors that occurred while parsing a filterset.
10#[derive(Clone, Debug)]
11#[non_exhaustive]
12pub struct FiltersetParseErrors {
13    /// The input string.
14    pub input: String,
15
16    /// The parse errors returned.
17    pub errors: Vec<ParseSingleError>,
18}
19
20impl FiltersetParseErrors {
21    pub(crate) fn new(input: impl Into<String>, errors: Vec<ParseSingleError>) -> Self {
22        Self {
23            input: input.into(),
24            errors,
25        }
26    }
27}
28
29/// An individual error that occurred while parsing a filterset.
30#[derive(Clone, Debug, Error, Diagnostic, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum ParseSingleError {
33    /// An invalid regex was encountered.
34    #[error("invalid regex")]
35    InvalidRegex {
36        /// The part of the input that failed.
37        #[label("{}", message)]
38        span: SourceSpan,
39
40        /// A message indicating the failure.
41        message: String,
42    },
43
44    /// An invalid glob pattern was encountered.
45    #[error("invalid glob")]
46    InvalidGlob {
47        /// The part of the input that failed.
48        #[label("{}", error)]
49        span: SourceSpan,
50
51        /// The underlying error.
52        error: GlobConstructError,
53    },
54
55    /// A banned predicate was encountered.
56    #[error("predicate not allowed in `{kind}` expressions")]
57    BannedPredicate {
58        /// The kind of expression.
59        kind: FiltersetKind,
60
61        /// The span of the banned predicate.
62        #[label("this predicate causes {reason}")]
63        span: SourceSpan,
64
65        /// The reason why the predicate is banned.
66        reason: BannedPredicateReason,
67    },
68
69    /// An invalid regex was encountered but we couldn't determine a better error message.
70    #[error("invalid regex")]
71    InvalidRegexWithoutMessage(#[label("invalid regex")] SourceSpan),
72
73    /// A regex string was not closed.
74    #[error("expected close regex")]
75    ExpectedCloseRegex(#[label("missing `/`")] SourceSpan),
76
77    /// An unexpected OR operator was found.
78    #[error("invalid OR operator")]
79    InvalidOrOperator(#[label("expected `|`, `+`, or `or`")] SourceSpan),
80
81    /// An unexpected AND operator was found.
82    #[error("invalid AND operator")]
83    InvalidAndOperator(#[label("expected `&` or `and`")] SourceSpan),
84
85    /// An unexpected argument was found.
86    #[error("unexpected argument")]
87    UnexpectedArgument(#[label("this set doesn't take an argument")] SourceSpan),
88
89    /// An unexpected comma was found.
90    #[error("unexpected comma")]
91    UnexpectedComma(#[label("this set doesn't take multiple arguments")] SourceSpan),
92
93    /// An invalid string was found.
94    #[error("invalid string")]
95    InvalidString(#[label("invalid string")] SourceSpan),
96
97    /// An open parenthesis `(` was expected but not found.
98    #[error("expected open parenthesis")]
99    ExpectedOpenParenthesis(#[label("missing `(`")] SourceSpan),
100
101    /// A close parenthesis `)` was expected but not found.
102    #[error("expected close parenthesis")]
103    ExpectedCloseParenthesis(#[label("missing `)`")] SourceSpan),
104
105    /// An invalid escape character was found.
106    #[error("invalid escape character")]
107    InvalidEscapeCharacter(#[label("invalid escape character")] SourceSpan),
108
109    /// An expression was expected in this position but not found.
110    #[error("expected expression")]
111    ExpectedExpr(#[label("missing expression")] SourceSpan),
112
113    /// The expression was expected to end here but some extra text was found.
114    #[error("expected end of expression")]
115    ExpectedEndOfExpression(#[label("unparsed input")] SourceSpan),
116
117    /// This matcher didn't match any packages.
118    #[error("operator didn't match any packages")]
119    NoPackageMatch(#[label("no packages matched this")] SourceSpan),
120
121    /// This matcher didn't match any binary IDs.
122    #[error("operator didn't match any binary IDs")]
123    NoBinaryIdMatch(#[label("no binary IDs matched this")] SourceSpan),
124
125    /// This matcher didn't match any binary names.
126    #[error("operator didn't match any binary names")]
127    NoBinaryNameMatch(#[label("no binary names matched this")] SourceSpan),
128
129    /// Expected "host" or "target" for a `platform()` predicate.
130    #[error("invalid argument for platform")]
131    InvalidPlatformArgument(#[label("expected \"target\" or \"host\"")] SourceSpan),
132
133    /// An unknown parsing error occurred.
134    #[error("unknown parsing error")]
135    Unknown,
136}
137
138impl ParseSingleError {
139    pub(crate) fn invalid_regex(input: &str, start: usize, end: usize) -> Self {
140        // Use regex-syntax to parse the input so that we get better error messages.
141        match regex_syntax::Parser::new().parse(input) {
142            Ok(_) => {
143                // It is weird that a regex failed to parse with regex but succeeded with
144                // regex-syntax, but we can't do better.
145                Self::InvalidRegexWithoutMessage((start, end - start).into())
146            }
147            Err(err) => {
148                let (message, span) = match &err {
149                    regex_syntax::Error::Parse(err) => (format!("{}", err.kind()), err.span()),
150                    regex_syntax::Error::Translate(err) => (format!("{}", err.kind()), err.span()),
151                    _ => return Self::InvalidRegexWithoutMessage((start, end - start).into()),
152                };
153
154                // This isn't perfect because it doesn't account for "\/", but it'll do for now.
155                let err_start = start + span.start.offset;
156                let err_end = start + span.end.offset;
157
158                Self::InvalidRegex {
159                    span: (err_start, err_end - err_start).into(),
160                    message,
161                }
162            }
163        }
164    }
165}
166
167#[derive(Clone, Debug, Error, PartialEq, Eq)]
168pub enum GlobConstructError {
169    #[error("{}", .0.kind())]
170    InvalidGlob(globset::Error),
171
172    #[error("{}", .0)]
173    RegexError(String),
174}
175
176#[derive(Debug)]
177pub(crate) struct State<'a> {
178    // A `RefCell` is required here because the state must implement `Clone` to work with nom.
179    errors: &'a mut Vec<ParseSingleError>,
180}
181
182impl<'a> State<'a> {
183    pub fn new(errors: &'a mut Vec<ParseSingleError>) -> Self {
184        Self { errors }
185    }
186
187    pub fn report_error(&mut self, error: ParseSingleError) {
188        self.errors.push(error);
189    }
190}
191
192#[derive(Copy, Clone, Debug, PartialEq, Eq)]
193pub enum BannedPredicateReason {
194    /// This predicate causes infinite recursion.
195    InfiniteRecursion,
196}
197
198impl fmt::Display for BannedPredicateReason {
199    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
200        match self {
201            BannedPredicateReason::InfiniteRecursion => {
202                write!(f, "infinite recursion")
203            }
204        }
205    }
206}