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