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