cedar_policy_core/
parser.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
17//! This module contains the parser for the Cedar language.
18
19/// Concrete Syntax Tree def used as parser first pass
20pub mod cst;
21/// Step two: convert CST to package AST
22pub mod cst_to_ast;
23/// error handling utilities
24pub mod err;
25/// implementations for formatting, like `Display`
26mod fmt;
27pub use fmt::join_with_conjunction;
28/// Source location struct
29mod loc;
30pub use loc::Loc;
31/// Metadata wrapper for CST Nodes
32mod node;
33pub use node::Node;
34/// Step one: Convert text to CST
35pub mod text_to_cst;
36/// Utility functions to unescape string literals
37pub mod unescape;
38/// Utility functions
39pub mod util;
40
41use smol_str::SmolStr;
42use std::collections::HashMap;
43
44use crate::ast;
45use crate::ast::RestrictedExpressionParseError;
46use crate::est;
47
48/// simple main function for parsing policies
49/// generates numbered ids
50pub fn parse_policyset(text: &str) -> Result<ast::PolicySet, err::ParseErrors> {
51    let cst = text_to_cst::parse_policies(text)?;
52    cst.to_policyset()
53}
54
55/// Like `parse_policyset()`, but also returns the (lossless) original text of
56/// each individual policy.
57/// INVARIANT: The `PolicyId` of every `Policy` and `Template` returned by the
58/// `policies()` and `templates()` methods on the returned `Policy` _must_
59/// appear as a key in the returned map.
60pub fn parse_policyset_and_also_return_policy_text(
61    text: &str,
62) -> Result<(HashMap<ast::PolicyID, Option<&str>>, ast::PolicySet), err::ParseErrors> {
63    let cst = text_to_cst::parse_policies(text)?;
64    let pset = cst.to_policyset()?;
65    // PANIC SAFETY Shouldn't be `none` since `parse_policies()` and `to_policyset()` didn't return `Err`
66    #[allow(clippy::expect_used)]
67    // PANIC SAFETY Indexing is safe because of how the `SourceSpan` is constructed
68    #[allow(clippy::indexing_slicing)]
69    // The `PolicyID` keys for `texts` are generated by
70    // `cst.with_generated_policyids()`. This is the same method used to
71    // generate the ids for policies and templates in `cst.to_policyset()`,
72    // so every static policy and template in the policy set will have its
73    // `PolicyId` present as a key in this map.
74    let texts = cst
75        .with_generated_policyids()
76        .expect("shouldn't be `None` since `parse_policies` and `to_policyset` didn't return `Err`")
77        .map(|(id, policy)| {
78            if let Some(loc) = &policy.loc {
79                (id, Some(&text[loc.start()..loc.end()]))
80            } else {
81                (id, None)
82            }
83        })
84        .collect::<HashMap<ast::PolicyID, Option<&str>>>();
85    Ok((texts, pset))
86}
87
88/// Like `parse_policyset()`, but also returns the (lossless) ESTs -- that is,
89/// the ESTs of the original policies without any of the lossy transforms
90/// involved in converting to AST.
91pub fn parse_policyset_to_ests_and_pset(
92    text: &str,
93) -> Result<(HashMap<ast::PolicyID, est::Policy>, ast::PolicySet), err::ParseErrors> {
94    let cst = text_to_cst::parse_policies(text)?;
95    let pset = cst.to_policyset()?;
96    // PANIC SAFETY Shouldn't be `None` since `parse_policies()` and `to_policyset()` didn't return `Err`
97    #[allow(clippy::expect_used)]
98    let ests = cst
99        .with_generated_policyids()
100        .expect("missing policy set node")
101        .map(|(id, policy)| {
102            let p = policy.node.as_ref().expect("missing policy node").clone();
103            Ok((id, p.try_into()?))
104        })
105        .collect::<Result<HashMap<ast::PolicyID, est::Policy>, err::ParseErrors>>()?;
106    Ok((ests, pset))
107}
108
109/// Main function for parsing a policy _or_ template. In either case, the
110/// returned value will be a [`ast::Template`].
111/// If `id` is Some, then the resulting template will have that `id`.
112/// If the `id` is None, the parser will use "policy0".
113pub fn parse_policy_or_template(
114    id: Option<ast::PolicyID>,
115    text: &str,
116) -> Result<ast::Template, err::ParseErrors> {
117    let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
118    let cst = text_to_cst::parse_policy(text)?;
119    cst.to_template(id)
120}
121
122/// Like `parse_policy_or_template()`, but also returns the (lossless) EST -- that
123/// is, the EST of the original policy/template without any of the lossy transforms
124/// involved in converting to AST.
125pub fn parse_policy_or_template_to_est_and_ast(
126    id: Option<ast::PolicyID>,
127    text: &str,
128) -> Result<(est::Policy, ast::Template), err::ParseErrors> {
129    let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
130    let cst = text_to_cst::parse_policy(text)?;
131    let ast = cst.to_template(id)?;
132    let est = cst.try_into_inner()?.try_into()?;
133    Ok((est, ast))
134}
135
136/// Main function for parsing a template.
137/// Will return an error if provided with a static policy.
138/// If `id` is Some, then the resulting policy will have that `id`.
139/// If the `id` is None, the parser will use "policy0".
140pub fn parse_template(
141    id: Option<ast::PolicyID>,
142    text: &str,
143) -> Result<ast::Template, err::ParseErrors> {
144    let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
145    let cst = text_to_cst::parse_policy(text)?;
146    let template = cst.to_template(id)?;
147    validate_template_has_slots(template, cst)
148}
149
150/// Main function for parsing a (static) policy.
151/// Will return an error if provided with a template.
152/// If `id` is Some, then the resulting policy will have that `id`.
153/// If the `id` is None, the parser will use "policy0".
154pub fn parse_policy(
155    id: Option<ast::PolicyID>,
156    text: &str,
157) -> Result<ast::StaticPolicy, err::ParseErrors> {
158    let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
159    let cst = text_to_cst::parse_policy(text)?;
160    cst.to_policy(id)
161}
162
163/// Like `parse_policy()`, but also returns the (lossless) EST -- that is, the
164/// EST of the original policy without any of the lossy transforms involved in
165/// converting to AST.
166pub fn parse_policy_to_est_and_ast(
167    id: Option<ast::PolicyID>,
168    text: &str,
169) -> Result<(est::Policy, ast::StaticPolicy), err::ParseErrors> {
170    let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
171    let cst = text_to_cst::parse_policy(text)?;
172    let ast = cst.to_policy(id)?;
173    let est = cst.try_into_inner()?.try_into()?;
174    Ok((est, ast))
175}
176
177/// Parse a policy or template (either one works) to its EST representation
178pub fn parse_policy_or_template_to_est(text: &str) -> Result<est::Policy, err::ParseErrors> {
179    // We parse to EST and AST even though we only want the EST because some
180    // checks are applied by the CST-to-AST conversion and not CST-to-EST, and
181    // we do not want to return any EST if the policy text would not parse
182    // normally.
183    parse_policy_or_template_to_est_and_ast(None, text).map(|(est, _ast)| est)
184}
185
186/// parse an Expr
187///
188/// Private to this crate. Users outside Core should use `Expr`'s `FromStr` impl
189/// or its constructors
190pub(crate) fn parse_expr(ptext: &str) -> Result<ast::Expr, err::ParseErrors> {
191    let cst = text_to_cst::parse_expr(ptext)?;
192    cst.to_expr::<ast::ExprBuilder<()>>()
193}
194
195/// parse a RestrictedExpr
196///
197/// Private to this crate. Users outside Core should use `RestrictedExpr`'s
198/// `FromStr` impl or its constructors
199pub(crate) fn parse_restrictedexpr(
200    ptext: &str,
201) -> Result<ast::RestrictedExpr, RestrictedExpressionParseError> {
202    let expr = parse_expr(ptext)?;
203    Ok(ast::RestrictedExpr::new(expr)?)
204}
205
206/// parse an EntityUID
207///
208/// Private to this crate. Users outside Core should use `EntityUID`'s `FromStr`
209/// impl or its constructors
210pub(crate) fn parse_euid(euid: &str) -> Result<ast::EntityUID, err::ParseErrors> {
211    let cst = text_to_cst::parse_ref(euid)?;
212    cst.to_ref()
213}
214
215/// parse an [`ast::InternalName`]
216///
217/// Private to this crate. Users outside Core should use [`ast::InternalName`]'s
218/// `FromStr` impl or its constructors
219pub(crate) fn parse_internal_name(name: &str) -> Result<ast::InternalName, err::ParseErrors> {
220    let cst = text_to_cst::parse_name(name)?;
221    cst.to_internal_name()
222}
223
224/// parse a string into an ast::Literal (does not support expressions)
225///
226/// Private to this crate. Users outside Core should use `Literal`'s `FromStr` impl
227/// or its constructors
228pub(crate) fn parse_literal(val: &str) -> Result<ast::Literal, err::LiteralParseError> {
229    let cst = text_to_cst::parse_primary(val)?;
230    match cst.to_expr::<ast::ExprBuilder<()>>() {
231        Ok(ast) => match ast.expr_kind() {
232            ast::ExprKind::Lit(v) => Ok(v.clone()),
233            _ => Err(err::LiteralParseError::InvalidLiteral(ast)),
234        },
235        Err(errs) => Err(err::LiteralParseError::Parse(errs)),
236    }
237}
238
239/// parse a string into an internal Cedar string
240///
241/// This performs unescaping and validation, returning
242/// a String suitable for an attr, eid, or literal.
243///
244/// Quote handling is as if the input is surrounded by
245/// double quotes ("{val}").
246///
247/// It does not return a string suitable for a pattern. Use the
248/// full expression parser for those.
249pub fn parse_internal_string(val: &str) -> Result<SmolStr, err::ParseErrors> {
250    // we need to add quotes for this to be a valid string literal
251    let cst = text_to_cst::parse_primary(&format!(r#""{val}""#))?;
252    cst.to_string_literal::<ast::ExprBuilder<()>>()
253}
254
255/// parse an identifier
256///
257/// Private to this crate. Users outside Core should use `Id`'s `FromStr` impl
258/// or its constructors
259pub(crate) fn parse_ident(id: &str) -> Result<ast::Id, err::ParseErrors> {
260    let cst = text_to_cst::parse_ident(id)?;
261    cst.to_valid_ident()
262}
263
264/// parse an `AnyId`
265///
266/// Private to this crate. Users outside Core should use `AnyId`'s `FromStr` impl
267/// or its constructors
268pub(crate) fn parse_anyid(id: &str) -> Result<ast::AnyId, err::ParseErrors> {
269    let cst = text_to_cst::parse_ident(id)?;
270    cst.to_any_ident()
271}
272
273/// Check that a template contains slots. Return the template if it does, or an
274/// error otherwise.
275fn validate_template_has_slots(
276    template: ast::Template,
277    cst: Node<Option<cst::Policy>>,
278) -> Result<ast::Template, err::ParseErrors> {
279    if template.slots().count() == 0 {
280        Err(err::ToASTError::new(err::ToASTErrorKind::expected_template(), cst.loc).into())
281    } else {
282        Ok(template)
283    }
284}
285
286/// Utilities used in tests in this file (and maybe other files in this crate)
287#[cfg(test)]
288// PANIC SAFETY unit tests
289#[allow(clippy::panic)]
290pub(crate) mod test_utils {
291    use super::err::ParseErrors;
292    use crate::test_utils::*;
293
294    /// Expect that the given `ParseErrors` contains a particular number of errors.
295    ///
296    /// `src` is the original input text (which the miette labels index into).
297    #[track_caller] // report the caller's location as the location of the panic, not the location in this function
298    pub fn expect_n_errors(src: &str, errs: &ParseErrors, n: usize) {
299        assert_eq!(
300            errs.len(),
301            n,
302            "for the following input:\n{src}\nexpected {n} error(s), but saw {}\nactual errors were:\n{:?}", // the Debug representation of `miette::Report` is the pretty one, for some reason
303            errs.len(),
304            miette::Report::new(errs.clone())
305        );
306    }
307
308    /// Expect that the given `ParseErrors` contains at least one error with the given `ExpectedErrorMessage`.
309    ///
310    /// `src` is the original input text (which the miette labels index into).
311    #[track_caller] // report the caller's location as the location of the panic, not the location in this function
312    pub fn expect_some_error_matches(
313        src: &str,
314        errs: &ParseErrors,
315        msg: &ExpectedErrorMessage<'_>,
316    ) {
317        assert!(
318            errs.iter().any(|e| msg.matches(e)),
319            "for the following input:\n{src}\nexpected some error to match the following:\n{msg}\nbut actual errors were:\n{:?}", // the Debug representation of `miette::Report` is the pretty one, for some reason
320            miette::Report::new(errs.clone()),
321        );
322    }
323
324    /// Expect that the given `ParseErrors` contains exactly one error, and that it matches the given `ExpectedErrorMessage`.
325    ///
326    /// `src` is the original input text (which the miette labels index into).
327    #[track_caller] // report the caller's location as the location of the panic, not the location in this function
328    pub fn expect_exactly_one_error(src: &str, errs: &ParseErrors, msg: &ExpectedErrorMessage<'_>) {
329        match errs.len() {
330            0 => panic!("for the following input:\n{src}\nexpected an error, but the `ParseErrors` was empty"),
331            1 => {
332                let err = errs.iter().next().expect("already checked that len was 1");
333                expect_err(src, &miette::Report::new(err.clone()), msg);
334            }
335            n => panic!(
336                "for the following input:\n{src}\nexpected only one error, but got {n}. Expected to match the following:\n{msg}\nbut actual errors were:\n{:?}", // the Debug representation of `miette::Report` is the pretty one, for some reason
337                miette::Report::new(errs.clone()),
338            )
339        }
340    }
341}
342
343// PANIC SAFETY: Unit Test Code
344#[allow(clippy::panic, clippy::indexing_slicing)]
345#[allow(clippy::cognitive_complexity)]
346#[cfg(test)]
347/// Tests for the top-level parsing APIs
348mod tests {
349
350    use super::*;
351
352    use crate::ast::test_generators::*;
353    use crate::ast::{Eid, Literal, Value};
354    use crate::evaluator as eval;
355    use crate::extensions::Extensions;
356    use crate::parser::err::*;
357    use crate::parser::test_utils::*;
358    use crate::test_utils::*;
359    use cool_asserts::assert_matches;
360    use std::collections::HashSet;
361    use std::sync::Arc;
362
363    #[test]
364    fn test_template_parsing() {
365        for template in all_templates() {
366            let id = template.id();
367            let src = format!("{template}");
368            let parsed =
369                parse_policy_or_template(Some(ast::PolicyID::from_string(id)), &src).unwrap();
370            assert_eq!(
371                parsed.slots().collect::<HashSet<_>>(),
372                template.slots().collect::<HashSet<_>>()
373            );
374            assert_eq!(parsed.id(), template.id());
375            assert_eq!(parsed.effect(), template.effect());
376            assert_eq!(
377                parsed.principal_constraint(),
378                template.principal_constraint()
379            );
380            assert_eq!(parsed.action_constraint(), template.action_constraint());
381            assert_eq!(parsed.resource_constraint(), template.resource_constraint());
382            match (
383                parsed.non_scope_constraints(),
384                template.non_scope_constraints(),
385            ) {
386                (Some(parsed), Some(template)) => {
387                    assert!(
388                        parsed.eq_shape(template),
389                        "{:?} and {:?} should have the same shape.",
390                        parsed,
391                        template
392                    );
393                }
394                (Some(_), None) | (None, Some(_)) => {
395                    panic!(
396                        "{:?} and {:?} should have the same shape.",
397                        parsed, template
398                    )
399                }
400                (None, None) => (),
401            }
402        }
403    }
404
405    #[test]
406    fn test_error_out() {
407        let src = r#"
408            permit(principal:p,action:a,resource:r)
409            when{w or if c but not z} // expr error
410            unless{u if c else d or f} // expr error
411            advice{"doit"};
412
413            permit(principality in Group::"jane_friends", // policy error
414            action in [PhotoOp::"view", PhotoOp::"comment"],
415            resource in Album::"jane_trips");
416
417            forbid(principal, action, resource)
418            when   { "private" in resource.tags }
419            unless { resource in principal.account };
420        "#;
421        let errs = parse_policyset(src).expect_err("expected parsing to fail");
422        let unrecognized_tokens = vec![
423            ("or", "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `}`, `has`, `in`, `is`, or `like`"),
424            ("if", "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `}`, `has`, `in`, `is`, or `like`"),
425        ];
426        for (token, label) in unrecognized_tokens {
427            expect_some_error_matches(
428                src,
429                &errs,
430                &ExpectedErrorMessageBuilder::error(&format!("unexpected token `{token}`"))
431                    .exactly_one_underline_with_label(token, label)
432                    .build(),
433            );
434        }
435        expect_n_errors(src, &errs, 2);
436        assert!(errs.iter().all(|err| matches!(err, ParseError::ToCST(_))));
437    }
438
439    #[test]
440    fn entity_literals1() {
441        let src = r#"Test::{ test : "Test" }"#;
442        let errs = parse_euid(src).unwrap_err();
443        expect_exactly_one_error(
444            src,
445            &errs,
446            &ExpectedErrorMessageBuilder::error("invalid entity literal: Test::{test: \"Test\"}")
447                .help("entity literals should have a form like `Namespace::User::\"alice\"`")
448                .exactly_one_underline("Test::{ test : \"Test\" }")
449                .build(),
450        );
451    }
452
453    #[test]
454    fn entity_literals2() {
455        let src = r#"permit(principal == Test::{ test : "Test" }, action, resource);"#;
456        let errs = parse_policy(None, src).unwrap_err();
457        expect_exactly_one_error(
458            src,
459            &errs,
460            &ExpectedErrorMessageBuilder::error("invalid entity literal: Test::{test: \"Test\"}")
461                .help("entity literals should have a form like `Namespace::User::\"alice\"`")
462                .exactly_one_underline("Test::{ test : \"Test\" }")
463                .build(),
464        );
465    }
466
467    #[test]
468    fn interpret_exprs() {
469        let request = eval::test::basic_request();
470        let entities = eval::test::basic_entities();
471        let exts = Extensions::none();
472        let evaluator = eval::Evaluator::new(request, &entities, exts);
473        // The below tests check not only that we get the expected `Value`, but
474        // that it has the expected source location.
475        // We have to check that separately because the `PartialEq` and `Eq`
476        // impls for `Value` do not compare source locations.
477        // This is somewhat a test of the evaluator, not just the parser; but
478        // the actual evaluator unit tests do not use the parser and thus do
479        // not have source locations attached to their input expressions, so
480        // this file is where we effectively perform evaluator tests related to
481        // propagating source locations from expressions to values.
482
483        // bools
484        let src = "false";
485        let expr = parse_expr(src).unwrap();
486        let val = evaluator.interpret_inline_policy(&expr).unwrap();
487        assert_eq!(val, Value::from(false));
488        assert_eq!(val.source_loc(), Some(&Loc::new(0..5, Arc::from(src))));
489
490        let src = "true && true";
491        let expr = parse_expr(src).unwrap();
492        let val = evaluator.interpret_inline_policy(&expr).unwrap();
493        assert_eq!(val, Value::from(true));
494        assert_eq!(val.source_loc(), Some(&Loc::new(0..12, Arc::from(src))));
495
496        let src = "!true || false && !true";
497        let expr = parse_expr(src).unwrap();
498        let val = evaluator.interpret_inline_policy(&expr).unwrap();
499        assert_eq!(val, Value::from(false));
500        assert_eq!(val.source_loc(), Some(&Loc::new(0..23, Arc::from(src))));
501
502        let src = "!!!!true";
503        let expr = parse_expr(src).unwrap();
504        let val = evaluator.interpret_inline_policy(&expr).unwrap();
505        assert_eq!(val, Value::from(true));
506        assert_eq!(val.source_loc(), Some(&Loc::new(0..8, Arc::from(src))));
507
508        let src = r#"
509        if false || true != 4 then
510            600
511        else
512            -200
513        "#;
514        let expr = parse_expr(src).unwrap();
515        let val = evaluator.interpret_inline_policy(&expr).unwrap();
516        assert_eq!(val, Value::from(600));
517        assert_eq!(val.source_loc(), Some(&Loc::new(9..81, Arc::from(src))));
518    }
519
520    #[test]
521    fn interpret_membership() {
522        let request = eval::test::basic_request();
523        let entities = eval::test::rich_entities();
524        let exts = Extensions::none();
525        let evaluator = eval::Evaluator::new(request, &entities, exts);
526        // The below tests check not only that we get the expected `Value`, but
527        // that it has the expected source location.
528        // See note on this in the above test.
529
530        let src = r#"
531
532        test_entity_type::"child" in
533            test_entity_type::"unrelated"
534
535        "#;
536        let expr = parse_expr(src).unwrap();
537        let val = evaluator.interpret_inline_policy(&expr).unwrap();
538        assert_eq!(val, Value::from(false));
539        assert_eq!(val.source_loc(), Some(&Loc::new(10..80, Arc::from(src))));
540        // because "10..80" is hard to read, we also assert that the correct portion of `src` is indicated
541        assert_eq!(
542            val.source_loc().unwrap().snippet(),
543            Some(
544                r#"test_entity_type::"child" in
545            test_entity_type::"unrelated""#
546            )
547        );
548
549        let src = r#"
550
551        test_entity_type::"child" in
552            test_entity_type::"child"
553
554        "#;
555        let expr = parse_expr(src).unwrap();
556        let val = evaluator.interpret_inline_policy(&expr).unwrap();
557        assert_eq!(val, Value::from(true));
558        assert_eq!(val.source_loc(), Some(&Loc::new(10..76, Arc::from(src))));
559        assert_eq!(
560            val.source_loc().unwrap().snippet(),
561            Some(
562                r#"test_entity_type::"child" in
563            test_entity_type::"child""#
564            )
565        );
566
567        let src = r#"
568
569        other_type::"other_child" in
570            test_entity_type::"parent"
571
572        "#;
573        let expr = parse_expr(src).unwrap();
574        let val = evaluator.interpret_inline_policy(&expr).unwrap();
575        assert_eq!(val, Value::from(true));
576        assert_eq!(val.source_loc(), Some(&Loc::new(10..77, Arc::from(src))));
577        assert_eq!(
578            val.source_loc().unwrap().snippet(),
579            Some(
580                r#"other_type::"other_child" in
581            test_entity_type::"parent""#
582            )
583        );
584
585        let src = r#"
586
587        test_entity_type::"child" in
588            test_entity_type::"grandparent"
589
590        "#;
591        let expr = parse_expr(src).unwrap();
592        let val = evaluator.interpret_inline_policy(&expr).unwrap();
593        assert_eq!(val, Value::from(true));
594        assert_eq!(val.source_loc(), Some(&Loc::new(10..82, Arc::from(src))));
595        assert_eq!(
596            val.source_loc().unwrap().snippet(),
597            Some(
598                r#"test_entity_type::"child" in
599            test_entity_type::"grandparent""#
600            )
601        );
602    }
603
604    /// Tests parser+evaluator with relations `<`, `<=`, `>`, `&&`, `||`, `!=`
605    #[test]
606    fn interpret_relation() {
607        let request = eval::test::basic_request();
608        let entities = eval::test::basic_entities();
609        let exts = Extensions::none();
610        let evaluator = eval::Evaluator::new(request, &entities, exts);
611        // The below tests check not only that we get the expected `Value`, but
612        // that it has the expected source location.
613        // See note on this in the above test.
614
615        let src = r#"
616
617            3 < 2 || 2 > 3
618
619        "#;
620        let expr = parse_expr(src).unwrap();
621        let val = evaluator.interpret_inline_policy(&expr).unwrap();
622        assert_eq!(val, Value::from(false));
623        assert_eq!(val.source_loc(), Some(&Loc::new(14..28, Arc::from(src))));
624        // because "14..28" is hard to read, we also assert that the correct portion of `src` is indicated
625        assert_eq!(val.source_loc().unwrap().snippet(), Some("3 < 2 || 2 > 3"));
626
627        let src = r#"
628
629            7 <= 7 && 4 != 5
630
631        "#;
632        let expr = parse_expr(src).unwrap();
633        let val = evaluator.interpret_inline_policy(&expr).unwrap();
634        assert_eq!(val, Value::from(true));
635        assert_eq!(val.source_loc(), Some(&Loc::new(14..30, Arc::from(src))));
636        assert_eq!(
637            val.source_loc().unwrap().snippet(),
638            Some("7 <= 7 && 4 != 5")
639        );
640    }
641
642    /// Tests parser+evaluator with builtin methods `containsAll()`, `hasTag()`, `getTag()`
643    #[test]
644    fn interpret_methods() {
645        let src = r#"
646            [2, 3, "foo"].containsAll([3, "foo"])
647            && context.violations.isEmpty()
648            && principal.hasTag(resource.getTag(context.cur_time))
649        "#;
650        let request = eval::test::basic_request();
651        let entities = eval::test::basic_entities();
652        let exts = Extensions::none();
653        let evaluator = eval::Evaluator::new(request, &entities, exts);
654
655        let expr = parse_expr(src).unwrap();
656        assert_matches!(evaluator.interpret_inline_policy(&expr), Err(e) => {
657            expect_err(
658                src,
659                &miette::Report::new(e),
660                &ExpectedErrorMessageBuilder::error(r#"`test_entity_type::"test_resource"` does not have the tag `03:22:11`"#)
661                    .help(r#"`test_entity_type::"test_resource"` does not have any tags"#)
662                    .exactly_one_underline("resource.getTag(context.cur_time)")
663                    .build(),
664            );
665        });
666    }
667
668    #[test]
669    fn unquoted_tags() {
670        let src = r#"
671            principal.hasTag(foo)
672        "#;
673        assert_matches!(parse_expr(src), Err(e) => {
674            expect_err(
675                src,
676                &miette::Report::new(e),
677                &ExpectedErrorMessageBuilder::error("invalid variable: foo")
678                    .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `foo` in quotes to make a string?")
679                    .exactly_one_underline("foo")
680                    .build(),
681            );
682        });
683
684        let src = r#"
685            principal.getTag(foo)
686        "#;
687        assert_matches!(parse_expr(src), Err(e) => {
688            expect_err(
689                src,
690                &miette::Report::new(e),
691                &ExpectedErrorMessageBuilder::error("invalid variable: foo")
692                    .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `foo` in quotes to make a string?")
693                    .exactly_one_underline("foo")
694                    .build(),
695            );
696        });
697    }
698
699    #[test]
700    fn parse_exists() {
701        let result = parse_policyset(
702            r#"
703            permit(principal, action, resource)
704            when{ true };
705        "#,
706        );
707        assert!(!result.expect("parse error").is_empty());
708    }
709
710    #[test]
711    fn attr_named_tags() {
712        let src = r#"
713            permit(principal, action, resource)
714            when {
715                resource.tags.contains({k: "foo", v: "bar"})
716            };
717        "#;
718        parse_policy_to_est_and_ast(None, src)
719            .unwrap_or_else(|e| panic!("{:?}", &miette::Report::new(e)));
720    }
721
722    #[test]
723    fn test_parse_policyset() {
724        use crate::ast::PolicyID;
725        let multiple_policies = r#"
726            permit(principal, action, resource)
727            when { principal == resource.owner };
728
729            forbid(principal, action == Action::"modify", resource) // a comment
730            when { resource . highSecurity }; // intentionally not conforming to our formatter
731        "#;
732        let pset = parse_policyset(multiple_policies).expect("Should parse");
733        assert_eq!(pset.policies().count(), 2);
734        assert_eq!(pset.static_policies().count(), 2);
735        let (texts, pset) =
736            parse_policyset_and_also_return_policy_text(multiple_policies).expect("Should parse");
737        assert_eq!(pset.policies().count(), 2);
738        assert_eq!(pset.static_policies().count(), 2);
739        assert_eq!(texts.len(), 2);
740        assert_eq!(
741            texts.get(&PolicyID::from_string("policy0")),
742            Some(&Some(
743                r#"permit(principal, action, resource)
744            when { principal == resource.owner };"#
745            ))
746        );
747        assert_eq!(
748            texts.get(&PolicyID::from_string("policy1")),
749            Some(&Some(
750                r#"forbid(principal, action == Action::"modify", resource) // a comment
751            when { resource . highSecurity };"#
752            ))
753        );
754    }
755
756    #[test]
757    fn test_parse_string() {
758        // test idempotence
759        assert_eq!(
760            Eid::new(parse_internal_string(r"a\nblock\nid").expect("should parse")).escaped(),
761            r"a\nblock\nid",
762        );
763        parse_internal_string(r#"oh, no, a '! "#).expect("single quote should be fine");
764        parse_internal_string(r#"oh, no, a \"! and a \'! "#).expect("escaped quotes should parse");
765        let src = r#"oh, no, a "! "#;
766        let errs = parse_internal_string(src).expect_err("unescaped double quote not allowed");
767        expect_exactly_one_error(
768            src,
769            &errs,
770            &ExpectedErrorMessageBuilder::error("invalid token")
771                .exactly_one_underline("")
772                .build(),
773        );
774    }
775
776    #[test]
777    fn good_cst_bad_ast() {
778        let src = r#"
779            permit(principal, action, resource) when { principal.name.like == "3" };
780            "#;
781        let p = parse_policyset_to_ests_and_pset(src);
782        assert_matches!(p, Err(e) => expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: like").exactly_one_underline("like").build()));
783    }
784
785    #[test]
786    fn no_slots_in_condition() {
787        let src = r#"
788            permit(principal, action, resource) when {
789                resource == ?resource
790            };
791            "#;
792        let slot_in_when_clause =
793            ExpectedErrorMessageBuilder::error("found template slot ?resource in a `when` clause")
794                .help("slots are currently unsupported in `when` clauses")
795                .exactly_one_underline("?resource")
796                .build();
797        let unexpected_template = ExpectedErrorMessageBuilder::error(
798            "expected a static policy, got a template containing the slot ?resource",
799        )
800        .help("try removing the template slot(s) from this policy")
801        .exactly_one_underline("?resource")
802        .build();
803        assert_matches!(parse_policy(None, src), Err(e) => {
804            expect_n_errors(src, &e, 2);
805            expect_some_error_matches(src, &e, &slot_in_when_clause);
806            expect_some_error_matches(src, &e, &unexpected_template);
807        });
808        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
809            expect_exactly_one_error(src, &e, &slot_in_when_clause);
810        });
811        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
812            expect_n_errors(src, &e, 2);
813            expect_some_error_matches(src, &e, &slot_in_when_clause);
814            expect_some_error_matches(src, &e, &unexpected_template);
815        });
816        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
817            expect_exactly_one_error(src, &e, &slot_in_when_clause);
818        });
819        assert_matches!(parse_policyset(src), Err(e) => {
820            expect_exactly_one_error(src, &e, &slot_in_when_clause);
821        });
822        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
823            expect_exactly_one_error(src, &e, &slot_in_when_clause);
824        });
825
826        let src = r#"
827            permit(principal, action, resource) when {
828                resource == ?principal
829            };
830            "#;
831        let slot_in_when_clause =
832            ExpectedErrorMessageBuilder::error("found template slot ?principal in a `when` clause")
833                .help("slots are currently unsupported in `when` clauses")
834                .exactly_one_underline("?principal")
835                .build();
836        let unexpected_template = ExpectedErrorMessageBuilder::error(
837            "expected a static policy, got a template containing the slot ?principal",
838        )
839        .help("try removing the template slot(s) from this policy")
840        .exactly_one_underline("?principal")
841        .build();
842        assert_matches!(parse_policy(None, src), Err(e) => {
843            expect_n_errors(src, &e, 2);
844            expect_some_error_matches(src, &e, &slot_in_when_clause);
845            expect_some_error_matches(src, &e, &unexpected_template);
846        });
847        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
848            expect_exactly_one_error(src, &e, &slot_in_when_clause);
849        });
850        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
851            expect_n_errors(src, &e, 2);
852            expect_some_error_matches(src, &e, &slot_in_when_clause);
853            expect_some_error_matches(src, &e, &unexpected_template);
854        });
855        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
856            expect_exactly_one_error(src, &e, &slot_in_when_clause);
857        });
858        assert_matches!(parse_policyset(src), Err(e) => {
859            expect_exactly_one_error(src, &e, &slot_in_when_clause);
860        });
861        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
862            expect_exactly_one_error(src, &e, &slot_in_when_clause);
863        });
864
865        let src = r#"
866            permit(principal, action, resource) when {
867                resource == ?blah
868            };
869            "#;
870        let error = ExpectedErrorMessageBuilder::error("`?blah` is not a valid template slot")
871            .help("a template slot may only be `?principal` or `?resource`")
872            .exactly_one_underline("?blah")
873            .build();
874        assert_matches!(parse_policy(None, src), Err(e) => {
875            expect_exactly_one_error(src, &e, &error);
876        });
877        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
878            expect_exactly_one_error(src, &e, &error);
879        });
880        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
881            expect_exactly_one_error(src, &e, &error);
882        });
883        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
884            expect_exactly_one_error(src, &e, &error);
885        });
886        assert_matches!(parse_policyset(src), Err(e) => {
887            expect_exactly_one_error(src, &e, &error);
888        });
889        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
890            expect_exactly_one_error(src, &e, &error);
891        });
892
893        let src = r#"
894            permit(principal, action, resource) unless {
895                resource == ?resource
896            };
897            "#;
898        let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
899            "found template slot ?resource in a `unless` clause",
900        )
901        .help("slots are currently unsupported in `unless` clauses")
902        .exactly_one_underline("?resource")
903        .build();
904        let unexpected_template = ExpectedErrorMessageBuilder::error(
905            "expected a static policy, got a template containing the slot ?resource",
906        )
907        .help("try removing the template slot(s) from this policy")
908        .exactly_one_underline("?resource")
909        .build();
910        assert_matches!(parse_policy(None, src), Err(e) => {
911            expect_n_errors(src, &e, 2);
912            expect_some_error_matches(src, &e, &slot_in_unless_clause);
913            expect_some_error_matches(src, &e, &unexpected_template);
914        });
915        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
916            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
917        });
918        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
919            expect_n_errors(src, &e, 2);
920            expect_some_error_matches(src, &e, &slot_in_unless_clause);
921            expect_some_error_matches(src, &e, &unexpected_template);
922        });
923        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
924            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
925        });
926        assert_matches!(parse_policyset(src), Err(e) => {
927            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
928        });
929        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
930            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
931        });
932
933        let src = r#"
934            permit(principal, action, resource) unless {
935                resource == ?principal
936            };
937            "#;
938        let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
939            "found template slot ?principal in a `unless` clause",
940        )
941        .help("slots are currently unsupported in `unless` clauses")
942        .exactly_one_underline("?principal")
943        .build();
944        let unexpected_template = ExpectedErrorMessageBuilder::error(
945            "expected a static policy, got a template containing the slot ?principal",
946        )
947        .help("try removing the template slot(s) from this policy")
948        .exactly_one_underline("?principal")
949        .build();
950        assert_matches!(parse_policy(None, src), Err(e) => {
951            expect_n_errors(src, &e, 2);
952            expect_some_error_matches(src, &e, &slot_in_unless_clause);
953            expect_some_error_matches(src, &e, &unexpected_template);
954        });
955        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
956            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
957        });
958        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
959            expect_n_errors(src, &e, 2);
960            expect_some_error_matches(src, &e, &slot_in_unless_clause);
961            expect_some_error_matches(src, &e, &unexpected_template);
962        });
963        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
964            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
965        });
966        assert_matches!(parse_policyset(src), Err(e) => {
967            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
968        });
969        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
970            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
971        });
972
973        let src = r#"
974            permit(principal, action, resource) unless {
975                resource == ?blah
976            };
977            "#;
978        let error = ExpectedErrorMessageBuilder::error("`?blah` is not a valid template slot")
979            .help("a template slot may only be `?principal` or `?resource`")
980            .exactly_one_underline("?blah")
981            .build();
982        assert_matches!(parse_policy(None, src), Err(e) => {
983            expect_exactly_one_error(src, &e, &error);
984        });
985        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
986            expect_exactly_one_error(src, &e, &error);
987        });
988        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
989            expect_exactly_one_error(src, &e, &error);
990        });
991        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
992            expect_exactly_one_error(src, &e, &error);
993        });
994        assert_matches!(parse_policyset(src), Err(e) => {
995            expect_exactly_one_error(src, &e, &error);
996        });
997        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
998            expect_exactly_one_error(src, &e, &error);
999        });
1000
1001        let src = r#"
1002            permit(principal, action, resource) unless {
1003                resource == ?resource
1004            } when {
1005                resource == ?resource
1006            };
1007            "#;
1008        let slot_in_when_clause =
1009            ExpectedErrorMessageBuilder::error("found template slot ?resource in a `when` clause")
1010                .help("slots are currently unsupported in `when` clauses")
1011                .exactly_one_underline("?resource")
1012                .build();
1013        let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
1014            "found template slot ?resource in a `unless` clause",
1015        )
1016        .help("slots are currently unsupported in `unless` clauses")
1017        .exactly_one_underline("?resource")
1018        .build();
1019        let unexpected_template = ExpectedErrorMessageBuilder::error(
1020            "expected a static policy, got a template containing the slot ?resource",
1021        )
1022        .help("try removing the template slot(s) from this policy")
1023        .exactly_one_underline("?resource")
1024        .build();
1025        assert_matches!(parse_policy(None, src), Err(e) => {
1026            expect_n_errors(src, &e, 4);
1027            expect_some_error_matches(src, &e, &slot_in_when_clause);
1028            expect_some_error_matches(src, &e, &slot_in_unless_clause);
1029            expect_some_error_matches(src, &e, &unexpected_template); // 2 copies of this error
1030        });
1031        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
1032            expect_n_errors(src, &e, 2);
1033            expect_some_error_matches(src, &e, &slot_in_when_clause);
1034            expect_some_error_matches(src, &e, &slot_in_unless_clause);
1035        });
1036        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
1037            expect_n_errors(src, &e, 4);
1038            expect_some_error_matches(src, &e, &slot_in_when_clause);
1039            expect_some_error_matches(src, &e, &slot_in_unless_clause);
1040            expect_some_error_matches(src, &e, &unexpected_template); // 2 copies of this error
1041        });
1042        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
1043            expect_n_errors(src, &e, 2);
1044            expect_some_error_matches(src, &e, &slot_in_when_clause);
1045            expect_some_error_matches(src, &e, &slot_in_unless_clause);
1046        });
1047        assert_matches!(parse_policyset(src), Err(e) => {
1048            expect_n_errors(src, &e, 2);
1049            expect_some_error_matches(src, &e, &slot_in_when_clause);
1050            expect_some_error_matches(src, &e, &slot_in_unless_clause);
1051        });
1052        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
1053            expect_n_errors(src, &e, 2);
1054            expect_some_error_matches(src, &e, &slot_in_when_clause);
1055            expect_some_error_matches(src, &e, &slot_in_unless_clause);
1056        });
1057    }
1058
1059    #[test]
1060    fn record_literals() {
1061        // unquoted keys
1062        let src = r#"permit(principal, action, resource) when { context.foo == { foo: 2, bar: "baz" } };"#;
1063        assert_matches!(parse_policy(None, src), Ok(_));
1064        // quoted keys
1065        let src = r#"permit(principal, action, resource) when { context.foo == { "foo": 2, "hi mom it's 🦀": "baz" } };"#;
1066        assert_matches!(parse_policy(None, src), Ok(_));
1067        // duplicate key
1068        let src = r#"permit(principal, action, resource) when { context.foo == { "spam": -341, foo: 2, "🦀": true, foo: "baz" } };"#;
1069        assert_matches!(parse_policy(None, src), Err(e) => {
1070            expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate key `foo` in record literal").exactly_one_underline(r#"{ "spam": -341, foo: 2, "🦀": true, foo: "baz" }"#).build());
1071        });
1072    }
1073
1074    #[test]
1075    fn annotation_errors() {
1076        let src = r#"
1077            @foo("1")
1078            @foo("2")
1079            permit(principal, action, resource);
1080        "#;
1081        assert_matches!(parse_policy(None, src), Err(e) => {
1082            expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("2")"#).build());
1083        });
1084
1085        let src = r#"
1086            @foo("1")
1087            @foo("1")
1088            permit(principal, action, resource);
1089        "#;
1090        assert_matches!(parse_policy(None, src), Err(e) => {
1091            expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("1")"#).build());
1092        });
1093
1094        let src = r#"
1095            @foo("1")
1096            @bar("yellow")
1097            @foo("abc")
1098            @hello("goodbye")
1099            @bar("123")
1100            @foo("def")
1101            permit(principal, action, resource);
1102        "#;
1103        assert_matches!(parse_policy(None, src), Err(e) => {
1104            expect_n_errors(src, &e, 3); // two errors for @foo and one for @bar
1105            expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("abc")"#).build());
1106            expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("def")"#).build());
1107            expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @bar").exactly_one_underline(r#"@bar("123")"#).build());
1108        })
1109    }
1110
1111    #[test]
1112    fn unexpected_token_errors() {
1113        #[track_caller]
1114        fn assert_labeled_span(src: &str, msg: &str, underline: &str, label: &str) {
1115            assert_matches!(parse_policy(None, src), Err(e) => {
1116                expect_exactly_one_error(
1117                    src,
1118                    &e,
1119                    &ExpectedErrorMessageBuilder::error(msg)
1120                        .exactly_one_underline_with_label(underline, label)
1121                        .build());
1122            });
1123        }
1124
1125        // Don't list out all the special case identifiers
1126        assert_labeled_span("@", "unexpected end of input", "", "expected identifier");
1127        assert_labeled_span(
1128            "permit(principal, action, resource) when { principal.",
1129            "unexpected end of input",
1130            "",
1131            "expected identifier",
1132        );
1133
1134        // We specifically want `when` or `unless`, but we previously listed all
1135        // identifier tokens, so this is an improvement.
1136        assert_labeled_span(
1137            "permit(principal, action, resource)",
1138            "unexpected end of input",
1139            "",
1140            "expected `;` or identifier",
1141        );
1142        // AST actually requires `permit` or `forbid`, but grammar looks for any
1143        // identifier.
1144        assert_labeled_span(
1145            "@if(\"a\")",
1146            "unexpected end of input",
1147            "",
1148            "expected `@` or identifier",
1149        );
1150        // AST actually requires `principal` (`action`, `resource`, resp.). In
1151        // the `principal` case we also claim to expect `)` because an empty scope
1152        // initially parses to a CST. The trailing comma rules this out in the others.
1153        assert_labeled_span(
1154            "permit(",
1155            "unexpected end of input",
1156            "",
1157            "expected `)` or identifier",
1158        );
1159        assert_labeled_span(
1160            "permit(,,);",
1161            "unexpected token `,`",
1162            ",",
1163            "expected `)` or identifier",
1164        );
1165        assert_labeled_span(
1166            "permit(principal,",
1167            "unexpected end of input",
1168            "",
1169            "expected `)` or identifier",
1170        );
1171        assert_labeled_span(
1172            "permit(principal,action,",
1173            "unexpected end of input",
1174            "",
1175            "expected `)` or identifier",
1176        );
1177        // Nothing will actually convert to an AST here.
1178        assert_labeled_span(
1179            "permit(principal,action,resource,",
1180            "unexpected end of input",
1181            "",
1182            "expected `)` or identifier",
1183        );
1184        // We still list out `if` as an expected token because it doesn't get
1185        // parsed as an ident in this position.
1186        assert_labeled_span(
1187            "permit(principal, action, resource) when {",
1188            "unexpected end of input",
1189            "",
1190            "expected `!`, `(`, `-`, `[`, `{`, `}`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`",
1191        );
1192        // The right operand of an `is` gets parsed as any `Expr`, so we will
1193        // list out all the possible expression tokens even though _only_
1194        // `identifier` is accepted. This choice allows nicer error messages for
1195        // `principal is User::"alice"`, but it doesn't work in our favor here.
1196        assert_labeled_span(
1197            "permit(principal, action, resource) when { principal is",
1198            "unexpected end of input",
1199            "",
1200            "expected `!`, `(`, `-`, `[`, `{`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`",
1201        );
1202
1203        // We expect binary operators, but don't claim to expect `=`, `%` or
1204        // `/`. We still expect `::` even though `true` is a reserved identifier
1205        // and so we can't have an entity reference `true::"eid"`
1206        assert_labeled_span(
1207            "permit(principal, action, resource) when { if true",
1208            "unexpected end of input",
1209            "",
1210            "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `has`, `in`, `is`, `like`, or `then`",
1211        )
1212    }
1213
1214    #[test]
1215    fn string_escapes() {
1216        // test strings with valid escapes
1217        // convert a string `s` to `<double-quote> <escaped-form-of-s> <double-quote>`
1218        // and test if the resulting string literal AST contains exactly `s`
1219        // for instance, "\u{1F408}"" is converted into r#""\u{1F408}""#,
1220        // the latter should be parsed into `Literal(String("🐈"))` and
1221        // `🐈` is represented by '\u{1F408}'
1222        let test_valid = |s: &str| {
1223            let r = parse_literal(&format!("\"{}\"", s.escape_default()));
1224            assert_eq!(r, Ok(Literal::String(s.into())));
1225        };
1226        test_valid("\t");
1227        test_valid("\0");
1228        test_valid("👍");
1229        test_valid("🐈");
1230        test_valid("\u{1F408}");
1231        test_valid("abc\tde\\fg");
1232        test_valid("aaa\u{1F408}bcd👍👍👍");
1233        // test string with invalid escapes
1234        let test_invalid = |s: &str, bad_escapes: Vec<&str>| {
1235            let src: &str = &format!("\"{s}\"");
1236            assert_matches!(parse_literal(src), Err(LiteralParseError::Parse(e)) => {
1237                expect_n_errors(src, &e, bad_escapes.len());
1238                bad_escapes.iter().for_each(|esc|
1239                    expect_some_error_matches(
1240                        src,
1241                        &e,
1242                        &ExpectedErrorMessageBuilder::error(&format!("the input `{esc}` is not a valid escape"))
1243                            .exactly_one_underline(src)
1244                            .build()
1245                    )
1246                );
1247            })
1248        };
1249        // invalid escape `\a`
1250        test_invalid("\\a", vec!["\\a"]);
1251        // invalid escape `\b`
1252        test_invalid("\\b", vec!["\\b"]);
1253        // invalid escape `\p`
1254        test_invalid("\\\\aa\\p", vec!["\\p"]);
1255        // invalid escape `\a` and empty unicode escape
1256        test_invalid(r"\aaa\u{}", vec!["\\a", "\\u{}"]);
1257    }
1258}