cedar_policy_core/parser/
err.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17use std::collections::{BTreeSet, HashMap, HashSet};
18use std::fmt::{self, Display, Write};
19use std::iter;
20use std::ops::{Deref, DerefMut};
21
22use either::Either;
23use lalrpop_util as lalr;
24use lazy_static::lazy_static;
25use miette::{Diagnostic, LabeledSpan, SourceSpan};
26use smol_str::SmolStr;
27use thiserror::Error;
28
29use crate::ast::{self, ExprConstructionError, InputInteger, PolicyID, RestrictedExprError, Var};
30use crate::parser::fmt::join_with_conjunction;
31use crate::parser::loc::Loc;
32use crate::parser::node::Node;
33use crate::parser::unescape::UnescapeError;
34
35use super::cst;
36
37pub(crate) type RawLocation = usize;
38pub(crate) type RawToken<'a> = lalr::lexer::Token<'a>;
39pub(crate) type RawUserError = Node<String>;
40
41pub(crate) type RawParseError<'a> = lalr::ParseError<RawLocation, RawToken<'a>, RawUserError>;
42pub(crate) type RawErrorRecovery<'a> = lalr::ErrorRecovery<RawLocation, RawToken<'a>, RawUserError>;
43
44type OwnedRawParseError = lalr::ParseError<RawLocation, String, RawUserError>;
45
46/// For errors during parsing
47#[derive(Clone, Debug, Diagnostic, Error, PartialEq, Eq)]
48pub enum ParseError {
49    /// Error from the CST parser.
50    #[error(transparent)]
51    #[diagnostic(transparent)]
52    ToCST(#[from] ToCSTError),
53    /// Error in the CST -> AST transform, mostly well-formedness issues.
54    #[error(transparent)]
55    #[diagnostic(transparent)]
56    ToAST(#[from] ToASTError),
57    /// Error concerning restricted expressions.
58    #[error(transparent)]
59    #[diagnostic(transparent)]
60    RestrictedExpr(#[from] RestrictedExprError),
61    /// Errors concerning parsing literals on their own
62    #[error(transparent)]
63    #[diagnostic(transparent)]
64    ParseLiteral(#[from] ParseLiteralError),
65}
66
67impl ParseError {
68    /// Extract a primary source span locating the error, if one is available.
69    #[deprecated(
70        since = "3.3.0",
71        note = "Use the location information provided by miette::Diagnostic via `.labels()` and `.source_code()` instead"
72    )]
73    pub fn primary_source_span(&self) -> Option<SourceSpan> {
74        match self {
75            ParseError::ToCST(to_cst_err) => Some(to_cst_err.primary_source_span()),
76            ParseError::ToAST(to_ast_err) => Some(to_ast_err.source_loc().span),
77            ParseError::RestrictedExpr(restricted_expr_err) => match restricted_expr_err {
78                RestrictedExprError::InvalidRestrictedExpression { expr, .. } => {
79                    expr.source_loc().map(|loc| loc.span)
80                }
81            },
82            ParseError::ParseLiteral(parse_lit_err) => parse_lit_err
83                .labels()
84                .and_then(|mut it| it.next().map(|lspan| *lspan.inner())),
85        }
86    }
87}
88
89/// Errors in the top-level parse literal entrypoint
90#[derive(Debug, Clone, PartialEq, Diagnostic, Error, Eq)]
91pub enum ParseLiteralError {
92    /// The top-level parser endpoint for parsing a literal encountered a non-literal.
93    /// Since this can be any possible other expression, we just return it as a string.
94    #[error("`{0}` is not a literal")]
95    ParseLiteral(String),
96}
97
98/// Errors in the CST -> AST transform, mostly well-formedness issues.
99#[derive(Debug, Error, Clone, PartialEq, Eq)]
100#[error("{kind}")]
101pub struct ToASTError {
102    kind: ToASTErrorKind,
103    loc: Loc,
104}
105
106// Construct `labels` and `source_code` based on the `loc` in this struct;
107// and everything else forwarded directly to `kind`.
108impl Diagnostic for ToASTError {
109    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
110        Some(Box::new(iter::once(LabeledSpan::underline(self.loc.span))))
111    }
112
113    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
114        self.kind.code()
115    }
116
117    fn severity(&self) -> Option<miette::Severity> {
118        self.kind.severity()
119    }
120
121    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
122        self.kind.help()
123    }
124
125    fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
126        self.kind.url()
127    }
128
129    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
130        Some(&self.loc.src as &dyn miette::SourceCode)
131    }
132
133    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
134        self.kind.diagnostic_source()
135    }
136}
137
138impl ToASTError {
139    /// Construct a new `ToASTError`.
140    pub fn new(kind: ToASTErrorKind, loc: Loc) -> Self {
141        Self { kind, loc }
142    }
143
144    /// Get the error kind.
145    pub fn kind(&self) -> &ToASTErrorKind {
146        &self.kind
147    }
148
149    pub(crate) fn source_loc(&self) -> &Loc {
150        &self.loc
151    }
152}
153
154/// Details about a particular kind of `ToASTError`.
155#[derive(Debug, Diagnostic, Error, Clone, PartialEq, Eq)]
156#[non_exhaustive]
157pub enum ToASTErrorKind {
158    /// Returned when we attempt to parse a template with a conflicting id
159    #[error("a template with id `{0}` already exists in the policy set")]
160    DuplicateTemplateId(PolicyID),
161    /// Returned when we attempt to parse a policy with a conflicting id
162    #[error("a policy with id `{0}` already exists in the policy set")]
163    DuplicatePolicyId(PolicyID),
164    /// Returned when a template is encountered but a static policy is expected
165    #[error(transparent)]
166    #[diagnostic(transparent)]
167    ExpectedStaticPolicy(#[from] parse_errors::ExpectedStaticPolicy),
168    /// Returned when a static policy is encountered but a template is expected
169    #[error(transparent)]
170    #[diagnostic(transparent)]
171    ExpectedTemplate(#[from] parse_errors::ExpectedTemplate),
172    /// Returned when we attempt to parse a policy or template with duplicate or
173    /// conflicting annotations
174    #[error("duplicate annotation: @{0}")]
175    DuplicateAnnotation(ast::AnyId),
176    /// Returned when a policy contains template slots in a when/unless clause. This is not currently supported. See RFC 3
177    #[error("found template slot {} in a `{clausetype}` clause", slot.id)]
178    #[diagnostic(help("slots are currently unsupported in `{clausetype}` clauses"))]
179    SlotsInConditionClause {
180        /// Slot that was found in a when/unless clause
181        slot: ast::Slot,
182        /// Clause type, e.g. "when" or "unless"
183        clausetype: &'static str,
184    },
185    /// Returned when a policy is missing one of the 3 required scope clauses. (`principal`, `action`, and `resource`)
186    #[error("this policy is missing the `{0}` variable in the scope")]
187    MissingScopeConstraint(Var),
188    /// Returned when a policy has an extra scope clause. This is not valid syntax
189    #[error("this policy has an extra constraint in the scope: `{0}`")]
190    #[diagnostic(help(
191        "a policy must have exactly `principal`, `action`, and `resource` constraints"
192    ))]
193    ExtraScopeConstraints(cst::VariableDef),
194    /// Returned when a policy uses a reserved keyword as an identifier.
195    #[error("this identifier is reserved and cannot be used: `{0}`")]
196    ReservedIdentifier(cst::Ident),
197    /// Returned when a policy contains an invalid identifier.
198    /// This error is not currently returned, but is here for future-proofing.
199    /// See [`cst::Ident::Invalid`]
200    #[error("not a valid identifier: `{0}`")]
201    InvalidIdentifier(String),
202    /// Returned when a policy uses '=' as a binary operator.
203    /// '=' is not an operator in Cedar; we can suggest '==' instead.
204    #[error("'=' is not a valid operator in Cedar")]
205    #[diagnostic(help("try using '==' instead"))]
206    InvalidSingleEq,
207    /// Returned when a policy uses a effect keyword beyond `permit` or `forbid`
208    #[error("not a valid policy effect: `{0}`")]
209    #[diagnostic(help("effect must be either `permit` or `forbid`"))]
210    InvalidEffect(cst::Ident),
211    /// Returned when a policy uses a condition keyword beyond `when` or `unless`
212    #[error("not a valid policy condition: `{0}`")]
213    #[diagnostic(help("condition must be either `when` or `unless`"))]
214    InvalidCondition(cst::Ident),
215    /// Returned when a policy uses a variable in the scope beyond `principal`, `action`, or `resource`
216    #[error("expected a variable that is valid in the policy scope; found: `{0}`")]
217    #[diagnostic(help(
218        "policy scopes must contain a `principal`, `action`, and `resource` element in that order"
219    ))]
220    InvalidScopeConstraintVariable(cst::Ident),
221    /// Returned when a policy contains an invalid method name
222    #[error("not a valid method name: `{0}`")]
223    InvalidMethodName(String),
224    /// Returned when a policy scope clause contains the wrong variable.
225    /// (`principal` must be in the first clause, etc...)
226    #[error("found the variable `{got}` where the variable `{expected}` must be used")]
227    #[diagnostic(help(
228        "policy scopes must contain a `principal`, `action`, and `resource` element in that order"
229    ))]
230    IncorrectVariable {
231        /// The variable that is expected in this clause
232        expected: Var,
233        /// The variable that was present in this clause
234        got: Var,
235    },
236    /// Returned when a policy scope clause uses an operator not allowed in scopes.
237    #[error("not a valid policy scope constraint: {0}")]
238    #[diagnostic(help(
239        "policy scope constraints must be either `==`, `in`, `is`, or `_ is _ in _`"
240    ))]
241    InvalidConstraintOperator(cst::RelOp),
242    /// Returned when an Entity UID used as an action does not have the type `Action`
243    #[error("expected an entity uid with the type `Action` but got `{0}`")]
244    #[diagnostic(help("action entities must have type `Action`, optionally in a namespace"))]
245    InvalidActionType(crate::ast::EntityUID),
246    /// Returned when a condition clause is empty
247    #[error("{}condition clause cannot be empty", match .0 { Some(ident) => format!("`{}` ", ident), None => "".to_string() })]
248    EmptyClause(Option<cst::Ident>),
249    /// Returned when the internal invariant around annotation info has been violated
250    #[error("internal invariant violated. No parse errors were reported but annotation information was missing")]
251    AnnotationInvariantViolation,
252    /// Returned when membership chains do not resolve to an expression, violating an internal invariant
253    #[error("internal invariant violated. Membership chain did not resolve to an expression")]
254    MembershipInvariantViolation,
255    /// Returned for a non-parse-able string literal
256    #[error("invalid string literal: `{0}`")]
257    InvalidString(String),
258    /// Returned for attempting to use an arbitrary variable name. Cedar does not support arbitrary variables.
259    #[error("arbitrary variables are not supported; the valid Cedar variables are `principal`, `action`, `resource`, and `context`")]
260    #[diagnostic(help("did you mean to enclose `{0}` in quotes to make a string?"))]
261    ArbitraryVariable(SmolStr),
262    /// Returned for attempting to use an invalid attribute name
263    #[error("not a valid attribute name: `{0}`")]
264    #[diagnostic(help("attribute names can either be identifiers or string literals"))]
265    InvalidAttribute(SmolStr),
266    /// Returned for attempting to use an invalid attribute name in a record name
267    #[error("record literal has invalid attributes")]
268    InvalidAttributesInRecordLiteral,
269    /// Returned for attempting to use an attribute with a namespace
270    #[error("`{0}` cannot be used as an attribute as it contains a namespace")]
271    PathAsAttribute(String),
272    /// Returned when a policy attempts to call a method function-style
273    #[error("`{0}` is a method, not a function")]
274    #[diagnostic(help("use a method-style call: `e.{0}(..)`"))]
275    FunctionCallOnMethod(crate::ast::Id),
276    /// Returned when a policy attempts to call a function in the method style
277    #[error("`{0}` is a function, not a method")]
278    #[diagnostic(help("use a function-style call: `{0}(..)`"))]
279    MethodCallOnFunction(crate::ast::Id),
280    /// Returned when the right hand side of a `like` expression is not a constant pattern literal
281    #[error("right hand side of a `like` expression must be a pattern literal, but got `{0}`")]
282    InvalidPattern(String),
283    /// Returned when the right hand side of a `is` expression is not an entity type name
284    #[error("right hand side of an `is` expression must be an entity type name, but got `{0}`")]
285    #[diagnostic(help("try using `==` to test for equality"))]
286    IsInvalidName(String),
287    /// Returned when an unexpected node is in the policy scope clause
288    #[error("expected {expected}, found {got}")]
289    WrongNode {
290        /// What the expected AST node kind was
291        expected: &'static str,
292        /// What AST node was present in the policy source
293        got: String,
294        /// Optional free-form text with a suggestion for how to fix the problem
295        #[help]
296        suggestion: Option<String>,
297    },
298    /// Returned when a policy contains ambiguous ordering of operators.
299    /// This can be resolved by using parenthesis to make order explicit
300    #[error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")]
301    AmbiguousOperators,
302    /// Returned when a policy uses the division operator (`/`), which is not supported
303    #[error("division is not supported")]
304    UnsupportedDivision,
305    /// Returned when a policy uses the remainder/modulo operator (`%`), which is not supported
306    #[error("remainder/modulo is not supported")]
307    UnsupportedModulo,
308    /// Any `ExprConstructionError` can also happen while converting CST to AST
309    #[error(transparent)]
310    #[diagnostic(transparent)]
311    ExprConstructionError(#[from] ExprConstructionError),
312    /// Returned when a policy contains an integer literal that is out of range
313    #[error("integer literal `{0}` is too large")]
314    #[diagnostic(help("maximum allowed integer literal is `{}`", InputInteger::MAX))]
315    IntegerLiteralTooLarge(u64),
316    /// Returned when a unary operator is chained more than 4 times in a row
317    #[error("too many occurrences of `{0}`")]
318    #[diagnostic(help("cannot chain more the 4 applications of a unary operator"))]
319    UnaryOpLimit(crate::ast::UnaryOp),
320    /// Returned when a variable is called as a function, which is not allowed.
321    /// Functions are not first class values in Cedar
322    #[error("`{0}(...)` is not a valid function call")]
323    #[diagnostic(help("variables cannot be called as functions"))]
324    VariableCall(crate::ast::Var),
325    /// Returned when a policy attempts to call a method on a value that has no methods
326    #[error("attempted to call `{0}.{1}`, but `{0}` does not have any methods")]
327    NoMethods(crate::ast::Name, ast::Id),
328    /// Returned when a policy attempts to call a function that does not exist
329    #[error("`{0}` is not a function")]
330    NotAFunction(crate::ast::Name),
331    /// Returned when a policy attempts to write an entity literal
332    #[error("entity literals are not supported")]
333    UnsupportedEntityLiterals,
334    /// Returned when an expression is the target of a function call.
335    /// Functions are not first class values in Cedar
336    #[error("function calls must be of the form: `<name>(arg1, arg2, ...)`")]
337    ExpressionCall,
338    /// Returned when a policy attempts to access the fields of a value with no fields
339    #[error("incorrect member access `{0}.{1}`, `{0}` has no fields or methods")]
340    InvalidAccess(crate::ast::Name, SmolStr),
341    /// Returned when a policy attempts to index on a fields of a value with no fields
342    #[error("incorrect indexing expression `{0}[{1}]`, `{0}` has no fields")]
343    InvalidIndex(crate::ast::Name, SmolStr),
344    /// Returned when the contents of an indexing expression is not a string literal
345    #[error("the contents of an index expression must be a string literal")]
346    NonStringIndex,
347    /// Returned when a user attempts to use type-constraint `:` syntax. This
348    /// syntax was not adopted, but `is` can be used to write type constraints
349    /// in the policy scope.
350    #[error("type constraints using `:` are not supported")]
351    #[diagnostic(help("try using `is` instead"))]
352    TypeConstraints,
353    /// Returned when a policy uses a path in an invalid context
354    #[error("a path is not valid in this context")]
355    InvalidPath,
356    /// Returned when a string needs to be fully normalized
357    #[error("`{kind}` needs to be normalized (e.g., whitespace removed): `{src}`")]
358    #[diagnostic(help("the normalized form is `{normalized_src}`"))]
359    NonNormalizedString {
360        /// The kind of string we are expecting
361        kind: &'static str,
362        /// The source string passed in
363        src: String,
364        /// The normalized form of the string
365        normalized_src: String,
366    },
367    /// Returned when a CST node is empty
368    #[error("data should not be empty")]
369    MissingNodeData,
370    /// Returned when the right hand side of a `has` expression is neither a field name or a string literal
371    #[error("the right hand side of a `has` expression must be a field name or string literal")]
372    HasNonLiteralRHS,
373    /// Returned when a CST expression is invalid
374    #[error("`{0}` is not a valid expression")]
375    InvalidExpression(cst::Name),
376    /// Returned when a function or method is called with the wrong arity
377    #[error("call to `{name}` requires exactly {expected} argument{}, but got {got} argument{}", if .expected == &1 { "" } else { "s" }, if .got == &1 { "" } else { "s" })]
378    WrongArity {
379        /// Name of the function or method being called
380        name: &'static str,
381        /// The expected number of arguments
382        expected: usize,
383        /// The number of arguments present in source
384        got: usize,
385    },
386    /// Returned when a string contains invalid escapes
387    #[error(transparent)]
388    #[diagnostic(transparent)]
389    Unescape(#[from] UnescapeError),
390    /// Returns when a policy scope has incorrect EntityUIDs/Template Slots
391    #[error(transparent)]
392    #[diagnostic(transparent)]
393    RefCreation(#[from] RefCreationError),
394    /// Returned when an `is` appears in an invalid position in the policy scope
395    #[error(transparent)]
396    #[diagnostic(transparent)]
397    InvalidIs(#[from] InvalidIsError),
398    /// Returned when a policy contains a template slot other than `?principal` or `?resource`
399    #[error("`{0}` is not a valid template slot")]
400    #[diagnostic(help("a template slot may only be `?principal` or `?resource`"))]
401    InvalidSlot(SmolStr),
402}
403
404impl ToASTErrorKind {
405    /// Constructor for the [`ToASTErrorKind::WrongNode`] error
406    pub fn wrong_node(
407        expected: &'static str,
408        got: impl Into<String>,
409        suggestion: Option<impl Into<String>>,
410    ) -> Self {
411        Self::WrongNode {
412            expected,
413            got: got.into(),
414            suggestion: suggestion.map(Into::into),
415        }
416    }
417
418    /// Constructor for the [`ToASTErrorKind::WrongArity`] error
419    pub fn wrong_arity(name: &'static str, expected: usize, got: usize) -> Self {
420        Self::WrongArity {
421            name,
422            expected,
423            got,
424        }
425    }
426
427    /// Constructor for the [`ToASTErrorKind::ExpectedStaticPolicy`] error
428    pub fn expected_static_policy(slot: ast::Slot) -> Self {
429        parse_errors::ExpectedStaticPolicy { slot }.into()
430    }
431
432    /// Constructor for the [`ToASTErrorKind::ExpectedTemplate`] error
433    pub fn expected_template() -> Self {
434        parse_errors::ExpectedTemplate::new().into()
435    }
436}
437
438/// Error subtypes for [`ToASTErrorKind`]
439pub mod parse_errors {
440    use super::*;
441
442    /// Details about a `ExpectedStaticPolicy` error.
443    #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
444    #[error("expected a static policy, got a template containing the slot {}", slot.id)]
445    #[diagnostic(help("try removing the template slot(s) from this policy"))]
446    pub struct ExpectedStaticPolicy {
447        /// Slot that was found (which is not valid in a static policy)
448        pub(crate) slot: ast::Slot,
449    }
450
451    impl From<ast::UnexpectedSlotError> for ExpectedStaticPolicy {
452        fn from(err: ast::UnexpectedSlotError) -> Self {
453            match err {
454                ast::UnexpectedSlotError::FoundSlot(slot) => Self { slot },
455            }
456        }
457    }
458
459    /// Details about a `ExpectedTemplate` error.
460    #[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
461    #[error("expected a template, got a static policy")]
462    #[diagnostic(help("a template should include slot(s) `?principal` or `?resource`"))]
463    pub struct ExpectedTemplate {
464        /// A private field, just so the public interface notes this as a
465        /// private-fields struct and not a empty-fields struct for semver
466        /// purposes (e.g., consumers cannot construct this type with
467        /// `ExpectedTemplate {}`)
468        _dummy: (),
469    }
470
471    impl ExpectedTemplate {
472        pub(crate) fn new() -> Self {
473            Self { _dummy: () }
474        }
475    }
476}
477
478/// Error surrounding EntityUIds/Template slots in policy scopes
479#[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
480pub enum RefCreationError {
481    /// Error surrounding EntityUIds/Template slots in policy scopes
482    #[error("expected {}, got: {got}", match .expected { Either::Left(r) => r.to_string(), Either::Right((r1, r2)) => format!("{r1} or {r2}") })]
483    RefCreation {
484        /// What kinds of references the given scope clause required.
485        /// Some scope clauses require exactly one kind of reference, some require one of two
486        expected: Either<Ref, (Ref, Ref)>,
487        /// The kind of reference that was present in the policy
488        got: Ref,
489    },
490}
491
492impl RefCreationError {
493    /// Constructor for when a policy scope requires exactly one kind of reference
494    pub fn one_expected(expected: Ref, got: Ref) -> Self {
495        Self::RefCreation {
496            expected: Either::Left(expected),
497            got,
498        }
499    }
500
501    /// Constructor for when a policy scope requires one of two kinds of references
502    pub fn two_expected(r1: Ref, r2: Ref, got: Ref) -> Self {
503        let expected = Either::Right((r1, r2));
504        Self::RefCreation { expected, got }
505    }
506}
507
508/// The 3 kinds of literals that can be in a policy scope
509#[derive(Debug, Clone, PartialEq, Eq)]
510pub enum Ref {
511    /// A single entity uids
512    Single,
513    /// A list of entity uids
514    Set,
515    /// A template slot
516    Template,
517}
518
519impl std::fmt::Display for Ref {
520    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
521        match self {
522            Ref::Single => write!(f, "single entity uid"),
523            Ref::Template => write!(f, "template slot"),
524            Ref::Set => write!(f, "set of entity uids"),
525        }
526    }
527}
528
529/// Error when `is` appears in the policy scope in a position where it is
530/// forbidden.
531#[derive(Debug, Clone, Diagnostic, Error, PartialEq, Eq)]
532pub enum InvalidIsError {
533    /// The action scope may not contain an `is`
534    #[error("`is` cannot appear in the action scope")]
535    #[diagnostic(help("try moving `action is ..` into a `when` condition"))]
536    ActionScope,
537    /// An `is` cannot appear with this operator in the policy scope
538    #[error("`is` cannot appear in the scope at the same time as `{0}`")]
539    #[diagnostic(help("try moving `is` into a `when` condition"))]
540    WrongOp(cst::RelOp),
541}
542
543/// Error from the CST parser.
544#[derive(Clone, Debug, Error, PartialEq, Eq)]
545pub struct ToCSTError {
546    err: OwnedRawParseError,
547}
548
549impl ToCSTError {
550    /// Extract a primary source span locating the error.
551    pub fn primary_source_span(&self) -> SourceSpan {
552        match &self.err {
553            OwnedRawParseError::InvalidToken { location } => SourceSpan::from(*location),
554            OwnedRawParseError::UnrecognizedEof { location, .. } => SourceSpan::from(*location),
555            OwnedRawParseError::UnrecognizedToken {
556                token: (token_start, _, token_end),
557                ..
558            } => SourceSpan::from(*token_start..*token_end),
559            OwnedRawParseError::ExtraToken {
560                token: (token_start, _, token_end),
561            } => SourceSpan::from(*token_start..*token_end),
562            OwnedRawParseError::User { error } => error.loc.span,
563        }
564    }
565
566    pub(crate) fn from_raw_parse_err(err: RawParseError<'_>) -> Self {
567        Self {
568            err: err.map_token(|token| token.to_string()),
569        }
570    }
571
572    pub(crate) fn from_raw_err_recovery(recovery: RawErrorRecovery<'_>) -> Self {
573        Self::from_raw_parse_err(recovery.error)
574    }
575}
576
577impl Display for ToCSTError {
578    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
579        match &self.err {
580            OwnedRawParseError::InvalidToken { .. } => write!(f, "invalid token"),
581            OwnedRawParseError::UnrecognizedEof { .. } => write!(f, "unexpected end of input"),
582            OwnedRawParseError::UnrecognizedToken {
583                token: (_, token, _),
584                ..
585            } => write!(f, "unexpected token `{token}`"),
586            OwnedRawParseError::ExtraToken {
587                token: (_, token, _),
588                ..
589            } => write!(f, "extra token `{token}`"),
590            OwnedRawParseError::User { error } => write!(f, "{error}"),
591        }
592    }
593}
594
595impl Diagnostic for ToCSTError {
596    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
597        let primary_source_span = self.primary_source_span();
598        let labeled_span = match &self.err {
599            OwnedRawParseError::InvalidToken { .. } => LabeledSpan::underline(primary_source_span),
600            OwnedRawParseError::UnrecognizedEof { expected, .. } => LabeledSpan::new_with_span(
601                expected_to_string(expected, &POLICY_TOKEN_CONFIG),
602                primary_source_span,
603            ),
604            OwnedRawParseError::UnrecognizedToken { expected, .. } => LabeledSpan::new_with_span(
605                expected_to_string(expected, &POLICY_TOKEN_CONFIG),
606                primary_source_span,
607            ),
608            OwnedRawParseError::ExtraToken { .. } => LabeledSpan::underline(primary_source_span),
609            OwnedRawParseError::User { .. } => LabeledSpan::underline(primary_source_span),
610        };
611        Some(Box::new(iter::once(labeled_span)))
612    }
613}
614
615/// Defines configurable rules for how tokens in an `UnrecognizedToken` or
616/// `UnrecognizedEof` error should be displayed to users.
617#[derive(Debug)]
618pub struct ExpectedTokenConfig {
619    /// Defines user-friendly names for tokens used by our parser. Keys are the
620    /// names of tokens as defined in the `.lalrpop` grammar file. A token may
621    /// be omitted from this map if the name is already friendly enough.
622    pub friendly_token_names: HashMap<&'static str, &'static str>,
623
624    /// Some tokens defined in our grammar always cause later processing to fail.
625    /// Our policy grammar defines a token for the mod operator `%`, but we
626    /// reject any CST that uses the operator. To reduce confusion we filter
627    /// these from the list of expected tokens in an error message.
628    pub impossible_tokens: HashSet<&'static str>,
629
630    /// Both our policy and schema grammar have a generic identifier token
631    /// and some more specific identifier tokens that we use to parse specific
632    /// constructs. It is very often not useful to explicitly list out all of
633    /// these special identifier because the parser really just wants any
634    /// generic identifier. That it would accept these does not give any
635    /// useful information.
636    pub special_identifier_tokens: HashSet<&'static str>,
637
638    /// If this token is expected, then the parser expected a generic identifier, so
639    /// we omit the specific identifiers favor of saying we expect an "identifier".
640    pub identifier_sentinel: &'static str,
641
642    /// Special identifiers that may be worth displaying even if the parser
643    /// wants a generic identifier. These can tokens will be parsed as something
644    /// other than an identifier when they occur as the first token in an
645    /// expression (or a type, in the case of the schema grammar).
646    pub first_set_identifier_tokens: HashSet<&'static str>,
647
648    /// If this token is expected, then the parser was looking to start parsing
649    /// an expression (or type, in the schema). We know that we should report the
650    /// tokens that aren't parsed as identifiers at the start of an expression.
651    pub first_set_sentinel: &'static str,
652}
653
654lazy_static! {
655    static ref POLICY_TOKEN_CONFIG: ExpectedTokenConfig = ExpectedTokenConfig {
656        friendly_token_names: HashMap::from([
657            ("TRUE", "`true`"),
658            ("FALSE", "`false`"),
659            ("IF", "`if`"),
660            ("PERMIT", "`permit`"),
661            ("FORBID", "`forbid`"),
662            ("WHEN", "`when`"),
663            ("UNLESS", "`unless`"),
664            ("IN", "`in`"),
665            ("HAS", "`has`"),
666            ("LIKE", "`like`"),
667            ("IS", "`is`"),
668            ("THEN", "`then`"),
669            ("ELSE", "`else`"),
670            ("PRINCIPAL", "`principal`"),
671            ("ACTION", "`action`"),
672            ("RESOURCE", "`resource`"),
673            ("CONTEXT", "`context`"),
674            ("PRINCIPAL_SLOT", "`?principal`"),
675            ("RESOURCE_SLOT", "`?resource`"),
676            ("IDENTIFIER", "identifier"),
677            ("NUMBER", "number"),
678            ("STRINGLIT", "string literal"),
679        ]),
680        impossible_tokens: HashSet::from(["\"=\"", "\"%\"", "\"/\"", "OTHER_SLOT"]),
681        special_identifier_tokens: HashSet::from([
682            "PERMIT",
683            "FORBID",
684            "WHEN",
685            "UNLESS",
686            "IN",
687            "HAS",
688            "LIKE",
689            "IS",
690            "THEN",
691            "ELSE",
692            "PRINCIPAL",
693            "ACTION",
694            "RESOURCE",
695            "CONTEXT",
696        ]),
697        identifier_sentinel: "IDENTIFIER",
698        first_set_identifier_tokens: HashSet::from(["TRUE", "FALSE", "IF"]),
699        first_set_sentinel: "\"!\"",
700    };
701}
702
703/// Format lalrpop expected error messages
704pub fn expected_to_string(expected: &[String], config: &ExpectedTokenConfig) -> Option<String> {
705    let mut expected = expected
706        .iter()
707        .filter(|e| !config.impossible_tokens.contains(e.as_str()))
708        .map(|e| e.as_str())
709        .collect::<BTreeSet<_>>();
710    if expected.contains(config.identifier_sentinel) {
711        for token in config.special_identifier_tokens.iter() {
712            expected.remove(*token);
713        }
714        if !expected.contains(config.first_set_sentinel) {
715            for token in config.first_set_identifier_tokens.iter() {
716                expected.remove(*token);
717            }
718        }
719    }
720    if expected.is_empty() {
721        return None;
722    }
723
724    let mut expected_string = "expected ".to_owned();
725    // PANIC SAFETY Shouldn't be `Err` since we're writing strings to a string
726    #[allow(clippy::expect_used)]
727    join_with_conjunction(
728        &mut expected_string,
729        "or",
730        expected,
731        |f, token| match config.friendly_token_names.get(token) {
732            Some(friendly_token_name) => write!(f, "{}", friendly_token_name),
733            None => write!(f, "{}", token.replace('"', "`")),
734        },
735    )
736    .expect("failed to format expected tokens");
737    Some(expected_string)
738}
739
740/// Multiple parse errors.
741#[derive(Clone, Debug, Default, PartialEq, Eq)]
742pub struct ParseErrors(pub Vec<ParseError>);
743
744impl ParseErrors {
745    const DESCRIPTION_IF_EMPTY: &'static str = "unknown parse error";
746
747    /// Constructs a new, empty `ParseErrors`.
748    pub fn new() -> Self {
749        ParseErrors(Vec::new())
750    }
751
752    /// Constructs a new, empty `ParseErrors` with the specified capacity.
753    pub fn with_capacity(capacity: usize) -> Self {
754        ParseErrors(Vec::with_capacity(capacity))
755    }
756
757    /// Add an error to the `ParseErrors`
758    pub(super) fn push(&mut self, err: impl Into<ParseError>) {
759        self.0.push(err.into());
760    }
761
762    /// returns a Vec with stringified versions of the ParseErrors
763    #[deprecated(
764        since = "3.3.0",
765        note = "Use `.iter().map(ToString::to_string)` instead; note that converting to string discards some information from the error which is available through `miette::Diagnostic`"
766    )]
767    pub fn errors_as_strings(&self) -> Vec<String> {
768        self.0.iter().map(ToString::to_string).collect()
769    }
770}
771
772impl Display for ParseErrors {
773    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
774        match self.first() {
775            Some(first_err) => write!(f, "{first_err}"), // intentionally showing only the first error; see #326
776            None => write!(f, "{}", Self::DESCRIPTION_IF_EMPTY),
777        }
778    }
779}
780
781impl std::error::Error for ParseErrors {
782    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
783        self.first().and_then(std::error::Error::source)
784    }
785
786    #[allow(deprecated)]
787    fn description(&self) -> &str {
788        match self.first() {
789            Some(first_err) => first_err.description(),
790            None => Self::DESCRIPTION_IF_EMPTY,
791        }
792    }
793
794    #[allow(deprecated)]
795    fn cause(&self) -> Option<&dyn std::error::Error> {
796        self.first().and_then(std::error::Error::cause)
797    }
798}
799
800// Except for `.related()`, everything else is forwarded to the first error, if it is present.
801// This ensures that users who only use `Display`, `.code()`, `.labels()` etc, still get rich
802// information for the first error, even if they don't realize there are multiple errors here.
803// See #326.
804impl Diagnostic for ParseErrors {
805    fn related<'a>(&'a self) -> Option<Box<dyn Iterator<Item = &'a dyn Diagnostic> + 'a>> {
806        // the .related() on the first error, and then the 2nd through Nth errors (but not their own .related())
807        let mut errs = self.iter().map(|err| err as &dyn Diagnostic);
808        errs.next().map(move |first_err| match first_err.related() {
809            Some(first_err_related) => Box::new(first_err_related.chain(errs)),
810            None => Box::new(errs) as Box<dyn Iterator<Item = _>>,
811        })
812    }
813
814    fn code<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
815        self.first().and_then(Diagnostic::code)
816    }
817
818    fn severity(&self) -> Option<miette::Severity> {
819        self.first().and_then(Diagnostic::severity)
820    }
821
822    fn help<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
823        self.first().and_then(Diagnostic::help)
824    }
825
826    fn url<'a>(&'a self) -> Option<Box<dyn Display + 'a>> {
827        self.first().and_then(Diagnostic::url)
828    }
829
830    fn source_code(&self) -> Option<&dyn miette::SourceCode> {
831        self.first().and_then(Diagnostic::source_code)
832    }
833
834    fn labels(&self) -> Option<Box<dyn Iterator<Item = LabeledSpan> + '_>> {
835        self.first().and_then(Diagnostic::labels)
836    }
837
838    fn diagnostic_source(&self) -> Option<&dyn Diagnostic> {
839        self.first().and_then(Diagnostic::diagnostic_source)
840    }
841}
842
843impl AsRef<Vec<ParseError>> for ParseErrors {
844    fn as_ref(&self) -> &Vec<ParseError> {
845        &self.0
846    }
847}
848
849impl AsMut<Vec<ParseError>> for ParseErrors {
850    fn as_mut(&mut self) -> &mut Vec<ParseError> {
851        &mut self.0
852    }
853}
854
855impl AsRef<[ParseError]> for ParseErrors {
856    fn as_ref(&self) -> &[ParseError] {
857        self.0.as_ref()
858    }
859}
860
861impl AsMut<[ParseError]> for ParseErrors {
862    fn as_mut(&mut self) -> &mut [ParseError] {
863        self.0.as_mut()
864    }
865}
866
867impl Deref for ParseErrors {
868    type Target = Vec<ParseError>;
869
870    fn deref(&self) -> &Self::Target {
871        &self.0
872    }
873}
874
875impl DerefMut for ParseErrors {
876    fn deref_mut(&mut self) -> &mut Self::Target {
877        &mut self.0
878    }
879}
880
881impl<T: Into<ParseError>> From<T> for ParseErrors {
882    fn from(err: T) -> Self {
883        vec![err.into()].into()
884    }
885}
886
887impl From<Vec<ParseError>> for ParseErrors {
888    fn from(errs: Vec<ParseError>) -> Self {
889        ParseErrors(errs)
890    }
891}
892
893impl<T: Into<ParseError>> FromIterator<T> for ParseErrors {
894    fn from_iter<I: IntoIterator<Item = T>>(errs: I) -> Self {
895        ParseErrors(errs.into_iter().map(Into::into).collect())
896    }
897}
898
899impl<T: Into<ParseError>> Extend<T> for ParseErrors {
900    fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
901        self.0.extend(iter.into_iter().map(Into::into))
902    }
903}
904
905impl IntoIterator for ParseErrors {
906    type Item = ParseError;
907    type IntoIter = std::vec::IntoIter<Self::Item>;
908
909    fn into_iter(self) -> Self::IntoIter {
910        self.0.into_iter()
911    }
912}
913
914impl<'a> IntoIterator for &'a ParseErrors {
915    type Item = &'a ParseError;
916    type IntoIter = std::slice::Iter<'a, ParseError>;
917
918    fn into_iter(self) -> Self::IntoIter {
919        self.0.iter()
920    }
921}
922
923impl<'a> IntoIterator for &'a mut ParseErrors {
924    type Item = &'a mut ParseError;
925    type IntoIter = std::slice::IterMut<'a, ParseError>;
926
927    fn into_iter(self) -> Self::IntoIter {
928        self.0.iter_mut()
929    }
930}