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
22mod 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(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(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(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.clone()).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(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(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()
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() {
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()
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(Some(src), 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, just for better assertion-failure messages
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#[cfg(test)]
331/// Tests for the top-level parsing APIs
332mod tests {
333
334    use super::*;
335
336    use crate::ast::test_generators::*;
337    use crate::ast::{Eid, Literal, Template, Value};
338    use crate::evaluator as eval;
339    use crate::extensions::Extensions;
340    use crate::parser::err::*;
341    use crate::parser::test_utils::*;
342    use crate::test_utils::*;
343    use cool_asserts::assert_matches;
344    use std::collections::HashSet;
345    use std::sync::Arc;
346
347    #[test]
348    fn test_template_parsing() {
349        for template in all_templates().map(Template::from) {
350            let id = template.id();
351            let src = format!("{template}");
352            let parsed =
353                parse_policy_or_template(Some(ast::PolicyID::from_string(id)), &src).unwrap();
354            assert_eq!(
355                parsed.slots().collect::<HashSet<_>>(),
356                template.slots().collect::<HashSet<_>>()
357            );
358            assert_eq!(parsed.id(), template.id());
359            assert_eq!(parsed.effect(), template.effect());
360            assert_eq!(
361                parsed.principal_constraint(),
362                template.principal_constraint()
363            );
364            assert_eq!(parsed.action_constraint(), template.action_constraint());
365            assert_eq!(parsed.resource_constraint(), template.resource_constraint());
366            assert!(
367                parsed
368                    .non_scope_constraints()
369                    .eq_shape(template.non_scope_constraints()),
370                "{:?} and {:?} should have the same shape.",
371                parsed.non_scope_constraints(),
372                template.non_scope_constraints()
373            );
374        }
375    }
376
377    #[test]
378    fn test_error_out() {
379        let src = r#"
380            permit(principal:p,action:a,resource:r)
381            when{w or if c but not z} // expr error
382            unless{u if c else d or f} // expr error
383            advice{"doit"};
384
385            permit(principality in Group::"jane_friends", // policy error
386            action in [PhotoOp::"view", PhotoOp::"comment"],
387            resource in Album::"jane_trips");
388
389            forbid(principal, action, resource)
390            when   { "private" in resource.tags }
391            unless { resource in principal.account };
392        "#;
393        let errs = parse_policyset(src).expect_err("expected parsing to fail");
394        let unrecognized_tokens = vec![
395            ("or", "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `}`, `has`, `in`, `is`, or `like`"),
396            ("if", "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `}`, `has`, `in`, `is`, or `like`"),
397        ];
398        for (token, label) in unrecognized_tokens {
399            expect_some_error_matches(
400                src,
401                &errs,
402                &ExpectedErrorMessageBuilder::error(&format!("unexpected token `{token}`"))
403                    .exactly_one_underline_with_label(token, label)
404                    .build(),
405            );
406        }
407        expect_n_errors(src, &errs, 2);
408        assert!(errs.iter().all(|err| matches!(err, ParseError::ToCST(_))));
409    }
410
411    #[test]
412    fn entity_literals1() {
413        let src = r#"Test::{ test : "Test" }"#;
414        let errs = parse_euid(src).unwrap_err();
415        expect_exactly_one_error(
416            src,
417            &errs,
418            &ExpectedErrorMessageBuilder::error("invalid entity literal: Test::{test: \"Test\"}")
419                .help("entity literals should have a form like `Namespace::User::\"alice\"`")
420                .exactly_one_underline("Test::{ test : \"Test\" }")
421                .build(),
422        );
423    }
424
425    #[test]
426    fn entity_literals2() {
427        let src = r#"permit(principal == Test::{ test : "Test" }, action, resource);"#;
428        let errs = parse_policy(None, src).unwrap_err();
429        expect_exactly_one_error(
430            src,
431            &errs,
432            &ExpectedErrorMessageBuilder::error("invalid entity literal: Test::{test: \"Test\"}")
433                .help("entity literals should have a form like `Namespace::User::\"alice\"`")
434                .exactly_one_underline("Test::{ test : \"Test\" }")
435                .build(),
436        );
437    }
438
439    #[test]
440    fn interpret_exprs() {
441        let request = eval::test::basic_request();
442        let entities = eval::test::basic_entities();
443        let exts = Extensions::none();
444        let evaluator = eval::Evaluator::new(request, &entities, exts);
445        // The below tests check not only that we get the expected `Value`, but
446        // that it has the expected source location.
447        // We have to check that separately because the `PartialEq` and `Eq`
448        // impls for `Value` do not compare source locations.
449        // This is somewhat a test of the evaluator, not just the parser; but
450        // the actual evaluator unit tests do not use the parser and thus do
451        // not have source locations attached to their input expressions, so
452        // this file is where we effectively perform evaluator tests related to
453        // propagating source locations from expressions to values.
454
455        // bools
456        let src = "false";
457        let expr = parse_expr(src).unwrap();
458        let val = evaluator.interpret_inline_policy(&expr).unwrap();
459        assert_eq!(val, Value::from(false));
460        assert_eq!(val.source_loc(), Some(&Loc::new(0..5, Arc::from(src))));
461
462        let src = "true && true";
463        let expr = parse_expr(src).unwrap();
464        let val = evaluator.interpret_inline_policy(&expr).unwrap();
465        assert_eq!(val, Value::from(true));
466        assert_eq!(val.source_loc(), Some(&Loc::new(0..12, Arc::from(src))));
467
468        let src = "!true || false && !true";
469        let expr = parse_expr(src).unwrap();
470        let val = evaluator.interpret_inline_policy(&expr).unwrap();
471        assert_eq!(val, Value::from(false));
472        assert_eq!(val.source_loc(), Some(&Loc::new(0..23, Arc::from(src))));
473
474        let src = "!!!!true";
475        let expr = parse_expr(src).unwrap();
476        let val = evaluator.interpret_inline_policy(&expr).unwrap();
477        assert_eq!(val, Value::from(true));
478        assert_eq!(val.source_loc(), Some(&Loc::new(0..8, Arc::from(src))));
479
480        let src = r#"
481        if false || true != 4 then
482            600
483        else
484            -200
485        "#;
486        let expr = parse_expr(src).unwrap();
487        let val = evaluator.interpret_inline_policy(&expr).unwrap();
488        assert_eq!(val, Value::from(600));
489        assert_eq!(val.source_loc(), Some(&Loc::new(9..81, Arc::from(src))));
490    }
491
492    #[test]
493    fn interpret_membership() {
494        let request = eval::test::basic_request();
495        let entities = eval::test::rich_entities();
496        let exts = Extensions::none();
497        let evaluator = eval::Evaluator::new(request, &entities, exts);
498        // The below tests check not only that we get the expected `Value`, but
499        // that it has the expected source location.
500        // See note on this in the above test.
501
502        let src = r#"
503
504        test_entity_type::"child" in
505            test_entity_type::"unrelated"
506
507        "#;
508        let expr = parse_expr(src).unwrap();
509        let val = evaluator.interpret_inline_policy(&expr).unwrap();
510        assert_eq!(val, Value::from(false));
511        assert_eq!(val.source_loc(), Some(&Loc::new(10..80, Arc::from(src))));
512        // because "10..80" is hard to read, we also assert that the correct portion of `src` is indicated
513        assert_eq!(
514            val.source_loc().unwrap().snippet(),
515            Some(
516                r#"test_entity_type::"child" in
517            test_entity_type::"unrelated""#
518            )
519        );
520
521        let src = r#"
522
523        test_entity_type::"child" in
524            test_entity_type::"child"
525
526        "#;
527        let expr = parse_expr(src).unwrap();
528        let val = evaluator.interpret_inline_policy(&expr).unwrap();
529        assert_eq!(val, Value::from(true));
530        assert_eq!(val.source_loc(), Some(&Loc::new(10..76, Arc::from(src))));
531        assert_eq!(
532            val.source_loc().unwrap().snippet(),
533            Some(
534                r#"test_entity_type::"child" in
535            test_entity_type::"child""#
536            )
537        );
538
539        let src = r#"
540
541        other_type::"other_child" in
542            test_entity_type::"parent"
543
544        "#;
545        let expr = parse_expr(src).unwrap();
546        let val = evaluator.interpret_inline_policy(&expr).unwrap();
547        assert_eq!(val, Value::from(true));
548        assert_eq!(val.source_loc(), Some(&Loc::new(10..77, Arc::from(src))));
549        assert_eq!(
550            val.source_loc().unwrap().snippet(),
551            Some(
552                r#"other_type::"other_child" in
553            test_entity_type::"parent""#
554            )
555        );
556
557        let src = r#"
558
559        test_entity_type::"child" in
560            test_entity_type::"grandparent"
561
562        "#;
563        let expr = parse_expr(src).unwrap();
564        let val = evaluator.interpret_inline_policy(&expr).unwrap();
565        assert_eq!(val, Value::from(true));
566        assert_eq!(val.source_loc(), Some(&Loc::new(10..82, Arc::from(src))));
567        assert_eq!(
568            val.source_loc().unwrap().snippet(),
569            Some(
570                r#"test_entity_type::"child" in
571            test_entity_type::"grandparent""#
572            )
573        );
574    }
575
576    #[test]
577    fn interpret_relation() {
578        let request = eval::test::basic_request();
579        let entities = eval::test::basic_entities();
580        let exts = Extensions::none();
581        let evaluator = eval::Evaluator::new(request, &entities, exts);
582        // The below tests check not only that we get the expected `Value`, but
583        // that it has the expected source location.
584        // See note on this in the above test.
585
586        let src = r#"
587
588            3 < 2 || 2 > 3
589
590        "#;
591        let expr = parse_expr(src).unwrap();
592        let val = evaluator.interpret_inline_policy(&expr).unwrap();
593        assert_eq!(val, Value::from(false));
594        assert_eq!(val.source_loc(), Some(&Loc::new(14..28, Arc::from(src))));
595        // because "14..28" is hard to read, we also assert that the correct portion of `src` is indicated
596        assert_eq!(val.source_loc().unwrap().snippet(), Some("3 < 2 || 2 > 3"));
597
598        let src = r#"
599
600            7 <= 7 && 4 != 5
601
602        "#;
603        let expr = parse_expr(src).unwrap();
604        let val = evaluator.interpret_inline_policy(&expr).unwrap();
605        assert_eq!(val, Value::from(true));
606        assert_eq!(val.source_loc(), Some(&Loc::new(14..30, Arc::from(src))));
607        assert_eq!(
608            val.source_loc().unwrap().snippet(),
609            Some("7 <= 7 && 4 != 5")
610        );
611    }
612
613    #[test]
614    fn parse_exists() {
615        let result = parse_policyset(
616            r#"
617            permit(principal, action, resource)
618            when{ true };
619        "#,
620        );
621        assert!(!result.expect("parse error").is_empty());
622    }
623
624    #[test]
625    fn test_parse_policyset() {
626        use crate::ast::PolicyID;
627        let multiple_policies = r#"
628            permit(principal, action, resource)
629            when { principal == resource.owner };
630
631            forbid(principal, action == Action::"modify", resource) // a comment
632            when { resource . highSecurity }; // intentionally not conforming to our formatter
633        "#;
634        let pset = parse_policyset(multiple_policies).expect("Should parse");
635        assert_eq!(pset.policies().count(), 2);
636        assert_eq!(pset.static_policies().count(), 2);
637        let (texts, pset) =
638            parse_policyset_and_also_return_policy_text(multiple_policies).expect("Should parse");
639        assert_eq!(pset.policies().count(), 2);
640        assert_eq!(pset.static_policies().count(), 2);
641        assert_eq!(texts.len(), 2);
642        assert_eq!(
643            texts.get(&PolicyID::from_string("policy0")),
644            Some(
645                &r#"permit(principal, action, resource)
646            when { principal == resource.owner };"#
647            )
648        );
649        assert_eq!(
650            texts.get(&PolicyID::from_string("policy1")),
651            Some(
652                &r#"forbid(principal, action == Action::"modify", resource) // a comment
653            when { resource . highSecurity };"#
654            )
655        );
656    }
657
658    #[test]
659    fn test_parse_string() {
660        // test idempotence
661        assert_eq!(
662            Eid::new(parse_internal_string(r"a\nblock\nid").expect("should parse")).escaped(),
663            r"a\nblock\nid",
664        );
665        parse_internal_string(r#"oh, no, a '! "#).expect("single quote should be fine");
666        parse_internal_string(r#"oh, no, a \"! and a \'! "#).expect("escaped quotes should parse");
667        let src = r#"oh, no, a "! "#;
668        let errs = parse_internal_string(src).expect_err("unescaped double quote not allowed");
669        expect_exactly_one_error(
670            src,
671            &errs,
672            &ExpectedErrorMessageBuilder::error("invalid token")
673                .exactly_one_underline("")
674                .build(),
675        );
676    }
677
678    #[test]
679    fn good_cst_bad_ast() {
680        let src = r#"
681            permit(principal, action, resource) when { principal.name.like == "3" };
682            "#;
683        let p = parse_policyset_to_ests_and_pset(src);
684        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()));
685    }
686
687    #[test]
688    fn no_slots_in_condition() {
689        let src = r#"
690            permit(principal, action, resource) when {
691                resource == ?resource
692            };
693            "#;
694        let slot_in_when_clause =
695            ExpectedErrorMessageBuilder::error("found template slot ?resource in a `when` clause")
696                .help("slots are currently unsupported in `when` clauses")
697                .exactly_one_underline("?resource")
698                .build();
699        let unexpected_template = ExpectedErrorMessageBuilder::error(
700            "expected a static policy, got a template containing the slot ?resource",
701        )
702        .help("try removing the template slot(s) from this policy")
703        .exactly_one_underline("?resource")
704        .build();
705        assert_matches!(parse_policy(None, src), Err(e) => {
706            expect_n_errors(src, &e, 2);
707            expect_some_error_matches(src, &e, &slot_in_when_clause);
708            expect_some_error_matches(src, &e, &unexpected_template);
709        });
710        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
711            expect_exactly_one_error(src, &e, &slot_in_when_clause);
712        });
713        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
714            expect_n_errors(src, &e, 2);
715            expect_some_error_matches(src, &e, &slot_in_when_clause);
716            expect_some_error_matches(src, &e, &unexpected_template);
717        });
718        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
719            expect_exactly_one_error(src, &e, &slot_in_when_clause);
720        });
721        assert_matches!(parse_policyset(src), Err(e) => {
722            expect_exactly_one_error(src, &e, &slot_in_when_clause);
723        });
724        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
725            expect_exactly_one_error(src, &e, &slot_in_when_clause);
726        });
727
728        let src = r#"
729            permit(principal, action, resource) when {
730                resource == ?principal
731            };
732            "#;
733        let slot_in_when_clause =
734            ExpectedErrorMessageBuilder::error("found template slot ?principal in a `when` clause")
735                .help("slots are currently unsupported in `when` clauses")
736                .exactly_one_underline("?principal")
737                .build();
738        let unexpected_template = ExpectedErrorMessageBuilder::error(
739            "expected a static policy, got a template containing the slot ?principal",
740        )
741        .help("try removing the template slot(s) from this policy")
742        .exactly_one_underline("?principal")
743        .build();
744        assert_matches!(parse_policy(None, src), Err(e) => {
745            expect_n_errors(src, &e, 2);
746            expect_some_error_matches(src, &e, &slot_in_when_clause);
747            expect_some_error_matches(src, &e, &unexpected_template);
748        });
749        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
750            expect_exactly_one_error(src, &e, &slot_in_when_clause);
751        });
752        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
753            expect_n_errors(src, &e, 2);
754            expect_some_error_matches(src, &e, &slot_in_when_clause);
755            expect_some_error_matches(src, &e, &unexpected_template);
756        });
757        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
758            expect_exactly_one_error(src, &e, &slot_in_when_clause);
759        });
760        assert_matches!(parse_policyset(src), Err(e) => {
761            expect_exactly_one_error(src, &e, &slot_in_when_clause);
762        });
763        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
764            expect_exactly_one_error(src, &e, &slot_in_when_clause);
765        });
766
767        let src = r#"
768            permit(principal, action, resource) when {
769                resource == ?blah
770            };
771            "#;
772        let error = ExpectedErrorMessageBuilder::error("`?blah` is not a valid template slot")
773            .help("a template slot may only be `?principal` or `?resource`")
774            .exactly_one_underline("?blah")
775            .build();
776        assert_matches!(parse_policy(None, src), Err(e) => {
777            expect_exactly_one_error(src, &e, &error);
778        });
779        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
780            expect_exactly_one_error(src, &e, &error);
781        });
782        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
783            expect_exactly_one_error(src, &e, &error);
784        });
785        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
786            expect_exactly_one_error(src, &e, &error);
787        });
788        assert_matches!(parse_policyset(src), Err(e) => {
789            expect_exactly_one_error(src, &e, &error);
790        });
791        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
792            expect_exactly_one_error(src, &e, &error);
793        });
794
795        let src = r#"
796            permit(principal, action, resource) unless {
797                resource == ?resource
798            };
799            "#;
800        let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
801            "found template slot ?resource in a `unless` clause",
802        )
803        .help("slots are currently unsupported in `unless` clauses")
804        .exactly_one_underline("?resource")
805        .build();
806        let unexpected_template = ExpectedErrorMessageBuilder::error(
807            "expected a static policy, got a template containing the slot ?resource",
808        )
809        .help("try removing the template slot(s) from this policy")
810        .exactly_one_underline("?resource")
811        .build();
812        assert_matches!(parse_policy(None, src), Err(e) => {
813            expect_n_errors(src, &e, 2);
814            expect_some_error_matches(src, &e, &slot_in_unless_clause);
815            expect_some_error_matches(src, &e, &unexpected_template);
816        });
817        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
818            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
819        });
820        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
821            expect_n_errors(src, &e, 2);
822            expect_some_error_matches(src, &e, &slot_in_unless_clause);
823            expect_some_error_matches(src, &e, &unexpected_template);
824        });
825        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
826            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
827        });
828        assert_matches!(parse_policyset(src), Err(e) => {
829            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
830        });
831        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
832            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
833        });
834
835        let src = r#"
836            permit(principal, action, resource) unless {
837                resource == ?principal
838            };
839            "#;
840        let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
841            "found template slot ?principal in a `unless` clause",
842        )
843        .help("slots are currently unsupported in `unless` 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_unless_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_unless_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_unless_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_unless_clause);
867        });
868        assert_matches!(parse_policyset(src), Err(e) => {
869            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
870        });
871        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
872            expect_exactly_one_error(src, &e, &slot_in_unless_clause);
873        });
874
875        let src = r#"
876            permit(principal, action, resource) unless {
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            } when {
907                resource == ?resource
908            };
909            "#;
910        let slot_in_when_clause =
911            ExpectedErrorMessageBuilder::error("found template slot ?resource in a `when` clause")
912                .help("slots are currently unsupported in `when` clauses")
913                .exactly_one_underline("?resource")
914                .build();
915        let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
916            "found template slot ?resource in a `unless` clause",
917        )
918        .help("slots are currently unsupported in `unless` clauses")
919        .exactly_one_underline("?resource")
920        .build();
921        let unexpected_template = ExpectedErrorMessageBuilder::error(
922            "expected a static policy, got a template containing the slot ?resource",
923        )
924        .help("try removing the template slot(s) from this policy")
925        .exactly_one_underline("?resource")
926        .build();
927        assert_matches!(parse_policy(None, src), Err(e) => {
928            expect_n_errors(src, &e, 4);
929            expect_some_error_matches(src, &e, &slot_in_when_clause);
930            expect_some_error_matches(src, &e, &slot_in_unless_clause);
931            expect_some_error_matches(src, &e, &unexpected_template); // 2 copies of this error
932        });
933        assert_matches!(parse_policy_or_template(None, src), Err(e) => {
934            expect_n_errors(src, &e, 2);
935            expect_some_error_matches(src, &e, &slot_in_when_clause);
936            expect_some_error_matches(src, &e, &slot_in_unless_clause);
937        });
938        assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
939            expect_n_errors(src, &e, 4);
940            expect_some_error_matches(src, &e, &slot_in_when_clause);
941            expect_some_error_matches(src, &e, &slot_in_unless_clause);
942            expect_some_error_matches(src, &e, &unexpected_template); // 2 copies of this error
943        });
944        assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
945            expect_n_errors(src, &e, 2);
946            expect_some_error_matches(src, &e, &slot_in_when_clause);
947            expect_some_error_matches(src, &e, &slot_in_unless_clause);
948        });
949        assert_matches!(parse_policyset(src), Err(e) => {
950            expect_n_errors(src, &e, 2);
951            expect_some_error_matches(src, &e, &slot_in_when_clause);
952            expect_some_error_matches(src, &e, &slot_in_unless_clause);
953        });
954        assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
955            expect_n_errors(src, &e, 2);
956            expect_some_error_matches(src, &e, &slot_in_when_clause);
957            expect_some_error_matches(src, &e, &slot_in_unless_clause);
958        });
959    }
960
961    #[test]
962    fn record_literals() {
963        // unquoted keys
964        let src = r#"permit(principal, action, resource) when { context.foo == { foo: 2, bar: "baz" } };"#;
965        assert_matches!(parse_policy(None, src), Ok(_));
966        // quoted keys
967        let src = r#"permit(principal, action, resource) when { context.foo == { "foo": 2, "hi mom it's 🦀": "baz" } };"#;
968        assert_matches!(parse_policy(None, src), Ok(_));
969        // duplicate key
970        let src = r#"permit(principal, action, resource) when { context.foo == { "spam": -341, foo: 2, "🦀": true, foo: "baz" } };"#;
971        assert_matches!(parse_policy(None, src), Err(e) => {
972            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());
973        });
974    }
975
976    #[test]
977    fn annotation_errors() {
978        let src = r#"
979            @foo("1")
980            @foo("2")
981            permit(principal, action, resource);
982        "#;
983        assert_matches!(parse_policy(None, src), Err(e) => {
984            expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("2")"#).build());
985        });
986
987        let src = r#"
988            @foo("1")
989            @foo("1")
990            permit(principal, action, resource);
991        "#;
992        assert_matches!(parse_policy(None, src), Err(e) => {
993            expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("1")"#).build());
994        });
995
996        let src = r#"
997            @foo("1")
998            @bar("yellow")
999            @foo("abc")
1000            @hello("goodbye")
1001            @bar("123")
1002            @foo("def")
1003            permit(principal, action, resource);
1004        "#;
1005        assert_matches!(parse_policy(None, src), Err(e) => {
1006            expect_n_errors(src, &e, 3); // two errors for @foo and one for @bar
1007            expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("abc")"#).build());
1008            expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("def")"#).build());
1009            expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @bar").exactly_one_underline(r#"@bar("123")"#).build());
1010        })
1011    }
1012
1013    #[test]
1014    fn unexpected_token_errors() {
1015        #[track_caller]
1016        fn assert_labeled_span(src: &str, msg: &str, underline: &str, label: &str) {
1017            assert_matches!(parse_policy(None, src), Err(e) => {
1018                expect_exactly_one_error(
1019                    src,
1020                    &e,
1021                    &ExpectedErrorMessageBuilder::error(msg)
1022                        .exactly_one_underline_with_label(underline, label)
1023                        .build());
1024            });
1025        }
1026
1027        // Don't list out all the special case identifiers
1028        assert_labeled_span("@", "unexpected end of input", "", "expected identifier");
1029        assert_labeled_span(
1030            "permit(principal, action, resource) when { principal.",
1031            "unexpected end of input",
1032            "",
1033            "expected identifier",
1034        );
1035
1036        // We specifically want `when` or `unless`, but we previously listed all
1037        // identifier tokens, so this is an improvement.
1038        assert_labeled_span(
1039            "permit(principal, action, resource)",
1040            "unexpected end of input",
1041            "",
1042            "expected `;` or identifier",
1043        );
1044        // AST actually requires `permit` or `forbid`, but grammar looks for any
1045        // identifier.
1046        assert_labeled_span(
1047            "@if(\"a\")",
1048            "unexpected end of input",
1049            "",
1050            "expected `@` or identifier",
1051        );
1052        // AST actually requires `principal` (`action`, `resource`, resp.). In
1053        // the `principal` case we also claim to expect `)` because an empty scope
1054        // initially parses to a CST. The trailing comma rules this out in the others.
1055        assert_labeled_span(
1056            "permit(",
1057            "unexpected end of input",
1058            "",
1059            "expected `)` or identifier",
1060        );
1061        assert_labeled_span(
1062            "permit(,,);",
1063            "unexpected token `,`",
1064            ",",
1065            "expected `)` or identifier",
1066        );
1067        assert_labeled_span(
1068            "permit(principal,",
1069            "unexpected end of input",
1070            "",
1071            "expected identifier",
1072        );
1073        assert_labeled_span(
1074            "permit(principal,action,",
1075            "unexpected end of input",
1076            "",
1077            "expected identifier",
1078        );
1079        // Nothing will actually convert to an AST here.
1080        assert_labeled_span(
1081            "permit(principal,action,resource,",
1082            "unexpected end of input",
1083            "",
1084            "expected identifier",
1085        );
1086        // We still list out `if` as an expected token because it doesn't get
1087        // parsed as an ident in this position.
1088        assert_labeled_span(
1089            "permit(principal, action, resource) when {",
1090            "unexpected end of input",
1091            "",
1092            "expected `!`, `(`, `-`, `[`, `{`, `}`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`",
1093        );
1094        // The right operand of an `is` gets parsed as any `Expr`, so we will
1095        // list out all the possible expression tokens even though _only_
1096        // `identifier` is accepted. This choice allows nicer error messages for
1097        // `principal is User::"alice"`, but it doesn't work in our favor here.
1098        assert_labeled_span(
1099            "permit(principal, action, resource) when { principal is",
1100            "unexpected end of input",
1101            "",
1102            "expected `!`, `(`, `-`, `[`, `{`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`",
1103        );
1104
1105        // We expect binary operators, but don't claim to expect `=`, `%` or
1106        // `/`. We still expect `::` even though `true` is a reserved identifier
1107        // and so we can't have an entity reference `true::"eid"`
1108        assert_labeled_span(
1109            "permit(principal, action, resource) when { if true",
1110            "unexpected end of input",
1111            "",
1112            "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `has`, `in`, `is`, `like`, or `then`",
1113        )
1114    }
1115
1116    #[test]
1117    fn string_escapes() {
1118        // test strings with valid escapes
1119        // convert a string `s` to `<double-quote> <escaped-form-of-s> <double-quote>`
1120        // and test if the resulting string literal AST contains exactly `s`
1121        // for instance, "\u{1F408}"" is converted into r#""\u{1F408}""#,
1122        // the latter should be parsed into `Literal(String("🐈"))` and
1123        // `🐈` is represented by '\u{1F408}'
1124        let test_valid = |s: &str| {
1125            let r = parse_literal(&format!("\"{}\"", s.escape_default()));
1126            assert_eq!(r, Ok(Literal::String(s.into())));
1127        };
1128        test_valid("\t");
1129        test_valid("\0");
1130        test_valid("👍");
1131        test_valid("🐈");
1132        test_valid("\u{1F408}");
1133        test_valid("abc\tde\\fg");
1134        test_valid("aaa\u{1F408}bcd👍👍👍");
1135        // test string with invalid escapes
1136        let test_invalid = |s: &str, bad_escapes: Vec<&str>| {
1137            let src: &str = &format!("\"{}\"", s);
1138            assert_matches!(parse_literal(src), Err(LiteralParseError::Parse(e)) => {
1139                expect_n_errors(src, &e, bad_escapes.len());
1140                bad_escapes.iter().for_each(|esc|
1141                    expect_some_error_matches(
1142                        src,
1143                        &e,
1144                        &ExpectedErrorMessageBuilder::error(&format!("the input `{esc}` is not a valid escape"))
1145                            .exactly_one_underline(src)
1146                            .build()
1147                    )
1148                );
1149            })
1150        };
1151        // invalid escape `\a`
1152        test_invalid("\\a", vec!["\\a"]);
1153        // invalid escape `\b`
1154        test_invalid("\\b", vec!["\\b"]);
1155        // invalid escape `\p`
1156        test_invalid("\\\\aa\\p", vec!["\\p"]);
1157        // invalid escape `\a` and empty unicode escape
1158        test_invalid(r"\aaa\u{}", vec!["\\a", "\\u{}"]);
1159    }
1160}