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