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