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 #[allow(clippy::expect_used)]
67 #[allow(clippy::indexing_slicing)]
69 let texts = cst
75 .with_generated_policyids()
76 .expect("shouldn't be `None` since `parse_policies` and `to_policyset` didn't return `Err`")
77 .map(|(id, policy)| {
78 if let Some(loc) = &policy.loc {
79 (id, Some(&text[loc.start()..loc.end()]))
80 } else {
81 (id, None)
82 }
83 })
84 .collect::<HashMap<ast::PolicyID, Option<&str>>>();
85 Ok((texts, pset))
86}
87
88pub fn parse_policyset_to_ests_and_pset(
92 text: &str,
93) -> Result<(HashMap<ast::PolicyID, est::Policy>, ast::PolicySet), err::ParseErrors> {
94 let cst = text_to_cst::parse_policies(text)?;
95 let pset = cst.to_policyset()?;
96 #[allow(clippy::expect_used)]
98 let ests = cst
99 .with_generated_policyids()
100 .expect("missing policy set node")
101 .map(|(id, policy)| {
102 let p = policy.node.as_ref().expect("missing policy node").clone();
103 Ok((id, p.try_into()?))
104 })
105 .collect::<Result<HashMap<ast::PolicyID, est::Policy>, err::ParseErrors>>()?;
106 Ok((ests, pset))
107}
108
109pub fn parse_policy_or_template(
114 id: Option<ast::PolicyID>,
115 text: &str,
116) -> Result<ast::Template, err::ParseErrors> {
117 let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
118 let cst = text_to_cst::parse_policy(text)?;
119 cst.to_template(id)
120}
121
122pub fn parse_policy_or_template_to_est_and_ast(
126 id: Option<ast::PolicyID>,
127 text: &str,
128) -> Result<(est::Policy, ast::Template), err::ParseErrors> {
129 let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
130 let cst = text_to_cst::parse_policy(text)?;
131 let ast = cst.to_template(id)?;
132 let est = cst.try_into_inner()?.try_into()?;
133 Ok((est, ast))
134}
135
136pub fn parse_template(
141 id: Option<ast::PolicyID>,
142 text: &str,
143) -> Result<ast::Template, err::ParseErrors> {
144 let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
145 let cst = text_to_cst::parse_policy(text)?;
146 let template = cst.to_template(id)?;
147 validate_template_has_slots(template, cst)
148}
149
150pub fn parse_policy(
155 id: Option<ast::PolicyID>,
156 text: &str,
157) -> Result<ast::StaticPolicy, err::ParseErrors> {
158 let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
159 let cst = text_to_cst::parse_policy(text)?;
160 cst.to_policy(id)
161}
162
163pub fn parse_policy_to_est_and_ast(
167 id: Option<ast::PolicyID>,
168 text: &str,
169) -> Result<(est::Policy, ast::StaticPolicy), err::ParseErrors> {
170 let id = id.unwrap_or_else(|| ast::PolicyID::from_string("policy0"));
171 let cst = text_to_cst::parse_policy(text)?;
172 let ast = cst.to_policy(id)?;
173 let est = cst.try_into_inner()?.try_into()?;
174 Ok((est, ast))
175}
176
177pub fn parse_policy_or_template_to_est(text: &str) -> Result<est::Policy, err::ParseErrors> {
179 parse_policy_or_template_to_est_and_ast(None, text).map(|(est, _ast)| est)
184}
185
186pub(crate) fn parse_expr(ptext: &str) -> Result<ast::Expr, err::ParseErrors> {
191 let cst = text_to_cst::parse_expr(ptext)?;
192 cst.to_expr::<ast::ExprBuilder<()>>()
193}
194
195pub(crate) fn parse_restrictedexpr(
200 ptext: &str,
201) -> Result<ast::RestrictedExpr, RestrictedExpressionParseError> {
202 let expr = parse_expr(ptext)?;
203 Ok(ast::RestrictedExpr::new(expr)?)
204}
205
206pub(crate) fn parse_euid(euid: &str) -> Result<ast::EntityUID, err::ParseErrors> {
211 let cst = text_to_cst::parse_ref(euid)?;
212 cst.to_ref()
213}
214
215pub(crate) fn parse_internal_name(name: &str) -> Result<ast::InternalName, err::ParseErrors> {
220 let cst = text_to_cst::parse_name(name)?;
221 cst.to_internal_name()
222}
223
224pub(crate) fn parse_literal(val: &str) -> Result<ast::Literal, err::LiteralParseError> {
229 let cst = text_to_cst::parse_primary(val)?;
230 match cst.to_expr::<ast::ExprBuilder<()>>() {
231 Ok(ast) => match ast.expr_kind() {
232 ast::ExprKind::Lit(v) => Ok(v.clone()),
233 _ => Err(err::LiteralParseError::InvalidLiteral(ast)),
234 },
235 Err(errs) => Err(err::LiteralParseError::Parse(errs)),
236 }
237}
238
239pub fn parse_internal_string(val: &str) -> Result<SmolStr, err::ParseErrors> {
250 let cst = text_to_cst::parse_primary(&format!(r#""{val}""#))?;
252 cst.to_string_literal::<ast::ExprBuilder<()>>()
253}
254
255pub(crate) fn parse_ident(id: &str) -> Result<ast::Id, err::ParseErrors> {
260 let cst = text_to_cst::parse_ident(id)?;
261 cst.to_valid_ident()
262}
263
264pub(crate) fn parse_anyid(id: &str) -> Result<ast::AnyId, err::ParseErrors> {
269 let cst = text_to_cst::parse_ident(id)?;
270 cst.to_any_ident()
271}
272
273fn validate_template_has_slots(
276 template: ast::Template,
277 cst: Node<Option<cst::Policy>>,
278) -> Result<ast::Template, err::ParseErrors> {
279 if template.slots().count() == 0 {
280 Err(err::ToASTError::new(err::ToASTErrorKind::expected_template(), cst.loc).into())
281 } else {
282 Ok(template)
283 }
284}
285
286#[cfg(test)]
288#[allow(clippy::panic)]
290pub(crate) mod test_utils {
291 use super::err::ParseErrors;
292 use crate::test_utils::*;
293
294 #[track_caller] pub fn expect_n_errors(src: &str, errs: &ParseErrors, n: usize) {
299 assert_eq!(
300 errs.len(),
301 n,
302 "for the following input:\n{src}\nexpected {n} error(s), but saw {}\nactual errors were:\n{:?}", errs.len(),
304 miette::Report::new(errs.clone())
305 );
306 }
307
308 #[track_caller] pub fn expect_some_error_matches(
313 src: &str,
314 errs: &ParseErrors,
315 msg: &ExpectedErrorMessage<'_>,
316 ) {
317 assert!(
318 errs.iter().any(|e| msg.matches(e)),
319 "for the following input:\n{src}\nexpected some error to match the following:\n{msg}\nbut actual errors were:\n{:?}", miette::Report::new(errs.clone()),
321 );
322 }
323
324 #[track_caller] pub fn expect_exactly_one_error(src: &str, errs: &ParseErrors, msg: &ExpectedErrorMessage<'_>) {
329 match errs.len() {
330 0 => panic!("for the following input:\n{src}\nexpected an error, but the `ParseErrors` was empty"),
331 1 => {
332 let err = errs.iter().next().expect("already checked that len was 1");
333 expect_err(src, &miette::Report::new(err.clone()), msg);
334 }
335 n => panic!(
336 "for the following input:\n{src}\nexpected only one error, but got {n}. Expected to match the following:\n{msg}\nbut actual errors were:\n{:?}", miette::Report::new(errs.clone()),
338 )
339 }
340 }
341}
342
343#[allow(clippy::panic, clippy::indexing_slicing)]
345#[allow(clippy::cognitive_complexity)]
346#[cfg(test)]
347mod tests {
349
350 use super::*;
351
352 use crate::ast::test_generators::*;
353 use crate::ast::{Eid, Literal, Value};
354 use crate::evaluator as eval;
355 use crate::extensions::Extensions;
356 use crate::parser::err::*;
357 use crate::parser::test_utils::*;
358 use crate::test_utils::*;
359 use cool_asserts::assert_matches;
360 use std::collections::HashSet;
361 use std::sync::Arc;
362
363 #[test]
364 fn test_template_parsing() {
365 for template in all_templates() {
366 let id = template.id();
367 let src = format!("{template}");
368 let parsed =
369 parse_policy_or_template(Some(ast::PolicyID::from_string(id)), &src).unwrap();
370 assert_eq!(
371 parsed.slots().collect::<HashSet<_>>(),
372 template.slots().collect::<HashSet<_>>()
373 );
374 assert_eq!(parsed.id(), template.id());
375 assert_eq!(parsed.effect(), template.effect());
376 assert_eq!(
377 parsed.principal_constraint(),
378 template.principal_constraint()
379 );
380 assert_eq!(parsed.action_constraint(), template.action_constraint());
381 assert_eq!(parsed.resource_constraint(), template.resource_constraint());
382 match (
383 parsed.non_scope_constraints(),
384 template.non_scope_constraints(),
385 ) {
386 (Some(parsed), Some(template)) => {
387 assert!(
388 parsed.eq_shape(template),
389 "{:?} and {:?} should have the same shape.",
390 parsed,
391 template
392 );
393 }
394 (Some(_), None) | (None, Some(_)) => {
395 panic!(
396 "{:?} and {:?} should have the same shape.",
397 parsed, template
398 )
399 }
400 (None, None) => (),
401 }
402 }
403 }
404
405 #[test]
406 fn test_error_out() {
407 let src = r#"
408 permit(principal:p,action:a,resource:r)
409 when{w or if c but not z} // expr error
410 unless{u if c else d or f} // expr error
411 advice{"doit"};
412
413 permit(principality in Group::"jane_friends", // policy error
414 action in [PhotoOp::"view", PhotoOp::"comment"],
415 resource in Album::"jane_trips");
416
417 forbid(principal, action, resource)
418 when { "private" in resource.tags }
419 unless { resource in principal.account };
420 "#;
421 let errs = parse_policyset(src).expect_err("expected parsing to fail");
422 let unrecognized_tokens = vec![
423 ("or", "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `}`, `has`, `in`, `is`, or `like`"),
424 ("if", "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `}`, `has`, `in`, `is`, or `like`"),
425 ];
426 for (token, label) in unrecognized_tokens {
427 expect_some_error_matches(
428 src,
429 &errs,
430 &ExpectedErrorMessageBuilder::error(&format!("unexpected token `{token}`"))
431 .exactly_one_underline_with_label(token, label)
432 .build(),
433 );
434 }
435 expect_n_errors(src, &errs, 2);
436 assert!(errs.iter().all(|err| matches!(err, ParseError::ToCST(_))));
437 }
438
439 #[test]
440 fn entity_literals1() {
441 let src = r#"Test::{ test : "Test" }"#;
442 let errs = parse_euid(src).unwrap_err();
443 expect_exactly_one_error(
444 src,
445 &errs,
446 &ExpectedErrorMessageBuilder::error("invalid entity literal: Test::{test: \"Test\"}")
447 .help("entity literals should have a form like `Namespace::User::\"alice\"`")
448 .exactly_one_underline("Test::{ test : \"Test\" }")
449 .build(),
450 );
451 }
452
453 #[test]
454 fn entity_literals2() {
455 let src = r#"permit(principal == Test::{ test : "Test" }, action, resource);"#;
456 let errs = parse_policy(None, src).unwrap_err();
457 expect_exactly_one_error(
458 src,
459 &errs,
460 &ExpectedErrorMessageBuilder::error("invalid entity literal: Test::{test: \"Test\"}")
461 .help("entity literals should have a form like `Namespace::User::\"alice\"`")
462 .exactly_one_underline("Test::{ test : \"Test\" }")
463 .build(),
464 );
465 }
466
467 #[test]
468 fn interpret_exprs() {
469 let request = eval::test::basic_request();
470 let entities = eval::test::basic_entities();
471 let exts = Extensions::none();
472 let evaluator = eval::Evaluator::new(request, &entities, exts);
473 let src = "false";
485 let expr = parse_expr(src).unwrap();
486 let val = evaluator.interpret_inline_policy(&expr).unwrap();
487 assert_eq!(val, Value::from(false));
488 assert_eq!(val.source_loc(), Some(&Loc::new(0..5, Arc::from(src))));
489
490 let src = "true && true";
491 let expr = parse_expr(src).unwrap();
492 let val = evaluator.interpret_inline_policy(&expr).unwrap();
493 assert_eq!(val, Value::from(true));
494 assert_eq!(val.source_loc(), Some(&Loc::new(0..12, Arc::from(src))));
495
496 let src = "!true || false && !true";
497 let expr = parse_expr(src).unwrap();
498 let val = evaluator.interpret_inline_policy(&expr).unwrap();
499 assert_eq!(val, Value::from(false));
500 assert_eq!(val.source_loc(), Some(&Loc::new(0..23, Arc::from(src))));
501
502 let src = "!!!!true";
503 let expr = parse_expr(src).unwrap();
504 let val = evaluator.interpret_inline_policy(&expr).unwrap();
505 assert_eq!(val, Value::from(true));
506 assert_eq!(val.source_loc(), Some(&Loc::new(0..8, Arc::from(src))));
507
508 let src = r#"
509 if false || true != 4 then
510 600
511 else
512 -200
513 "#;
514 let expr = parse_expr(src).unwrap();
515 let val = evaluator.interpret_inline_policy(&expr).unwrap();
516 assert_eq!(val, Value::from(600));
517 assert_eq!(val.source_loc(), Some(&Loc::new(9..81, Arc::from(src))));
518 }
519
520 #[test]
521 fn interpret_membership() {
522 let request = eval::test::basic_request();
523 let entities = eval::test::rich_entities();
524 let exts = Extensions::none();
525 let evaluator = eval::Evaluator::new(request, &entities, exts);
526 let src = r#"
531
532 test_entity_type::"child" in
533 test_entity_type::"unrelated"
534
535 "#;
536 let expr = parse_expr(src).unwrap();
537 let val = evaluator.interpret_inline_policy(&expr).unwrap();
538 assert_eq!(val, Value::from(false));
539 assert_eq!(val.source_loc(), Some(&Loc::new(10..80, Arc::from(src))));
540 assert_eq!(
542 val.source_loc().unwrap().snippet(),
543 Some(
544 r#"test_entity_type::"child" in
545 test_entity_type::"unrelated""#
546 )
547 );
548
549 let src = r#"
550
551 test_entity_type::"child" in
552 test_entity_type::"child"
553
554 "#;
555 let expr = parse_expr(src).unwrap();
556 let val = evaluator.interpret_inline_policy(&expr).unwrap();
557 assert_eq!(val, Value::from(true));
558 assert_eq!(val.source_loc(), Some(&Loc::new(10..76, Arc::from(src))));
559 assert_eq!(
560 val.source_loc().unwrap().snippet(),
561 Some(
562 r#"test_entity_type::"child" in
563 test_entity_type::"child""#
564 )
565 );
566
567 let src = r#"
568
569 other_type::"other_child" in
570 test_entity_type::"parent"
571
572 "#;
573 let expr = parse_expr(src).unwrap();
574 let val = evaluator.interpret_inline_policy(&expr).unwrap();
575 assert_eq!(val, Value::from(true));
576 assert_eq!(val.source_loc(), Some(&Loc::new(10..77, Arc::from(src))));
577 assert_eq!(
578 val.source_loc().unwrap().snippet(),
579 Some(
580 r#"other_type::"other_child" in
581 test_entity_type::"parent""#
582 )
583 );
584
585 let src = r#"
586
587 test_entity_type::"child" in
588 test_entity_type::"grandparent"
589
590 "#;
591 let expr = parse_expr(src).unwrap();
592 let val = evaluator.interpret_inline_policy(&expr).unwrap();
593 assert_eq!(val, Value::from(true));
594 assert_eq!(val.source_loc(), Some(&Loc::new(10..82, Arc::from(src))));
595 assert_eq!(
596 val.source_loc().unwrap().snippet(),
597 Some(
598 r#"test_entity_type::"child" in
599 test_entity_type::"grandparent""#
600 )
601 );
602 }
603
604 #[test]
606 fn interpret_relation() {
607 let request = eval::test::basic_request();
608 let entities = eval::test::basic_entities();
609 let exts = Extensions::none();
610 let evaluator = eval::Evaluator::new(request, &entities, exts);
611 let src = r#"
616
617 3 < 2 || 2 > 3
618
619 "#;
620 let expr = parse_expr(src).unwrap();
621 let val = evaluator.interpret_inline_policy(&expr).unwrap();
622 assert_eq!(val, Value::from(false));
623 assert_eq!(val.source_loc(), Some(&Loc::new(14..28, Arc::from(src))));
624 assert_eq!(val.source_loc().unwrap().snippet(), Some("3 < 2 || 2 > 3"));
626
627 let src = r#"
628
629 7 <= 7 && 4 != 5
630
631 "#;
632 let expr = parse_expr(src).unwrap();
633 let val = evaluator.interpret_inline_policy(&expr).unwrap();
634 assert_eq!(val, Value::from(true));
635 assert_eq!(val.source_loc(), Some(&Loc::new(14..30, Arc::from(src))));
636 assert_eq!(
637 val.source_loc().unwrap().snippet(),
638 Some("7 <= 7 && 4 != 5")
639 );
640 }
641
642 #[test]
644 fn interpret_methods() {
645 let src = r#"
646 [2, 3, "foo"].containsAll([3, "foo"])
647 && context.violations.isEmpty()
648 && principal.hasTag(resource.getTag(context.cur_time))
649 "#;
650 let request = eval::test::basic_request();
651 let entities = eval::test::basic_entities();
652 let exts = Extensions::none();
653 let evaluator = eval::Evaluator::new(request, &entities, exts);
654
655 let expr = parse_expr(src).unwrap();
656 assert_matches!(evaluator.interpret_inline_policy(&expr), Err(e) => {
657 expect_err(
658 src,
659 &miette::Report::new(e),
660 &ExpectedErrorMessageBuilder::error(r#"`test_entity_type::"test_resource"` does not have the tag `03:22:11`"#)
661 .help(r#"`test_entity_type::"test_resource"` does not have any tags"#)
662 .exactly_one_underline("resource.getTag(context.cur_time)")
663 .build(),
664 );
665 });
666 }
667
668 #[test]
669 fn unquoted_tags() {
670 let src = r#"
671 principal.hasTag(foo)
672 "#;
673 assert_matches!(parse_expr(src), Err(e) => {
674 expect_err(
675 src,
676 &miette::Report::new(e),
677 &ExpectedErrorMessageBuilder::error("invalid variable: foo")
678 .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `foo` in quotes to make a string?")
679 .exactly_one_underline("foo")
680 .build(),
681 );
682 });
683
684 let src = r#"
685 principal.getTag(foo)
686 "#;
687 assert_matches!(parse_expr(src), Err(e) => {
688 expect_err(
689 src,
690 &miette::Report::new(e),
691 &ExpectedErrorMessageBuilder::error("invalid variable: foo")
692 .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `foo` in quotes to make a string?")
693 .exactly_one_underline("foo")
694 .build(),
695 );
696 });
697 }
698
699 #[test]
700 fn parse_exists() {
701 let result = parse_policyset(
702 r#"
703 permit(principal, action, resource)
704 when{ true };
705 "#,
706 );
707 assert!(!result.expect("parse error").is_empty());
708 }
709
710 #[test]
711 fn attr_named_tags() {
712 let src = r#"
713 permit(principal, action, resource)
714 when {
715 resource.tags.contains({k: "foo", v: "bar"})
716 };
717 "#;
718 parse_policy_to_est_and_ast(None, src)
719 .unwrap_or_else(|e| panic!("{:?}", &miette::Report::new(e)));
720 }
721
722 #[test]
723 fn test_parse_policyset() {
724 use crate::ast::PolicyID;
725 let multiple_policies = r#"
726 permit(principal, action, resource)
727 when { principal == resource.owner };
728
729 forbid(principal, action == Action::"modify", resource) // a comment
730 when { resource . highSecurity }; // intentionally not conforming to our formatter
731 "#;
732 let pset = parse_policyset(multiple_policies).expect("Should parse");
733 assert_eq!(pset.policies().count(), 2);
734 assert_eq!(pset.static_policies().count(), 2);
735 let (texts, pset) =
736 parse_policyset_and_also_return_policy_text(multiple_policies).expect("Should parse");
737 assert_eq!(pset.policies().count(), 2);
738 assert_eq!(pset.static_policies().count(), 2);
739 assert_eq!(texts.len(), 2);
740 assert_eq!(
741 texts.get(&PolicyID::from_string("policy0")),
742 Some(&Some(
743 r#"permit(principal, action, resource)
744 when { principal == resource.owner };"#
745 ))
746 );
747 assert_eq!(
748 texts.get(&PolicyID::from_string("policy1")),
749 Some(&Some(
750 r#"forbid(principal, action == Action::"modify", resource) // a comment
751 when { resource . highSecurity };"#
752 ))
753 );
754 }
755
756 #[test]
757 fn test_parse_string() {
758 assert_eq!(
760 Eid::new(parse_internal_string(r"a\nblock\nid").expect("should parse")).escaped(),
761 r"a\nblock\nid",
762 );
763 parse_internal_string(r#"oh, no, a '! "#).expect("single quote should be fine");
764 parse_internal_string(r#"oh, no, a \"! and a \'! "#).expect("escaped quotes should parse");
765 let src = r#"oh, no, a "! "#;
766 let errs = parse_internal_string(src).expect_err("unescaped double quote not allowed");
767 expect_exactly_one_error(
768 src,
769 &errs,
770 &ExpectedErrorMessageBuilder::error("invalid token")
771 .exactly_one_underline("")
772 .build(),
773 );
774 }
775
776 #[test]
777 fn good_cst_bad_ast() {
778 let src = r#"
779 permit(principal, action, resource) when { principal.name.like == "3" };
780 "#;
781 let p = parse_policyset_to_ests_and_pset(src);
782 assert_matches!(p, Err(e) => expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: like").exactly_one_underline("like").build()));
783 }
784
785 #[test]
786 fn no_slots_in_condition() {
787 let src = r#"
788 permit(principal, action, resource) when {
789 resource == ?resource
790 };
791 "#;
792 let slot_in_when_clause =
793 ExpectedErrorMessageBuilder::error("found template slot ?resource in a `when` clause")
794 .help("slots are currently unsupported in `when` clauses")
795 .exactly_one_underline("?resource")
796 .build();
797 let unexpected_template = ExpectedErrorMessageBuilder::error(
798 "expected a static policy, got a template containing the slot ?resource",
799 )
800 .help("try removing the template slot(s) from this policy")
801 .exactly_one_underline("?resource")
802 .build();
803 assert_matches!(parse_policy(None, src), Err(e) => {
804 expect_n_errors(src, &e, 2);
805 expect_some_error_matches(src, &e, &slot_in_when_clause);
806 expect_some_error_matches(src, &e, &unexpected_template);
807 });
808 assert_matches!(parse_policy_or_template(None, src), Err(e) => {
809 expect_exactly_one_error(src, &e, &slot_in_when_clause);
810 });
811 assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
812 expect_n_errors(src, &e, 2);
813 expect_some_error_matches(src, &e, &slot_in_when_clause);
814 expect_some_error_matches(src, &e, &unexpected_template);
815 });
816 assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
817 expect_exactly_one_error(src, &e, &slot_in_when_clause);
818 });
819 assert_matches!(parse_policyset(src), Err(e) => {
820 expect_exactly_one_error(src, &e, &slot_in_when_clause);
821 });
822 assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
823 expect_exactly_one_error(src, &e, &slot_in_when_clause);
824 });
825
826 let src = r#"
827 permit(principal, action, resource) when {
828 resource == ?principal
829 };
830 "#;
831 let slot_in_when_clause =
832 ExpectedErrorMessageBuilder::error("found template slot ?principal in a `when` clause")
833 .help("slots are currently unsupported in `when` clauses")
834 .exactly_one_underline("?principal")
835 .build();
836 let unexpected_template = ExpectedErrorMessageBuilder::error(
837 "expected a static policy, got a template containing the slot ?principal",
838 )
839 .help("try removing the template slot(s) from this policy")
840 .exactly_one_underline("?principal")
841 .build();
842 assert_matches!(parse_policy(None, src), Err(e) => {
843 expect_n_errors(src, &e, 2);
844 expect_some_error_matches(src, &e, &slot_in_when_clause);
845 expect_some_error_matches(src, &e, &unexpected_template);
846 });
847 assert_matches!(parse_policy_or_template(None, src), Err(e) => {
848 expect_exactly_one_error(src, &e, &slot_in_when_clause);
849 });
850 assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
851 expect_n_errors(src, &e, 2);
852 expect_some_error_matches(src, &e, &slot_in_when_clause);
853 expect_some_error_matches(src, &e, &unexpected_template);
854 });
855 assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
856 expect_exactly_one_error(src, &e, &slot_in_when_clause);
857 });
858 assert_matches!(parse_policyset(src), Err(e) => {
859 expect_exactly_one_error(src, &e, &slot_in_when_clause);
860 });
861 assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
862 expect_exactly_one_error(src, &e, &slot_in_when_clause);
863 });
864
865 let src = r#"
866 permit(principal, action, resource) when {
867 resource == ?blah
868 };
869 "#;
870 let error = ExpectedErrorMessageBuilder::error("`?blah` is not a valid template slot")
871 .help("a template slot may only be `?principal` or `?resource`")
872 .exactly_one_underline("?blah")
873 .build();
874 assert_matches!(parse_policy(None, src), Err(e) => {
875 expect_exactly_one_error(src, &e, &error);
876 });
877 assert_matches!(parse_policy_or_template(None, src), Err(e) => {
878 expect_exactly_one_error(src, &e, &error);
879 });
880 assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
881 expect_exactly_one_error(src, &e, &error);
882 });
883 assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
884 expect_exactly_one_error(src, &e, &error);
885 });
886 assert_matches!(parse_policyset(src), Err(e) => {
887 expect_exactly_one_error(src, &e, &error);
888 });
889 assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
890 expect_exactly_one_error(src, &e, &error);
891 });
892
893 let src = r#"
894 permit(principal, action, resource) unless {
895 resource == ?resource
896 };
897 "#;
898 let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
899 "found template slot ?resource in a `unless` clause",
900 )
901 .help("slots are currently unsupported in `unless` clauses")
902 .exactly_one_underline("?resource")
903 .build();
904 let unexpected_template = ExpectedErrorMessageBuilder::error(
905 "expected a static policy, got a template containing the slot ?resource",
906 )
907 .help("try removing the template slot(s) from this policy")
908 .exactly_one_underline("?resource")
909 .build();
910 assert_matches!(parse_policy(None, src), Err(e) => {
911 expect_n_errors(src, &e, 2);
912 expect_some_error_matches(src, &e, &slot_in_unless_clause);
913 expect_some_error_matches(src, &e, &unexpected_template);
914 });
915 assert_matches!(parse_policy_or_template(None, src), Err(e) => {
916 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
917 });
918 assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
919 expect_n_errors(src, &e, 2);
920 expect_some_error_matches(src, &e, &slot_in_unless_clause);
921 expect_some_error_matches(src, &e, &unexpected_template);
922 });
923 assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
924 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
925 });
926 assert_matches!(parse_policyset(src), Err(e) => {
927 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
928 });
929 assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
930 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
931 });
932
933 let src = r#"
934 permit(principal, action, resource) unless {
935 resource == ?principal
936 };
937 "#;
938 let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
939 "found template slot ?principal in a `unless` clause",
940 )
941 .help("slots are currently unsupported in `unless` clauses")
942 .exactly_one_underline("?principal")
943 .build();
944 let unexpected_template = ExpectedErrorMessageBuilder::error(
945 "expected a static policy, got a template containing the slot ?principal",
946 )
947 .help("try removing the template slot(s) from this policy")
948 .exactly_one_underline("?principal")
949 .build();
950 assert_matches!(parse_policy(None, src), Err(e) => {
951 expect_n_errors(src, &e, 2);
952 expect_some_error_matches(src, &e, &slot_in_unless_clause);
953 expect_some_error_matches(src, &e, &unexpected_template);
954 });
955 assert_matches!(parse_policy_or_template(None, src), Err(e) => {
956 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
957 });
958 assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
959 expect_n_errors(src, &e, 2);
960 expect_some_error_matches(src, &e, &slot_in_unless_clause);
961 expect_some_error_matches(src, &e, &unexpected_template);
962 });
963 assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
964 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
965 });
966 assert_matches!(parse_policyset(src), Err(e) => {
967 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
968 });
969 assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
970 expect_exactly_one_error(src, &e, &slot_in_unless_clause);
971 });
972
973 let src = r#"
974 permit(principal, action, resource) unless {
975 resource == ?blah
976 };
977 "#;
978 let error = ExpectedErrorMessageBuilder::error("`?blah` is not a valid template slot")
979 .help("a template slot may only be `?principal` or `?resource`")
980 .exactly_one_underline("?blah")
981 .build();
982 assert_matches!(parse_policy(None, src), Err(e) => {
983 expect_exactly_one_error(src, &e, &error);
984 });
985 assert_matches!(parse_policy_or_template(None, src), Err(e) => {
986 expect_exactly_one_error(src, &e, &error);
987 });
988 assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
989 expect_exactly_one_error(src, &e, &error);
990 });
991 assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
992 expect_exactly_one_error(src, &e, &error);
993 });
994 assert_matches!(parse_policyset(src), Err(e) => {
995 expect_exactly_one_error(src, &e, &error);
996 });
997 assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
998 expect_exactly_one_error(src, &e, &error);
999 });
1000
1001 let src = r#"
1002 permit(principal, action, resource) unless {
1003 resource == ?resource
1004 } when {
1005 resource == ?resource
1006 };
1007 "#;
1008 let slot_in_when_clause =
1009 ExpectedErrorMessageBuilder::error("found template slot ?resource in a `when` clause")
1010 .help("slots are currently unsupported in `when` clauses")
1011 .exactly_one_underline("?resource")
1012 .build();
1013 let slot_in_unless_clause = ExpectedErrorMessageBuilder::error(
1014 "found template slot ?resource in a `unless` clause",
1015 )
1016 .help("slots are currently unsupported in `unless` clauses")
1017 .exactly_one_underline("?resource")
1018 .build();
1019 let unexpected_template = ExpectedErrorMessageBuilder::error(
1020 "expected a static policy, got a template containing the slot ?resource",
1021 )
1022 .help("try removing the template slot(s) from this policy")
1023 .exactly_one_underline("?resource")
1024 .build();
1025 assert_matches!(parse_policy(None, src), Err(e) => {
1026 expect_n_errors(src, &e, 4);
1027 expect_some_error_matches(src, &e, &slot_in_when_clause);
1028 expect_some_error_matches(src, &e, &slot_in_unless_clause);
1029 expect_some_error_matches(src, &e, &unexpected_template); });
1031 assert_matches!(parse_policy_or_template(None, src), Err(e) => {
1032 expect_n_errors(src, &e, 2);
1033 expect_some_error_matches(src, &e, &slot_in_when_clause);
1034 expect_some_error_matches(src, &e, &slot_in_unless_clause);
1035 });
1036 assert_matches!(parse_policy_to_est_and_ast(None, src), Err(e) => {
1037 expect_n_errors(src, &e, 4);
1038 expect_some_error_matches(src, &e, &slot_in_when_clause);
1039 expect_some_error_matches(src, &e, &slot_in_unless_clause);
1040 expect_some_error_matches(src, &e, &unexpected_template); });
1042 assert_matches!(parse_policy_or_template_to_est_and_ast(None, src), Err(e) => {
1043 expect_n_errors(src, &e, 2);
1044 expect_some_error_matches(src, &e, &slot_in_when_clause);
1045 expect_some_error_matches(src, &e, &slot_in_unless_clause);
1046 });
1047 assert_matches!(parse_policyset(src), Err(e) => {
1048 expect_n_errors(src, &e, 2);
1049 expect_some_error_matches(src, &e, &slot_in_when_clause);
1050 expect_some_error_matches(src, &e, &slot_in_unless_clause);
1051 });
1052 assert_matches!(parse_policyset_to_ests_and_pset(src), Err(e) => {
1053 expect_n_errors(src, &e, 2);
1054 expect_some_error_matches(src, &e, &slot_in_when_clause);
1055 expect_some_error_matches(src, &e, &slot_in_unless_clause);
1056 });
1057 }
1058
1059 #[test]
1060 fn record_literals() {
1061 let src = r#"permit(principal, action, resource) when { context.foo == { foo: 2, bar: "baz" } };"#;
1063 assert_matches!(parse_policy(None, src), Ok(_));
1064 let src = r#"permit(principal, action, resource) when { context.foo == { "foo": 2, "hi mom it's 🦀": "baz" } };"#;
1066 assert_matches!(parse_policy(None, src), Ok(_));
1067 let src = r#"permit(principal, action, resource) when { context.foo == { "spam": -341, foo: 2, "🦀": true, foo: "baz" } };"#;
1069 assert_matches!(parse_policy(None, src), Err(e) => {
1070 expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate key `foo` in record literal").exactly_one_underline(r#"{ "spam": -341, foo: 2, "🦀": true, foo: "baz" }"#).build());
1071 });
1072 }
1073
1074 #[test]
1075 fn annotation_errors() {
1076 let src = r#"
1077 @foo("1")
1078 @foo("2")
1079 permit(principal, action, resource);
1080 "#;
1081 assert_matches!(parse_policy(None, src), Err(e) => {
1082 expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("2")"#).build());
1083 });
1084
1085 let src = r#"
1086 @foo("1")
1087 @foo("1")
1088 permit(principal, action, resource);
1089 "#;
1090 assert_matches!(parse_policy(None, src), Err(e) => {
1091 expect_exactly_one_error(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("1")"#).build());
1092 });
1093
1094 let src = r#"
1095 @foo("1")
1096 @bar("yellow")
1097 @foo("abc")
1098 @hello("goodbye")
1099 @bar("123")
1100 @foo("def")
1101 permit(principal, action, resource);
1102 "#;
1103 assert_matches!(parse_policy(None, src), Err(e) => {
1104 expect_n_errors(src, &e, 3); expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("abc")"#).build());
1106 expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("def")"#).build());
1107 expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @bar").exactly_one_underline(r#"@bar("123")"#).build());
1108 })
1109 }
1110
1111 #[test]
1112 fn unexpected_token_errors() {
1113 #[track_caller]
1114 fn assert_labeled_span(src: &str, msg: &str, underline: &str, label: &str) {
1115 assert_matches!(parse_policy(None, src), Err(e) => {
1116 expect_exactly_one_error(
1117 src,
1118 &e,
1119 &ExpectedErrorMessageBuilder::error(msg)
1120 .exactly_one_underline_with_label(underline, label)
1121 .build());
1122 });
1123 }
1124
1125 assert_labeled_span("@", "unexpected end of input", "", "expected identifier");
1127 assert_labeled_span(
1128 "permit(principal, action, resource) when { principal.",
1129 "unexpected end of input",
1130 "",
1131 "expected identifier",
1132 );
1133
1134 assert_labeled_span(
1137 "permit(principal, action, resource)",
1138 "unexpected end of input",
1139 "",
1140 "expected `;` or identifier",
1141 );
1142 assert_labeled_span(
1145 "@if(\"a\")",
1146 "unexpected end of input",
1147 "",
1148 "expected `@` or identifier",
1149 );
1150 assert_labeled_span(
1154 "permit(",
1155 "unexpected end of input",
1156 "",
1157 "expected `)` or identifier",
1158 );
1159 assert_labeled_span(
1160 "permit(,,);",
1161 "unexpected token `,`",
1162 ",",
1163 "expected `)` or identifier",
1164 );
1165 assert_labeled_span(
1166 "permit(principal,",
1167 "unexpected end of input",
1168 "",
1169 "expected `)` or identifier",
1170 );
1171 assert_labeled_span(
1172 "permit(principal,action,",
1173 "unexpected end of input",
1174 "",
1175 "expected `)` or identifier",
1176 );
1177 assert_labeled_span(
1179 "permit(principal,action,resource,",
1180 "unexpected end of input",
1181 "",
1182 "expected `)` or identifier",
1183 );
1184 assert_labeled_span(
1187 "permit(principal, action, resource) when {",
1188 "unexpected end of input",
1189 "",
1190 "expected `!`, `(`, `-`, `[`, `{`, `}`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`",
1191 );
1192 assert_labeled_span(
1197 "permit(principal, action, resource) when { principal is",
1198 "unexpected end of input",
1199 "",
1200 "expected `!`, `(`, `-`, `[`, `{`, `false`, identifier, `if`, number, `?principal`, `?resource`, string literal, or `true`",
1201 );
1202
1203 assert_labeled_span(
1207 "permit(principal, action, resource) when { if true",
1208 "unexpected end of input",
1209 "",
1210 "expected `!=`, `&&`, `(`, `*`, `+`, `-`, `.`, `::`, `<`, `<=`, `==`, `>`, `>=`, `[`, `||`, `has`, `in`, `is`, `like`, or `then`",
1211 )
1212 }
1213
1214 #[test]
1215 fn string_escapes() {
1216 let test_valid = |s: &str| {
1223 let r = parse_literal(&format!("\"{}\"", s.escape_default()));
1224 assert_eq!(r, Ok(Literal::String(s.into())));
1225 };
1226 test_valid("\t");
1227 test_valid("\0");
1228 test_valid("👍");
1229 test_valid("🐈");
1230 test_valid("\u{1F408}");
1231 test_valid("abc\tde\\fg");
1232 test_valid("aaa\u{1F408}bcd👍👍👍");
1233 let test_invalid = |s: &str, bad_escapes: Vec<&str>| {
1235 let src: &str = &format!("\"{s}\"");
1236 assert_matches!(parse_literal(src), Err(LiteralParseError::Parse(e)) => {
1237 expect_n_errors(src, &e, bad_escapes.len());
1238 bad_escapes.iter().for_each(|esc|
1239 expect_some_error_matches(
1240 src,
1241 &e,
1242 &ExpectedErrorMessageBuilder::error(&format!("the input `{esc}` is not a valid escape"))
1243 .exactly_one_underline(src)
1244 .build()
1245 )
1246 );
1247 })
1248 };
1249 test_invalid("\\a", vec!["\\a"]);
1251 test_invalid("\\b", vec!["\\b"]);
1253 test_invalid("\\\\aa\\p", vec!["\\p"]);
1255 test_invalid(r"\aaa\u{}", vec!["\\a", "\\u{}"]);
1257 }
1258}