Skip to main content

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