1use super::cst;
39use super::err::{
40 self, ParseError, ParseErrors, Ref, RefCreationError, ToASTError, ToASTErrorKind,
41};
42use super::loc::Loc;
43use super::node::Node;
44use super::unescape::{to_pattern, to_unescaped_string};
45use crate::ast::{
46 self, ActionConstraint, CallStyle, EntityReference, EntityUID, Integer, PatternElem,
47 PolicySetError, PrincipalConstraint, PrincipalOrResourceConstraint, ResourceConstraint,
48};
49use crate::est::extract_single_argument;
50use itertools::{Either, Itertools};
51use smol_str::SmolStr;
52use std::cmp::Ordering;
53use std::collections::{BTreeMap, HashSet};
54use std::mem;
55use std::sync::Arc;
56
57struct ExtStyles<'a> {
59 functions: HashSet<&'a ast::Name>,
60 methods: HashSet<&'a str>,
61}
62
63lazy_static::lazy_static! {
65 static ref EXTENSION_STYLES: ExtStyles<'static> = load_styles();
66}
67fn load_styles() -> ExtStyles<'static> {
68 let mut functions = HashSet::new();
69 let mut methods = HashSet::new();
70 for func in crate::extensions::Extensions::all_available().all_funcs() {
71 match func.style() {
72 CallStyle::FunctionStyle => functions.insert(func.name()),
73 CallStyle::MethodStyle => methods.insert(func.name().basename().as_ref()),
74 };
75 }
76 ExtStyles { functions, methods }
77}
78
79impl Node<Option<cst::Policies>> {
80 pub fn with_generated_policyids(
83 &self,
84 ) -> Option<impl Iterator<Item = (ast::PolicyID, &Node<Option<cst::Policy>>)>> {
85 let policies = self.as_inner()?;
89
90 Some(
91 policies
92 .0
93 .iter()
94 .enumerate()
95 .map(|(count, node)| (ast::PolicyID::from_string(format!("policy{count}")), node)),
96 )
97 }
98
99 pub fn to_policyset(&self, errs: &mut ParseErrors) -> Option<ast::PolicySet> {
101 let mut pset = ast::PolicySet::new();
102 let mut complete_set = true;
103 for (policy_id, policy) in self.with_generated_policyids()? {
107 match policy.to_policy_or_template(policy_id, errs) {
109 Some(Either::Right(template)) => {
110 if let Err(e) = pset.add_template(template) {
111 match e {
112 PolicySetError::Occupied { id } => {
113 errs.push(self.to_ast_err(ToASTErrorKind::DuplicateTemplateId(id)))
114 }
115 };
116
117 complete_set = false
118 }
119 }
120 Some(Either::Left(inline_policy)) => {
121 if let Err(e) = pset.add_static(inline_policy) {
122 match e {
123 PolicySetError::Occupied { id } => {
124 errs.push(self.to_ast_err(ToASTErrorKind::DuplicatePolicyId(id)))
125 }
126 };
127
128 complete_set = false
129 }
130 }
131 None => complete_set = false,
132 };
133 }
134
135 if complete_set {
137 Some(pset)
138 } else {
139 None
140 }
141 }
142}
143
144impl Node<Option<cst::Policy>> {
145 pub fn to_policy_or_template(
147 &self,
148 id: ast::PolicyID,
149 errs: &mut ParseErrors,
150 ) -> Option<Either<ast::StaticPolicy, ast::Template>> {
151 let t = self.to_policy_template(id, errs)?;
152 if t.slots().count() == 0 {
153 ast::StaticPolicy::try_from(t).ok().map(Either::Left)
155 } else {
156 Some(Either::Right(t))
157 }
158 }
159
160 pub fn to_policy(
162 &self,
163 id: ast::PolicyID,
164 errs: &mut ParseErrors,
165 ) -> Option<ast::StaticPolicy> {
166 let tp = self.to_policy_template(id, errs);
167 let policy = tp.map(ast::StaticPolicy::try_from);
171 let new_errs = errs
186 .iter()
187 .filter_map(|err| match err {
188 ParseError::ToAST(err) => match err.kind() {
189 ToASTErrorKind::SlotsInConditionClause { slot, .. } => Some(ToASTError::new(
190 ToASTErrorKind::expected_static_policy(slot.clone()),
191 err.source_loc().clone(),
192 )),
193 _ => None,
194 },
195 _ => None,
196 })
197 .collect::<Vec<_>>();
198 errs.extend(new_errs);
199
200 match policy {
203 Some(Err(ast::UnexpectedSlotError::FoundSlot(slot))) => {
204 errs.push(ToASTError::new(
205 ToASTErrorKind::expected_static_policy(slot.clone()),
206 slot.loc.unwrap_or_else(|| self.loc.clone()),
207 ));
208 None
209 }
210 Some(Ok(p)) => Some(p),
212 None => None,
213 }
214 }
215
216 pub fn to_policy_template(
219 &self,
220 id: ast::PolicyID,
221 errs: &mut ParseErrors,
222 ) -> Option<ast::Template> {
223 let policy = self.as_inner()?;
227
228 let maybe_effect = policy.effect.to_effect(errs);
230
231 let (annot_success, annotations) = policy.get_ast_annotations(errs);
233 let mut failure = !annot_success;
234
235 let (maybe_principal, maybe_action, maybe_resource) = policy.extract_scope(errs);
237
238 let conds: Vec<_> = policy
240 .conds
241 .iter()
242 .filter_map(|c| {
243 let (e, is_when) = c.to_expr(errs)?;
244 for slot in e.slots() {
245 errs.push(ToASTError::new(
246 ToASTErrorKind::SlotsInConditionClause {
247 slot: slot.clone(),
248 clausetype: if is_when { "when" } else { "unless" },
249 },
250 slot.loc.unwrap_or_else(|| c.loc.clone()),
251 ));
252 }
253 Some(e)
254 })
255 .collect();
256
257 if conds.len() != policy.conds.len() {
258 failure = true
259 }
260
261 if failure || !errs.is_empty() {
263 return None;
264 };
265 let effect = maybe_effect?;
266 let principal = maybe_principal?;
267 let action = maybe_action?;
268 let resource = maybe_resource?;
269
270 Some(construct_template_policy(
271 id,
272 annotations,
273 effect,
274 principal,
275 action,
276 resource,
277 conds,
278 &self.loc,
279 ))
280 }
281}
282
283impl cst::Policy {
284 pub fn extract_scope(
286 &self,
287 errs: &mut ParseErrors,
288 ) -> (
289 Option<PrincipalConstraint>,
290 Option<ActionConstraint>,
291 Option<ResourceConstraint>,
292 ) {
293 let mut end_of_last_var = self.effect.loc.end();
296
297 let mut vars = self.variables.iter().peekable();
298 let principal = if let Some(scope1) = vars.next() {
299 end_of_last_var = scope1.loc.end();
300 scope1.to_principal_constraint(errs)
301 } else {
302 errs.push(ToASTError::new(
303 ToASTErrorKind::MissingScopeConstraint(ast::Var::Principal),
304 self.effect.loc.span(end_of_last_var),
305 ));
306 None
307 };
308 let action = if let Some(scope2) = vars.next() {
309 end_of_last_var = scope2.loc.end();
310 scope2.to_action_constraint(errs)
311 } else {
312 errs.push(ToASTError::new(
313 ToASTErrorKind::MissingScopeConstraint(ast::Var::Action),
314 self.effect.loc.span(end_of_last_var),
315 ));
316 None
317 };
318 let resource = if let Some(scope3) = vars.next() {
319 scope3.to_resource_constraint(errs)
320 } else {
321 errs.push(ToASTError::new(
322 ToASTErrorKind::MissingScopeConstraint(ast::Var::Resource),
323 self.effect.loc.span(end_of_last_var),
324 ));
325 None
326 };
327 if vars.peek().is_some() {
328 for extra_var in vars {
331 if let Some(def) = extra_var.as_inner() {
332 errs.push(
333 extra_var.to_ast_err(ToASTErrorKind::ExtraScopeConstraints(def.clone())),
334 )
335 }
336 }
337 }
338 (principal, action, resource)
339 }
340
341 pub fn get_ast_annotations(&self, errs: &mut ParseErrors) -> (bool, ast::Annotations) {
349 let mut failure = false;
350 let mut annotations = BTreeMap::new();
351 for node in self.annotations.iter() {
352 match node.to_kv_pair(errs) {
353 Some((k, v)) => {
354 use std::collections::btree_map::Entry;
355 match annotations.entry(k) {
356 Entry::Occupied(oentry) => {
357 failure = true;
358 errs.push(ToASTError::new(
359 ToASTErrorKind::DuplicateAnnotation(oentry.key().clone()),
360 node.loc.clone(),
361 ));
362 }
363 Entry::Vacant(ventry) => {
364 ventry.insert(v);
365 }
366 }
367 }
368 None => {
369 failure = true;
370 }
372 }
373 }
374 (!failure, annotations.into())
375 }
376}
377
378impl Node<Option<cst::Annotation>> {
379 pub fn to_kv_pair(&self, errs: &mut ParseErrors) -> Option<(ast::AnyId, ast::Annotation)> {
382 let anno = self.as_inner()?;
386
387 let maybe_key = anno.key.to_any_ident(errs);
388 let maybe_value = anno.value.as_valid_string(errs);
389 let maybe_value = match maybe_value.map(|s| to_unescaped_string(s)).transpose() {
390 Ok(maybe_value) => maybe_value,
391 Err(unescape_errs) => {
392 errs.extend(unescape_errs.into_iter().map(|e| self.to_ast_err(e)));
393 None
394 }
395 };
396
397 match (maybe_key, maybe_value) {
398 (Some(k), Some(v)) => Some((
399 k,
400 ast::Annotation {
401 val: v,
402 loc: Some(self.loc.clone()), },
404 )),
405 _ => None,
406 }
407 }
408}
409
410impl Node<Option<cst::Ident>> {
411 pub fn to_valid_ident(&self, errs: &mut ParseErrors) -> Option<ast::Id> {
413 let ident = self.as_inner()?;
417
418 match ident {
419 cst::Ident::If
420 | cst::Ident::True
421 | cst::Ident::False
422 | cst::Ident::Then
423 | cst::Ident::Else
424 | cst::Ident::In
425 | cst::Ident::Is
426 | cst::Ident::Has
427 | cst::Ident::Like => {
428 errs.push(self.to_ast_err(ToASTErrorKind::ReservedIdentifier(ident.clone())));
429 None
430 }
431 cst::Ident::Invalid(i) => {
432 errs.push(self.to_ast_err(ToASTErrorKind::InvalidIdentifier(i.clone())));
433 None
434 }
435 _ => Some(ast::Id::new_unchecked(format!("{ident}"))),
436 }
437 }
438
439 pub fn to_any_ident(&self, errs: &mut ParseErrors) -> Option<ast::AnyId> {
445 let ident = self.as_inner()?;
449
450 match ident {
451 cst::Ident::Invalid(i) => {
452 errs.push(self.to_ast_err(ToASTErrorKind::InvalidIdentifier(i.clone())));
453 None
454 }
455 _ => Some(ast::AnyId::new_unchecked(format!("{ident}"))),
456 }
457 }
458
459 pub(crate) fn to_effect(&self, errs: &mut ParseErrors) -> Option<ast::Effect> {
461 let effect = self.as_inner()?;
465
466 match effect {
467 cst::Ident::Permit => Some(ast::Effect::Permit),
468 cst::Ident::Forbid => Some(ast::Effect::Forbid),
469 _ => {
470 errs.push(self.to_ast_err(ToASTErrorKind::InvalidEffect(effect.clone())));
471 None
472 }
473 }
474 }
475 pub(crate) fn to_cond_is_when(&self, errs: &mut ParseErrors) -> Option<bool> {
476 let cond = self.as_inner()?;
480
481 match cond {
482 cst::Ident::When => Some(true),
483 cst::Ident::Unless => Some(false),
484 _ => {
485 errs.push(self.to_ast_err(ToASTErrorKind::InvalidCondition(cond.clone())));
486 None
487 }
488 }
489 }
490
491 fn to_var(&self, errs: &mut ParseErrors) -> Option<ast::Var> {
492 let ident = self.as_inner()?;
496
497 match ident {
498 cst::Ident::Principal => Some(ast::Var::Principal),
499 cst::Ident::Action => Some(ast::Var::Action),
500 cst::Ident::Resource => Some(ast::Var::Resource),
501 ident => {
502 errs.push(
503 self.to_ast_err(ToASTErrorKind::InvalidScopeConstraintVariable(
504 ident.clone(),
505 )),
506 );
507 None
508 }
509 }
510 }
511}
512
513impl ast::Id {
514 fn to_meth(
515 &self,
516 e: ast::Expr,
517 mut args: Vec<ast::Expr>,
518 errs: &mut ParseErrors,
519 loc: &Loc,
520 ) -> Option<ast::Expr> {
521 match self.as_ref() {
522 "contains" => extract_single_argument(args.into_iter(), "contains", loc)
523 .map(|arg| construct_method_contains(e, arg, loc.clone()))
524 .map_err(|err| errs.push(err))
525 .ok(),
526 "containsAll" => extract_single_argument(args.into_iter(), "containsAll", loc)
527 .map(|arg| construct_method_contains_all(e, arg, loc.clone()))
528 .map_err(|err| errs.push(err))
529 .ok(),
530 "containsAny" => extract_single_argument(args.into_iter(), "containsAny", loc)
531 .map(|arg| construct_method_contains_any(e, arg, loc.clone()))
532 .map_err(|err| errs.push(err))
533 .ok(),
534 id => {
535 if EXTENSION_STYLES.methods.contains(&id) {
536 args.insert(0, e);
537 Some(construct_ext_meth(id.to_string(), args, loc.clone()))
539 } else {
540 let unqual_name = ast::Name::unqualified_name(self.clone());
541 if EXTENSION_STYLES.functions.contains(&unqual_name) {
542 errs.push(ToASTError::new(
543 ToASTErrorKind::MethodCallOnFunction(unqual_name.id),
544 loc.clone(),
545 ));
546 } else {
547 errs.push(ToASTError::new(
548 ToASTErrorKind::InvalidMethodName(id.to_string()),
549 loc.clone(),
550 ));
551 }
552 None
553 }
554 }
555 }
556 }
557}
558
559#[derive(Debug)]
560enum PrincipalOrResource {
561 Principal(PrincipalConstraint),
562 Resource(ResourceConstraint),
563}
564
565impl Node<Option<cst::VariableDef>> {
566 fn to_principal_constraint(&self, errs: &mut ParseErrors) -> Option<PrincipalConstraint> {
567 match self.to_principal_or_resource_constraint(ast::Var::Principal, errs)? {
568 PrincipalOrResource::Principal(p) => Some(p),
569 PrincipalOrResource::Resource(_) => {
570 errs.push(self.to_ast_err(ToASTErrorKind::IncorrectVariable {
571 expected: ast::Var::Principal,
572 got: ast::Var::Resource,
573 }));
574 None
575 }
576 }
577 }
578
579 fn to_resource_constraint(&self, errs: &mut ParseErrors) -> Option<ResourceConstraint> {
580 match self.to_principal_or_resource_constraint(ast::Var::Resource, errs)? {
581 PrincipalOrResource::Principal(_) => {
582 errs.push(self.to_ast_err(ToASTErrorKind::IncorrectVariable {
583 expected: ast::Var::Resource,
584 got: ast::Var::Principal,
585 }));
586 None
587 }
588 PrincipalOrResource::Resource(r) => Some(r),
589 }
590 }
591
592 fn to_principal_or_resource_constraint(
593 &self,
594 expected: ast::Var,
595 errs: &mut ParseErrors,
596 ) -> Option<PrincipalOrResource> {
597 let vardef = self.as_inner()?;
601
602 let var = vardef.variable.to_var(errs)?;
603
604 if let Some(unused_typename) = vardef.unused_type_name.as_ref() {
605 unused_typename.to_type_constraint(errs)?;
606 }
607
608 let c = if let Some((op, rel_expr)) = &vardef.ineq {
609 let eref = rel_expr.to_ref_or_slot(errs, var)?;
610 match (op, &vardef.entity_type) {
611 (cst::RelOp::Eq, None) => Some(PrincipalOrResourceConstraint::Eq(eref)),
612 (cst::RelOp::Eq, Some(_)) => {
613 errs.push(self.to_ast_err(ToASTErrorKind::InvalidIs(
614 err::InvalidIsError::WrongOp(cst::RelOp::Eq),
615 )));
616 None
617 }
618 (cst::RelOp::In, None) => Some(PrincipalOrResourceConstraint::In(eref)),
619 (cst::RelOp::In, Some(entity_type)) => Some(PrincipalOrResourceConstraint::IsIn(
620 entity_type.to_expr_or_special(errs)?.into_name(errs)?,
621 eref,
622 )),
623 (cst::RelOp::InvalidSingleEq, _) => {
624 errs.push(self.to_ast_err(ToASTErrorKind::InvalidSingleEq));
625 None
626 }
627 (op, _) => {
628 errs.push(self.to_ast_err(ToASTErrorKind::InvalidConstraintOperator(*op)));
629 None
630 }
631 }
632 } else if let Some(entity_type) = &vardef.entity_type {
633 Some(PrincipalOrResourceConstraint::Is(
634 entity_type.to_expr_or_special(errs)?.into_name(errs)?,
635 ))
636 } else {
637 Some(PrincipalOrResourceConstraint::Any)
638 }?;
639 match var {
640 ast::Var::Principal => {
641 Some(PrincipalOrResource::Principal(PrincipalConstraint::new(c)))
642 }
643 ast::Var::Resource => Some(PrincipalOrResource::Resource(ResourceConstraint::new(c))),
644 got => {
645 errs.push(self.to_ast_err(ToASTErrorKind::IncorrectVariable { expected, got }));
646 None
647 }
648 }
649 }
650
651 fn to_action_constraint(&self, errs: &mut ParseErrors) -> Option<ast::ActionConstraint> {
652 let vardef = self.as_inner()?;
656
657 match vardef.variable.to_var(errs) {
658 Some(ast::Var::Action) => Some(()),
659 Some(got) => {
660 errs.push(self.to_ast_err(ToASTErrorKind::IncorrectVariable {
661 expected: ast::Var::Action,
662 got,
663 }));
664 None
665 }
666 None => None,
667 }?;
668
669 if let Some(typename) = vardef.unused_type_name.as_ref() {
670 typename.to_type_constraint(errs)?;
671 }
672
673 if vardef.entity_type.is_some() {
674 errs.push(self.to_ast_err(ToASTErrorKind::InvalidIs(err::InvalidIsError::ActionScope)));
675 return None;
676 }
677
678 let action_constraint = if let Some((op, rel_expr)) = &vardef.ineq {
679 match op {
680 cst::RelOp::In => match rel_expr.to_refs(errs, ast::Var::Action)? {
681 OneOrMultipleRefs::Single(single_ref) => {
682 Some(ActionConstraint::is_in([single_ref]))
683 }
684 OneOrMultipleRefs::Multiple(refs) => Some(ActionConstraint::is_in(refs)),
685 },
686 cst::RelOp::Eq => {
687 let single_ref = rel_expr.to_ref(ast::Var::Action, errs)?;
688 Some(ActionConstraint::is_eq(single_ref))
689 }
690 cst::RelOp::InvalidSingleEq => {
691 errs.push(self.to_ast_err(ToASTErrorKind::InvalidSingleEq));
692 None
693 }
694 op => {
695 errs.push(self.to_ast_err(ToASTErrorKind::InvalidConstraintOperator(*op)));
696 None
697 }
698 }
699 } else {
700 Some(ActionConstraint::Any)
701 }?;
702
703 match action_constraint.contains_only_action_types() {
704 Ok(a) => Some(a),
705 Err(non_action_euids) => {
706 non_action_euids.map(|euid| {
707 let new_err =
708 self.to_ast_err(ToASTErrorKind::InvalidActionType(euid.as_ref().clone()));
709 errs.push(new_err)
710 });
711 None
712 }
713 }
714 }
715}
716
717impl Node<Option<cst::Cond>> {
718 fn to_expr(&self, errs: &mut ParseErrors) -> Option<(ast::Expr, bool)> {
723 let cond = self.as_inner()?;
727
728 let maybe_is_when = cond.cond.to_cond_is_when(errs)?;
729
730 let maybe_expr = match &cond.expr {
731 Some(expr) => expr.to_expr(errs),
732 None => {
733 let ident = match cond.cond.as_inner() {
734 Some(ident) => ident.clone(),
735 None => {
736 if maybe_is_when {
740 cst::Ident::Ident("when".into())
741 } else {
742 cst::Ident::Ident("unless".into())
743 }
744 }
745 };
746 errs.push(self.to_ast_err(ToASTErrorKind::EmptyClause(Some(ident))));
747 None
748 }
749 };
750
751 maybe_expr.map(|e| {
752 if maybe_is_when {
753 (e, true)
754 } else {
755 (construct_expr_not(e, self.loc.clone()), false)
756 }
757 })
758 }
759}
760
761impl Node<Option<cst::Str>> {
762 pub(crate) fn as_valid_string(&self, errs: &mut ParseErrors) -> Option<&SmolStr> {
763 let id = self.as_inner()?;
767
768 match id {
769 cst::Str::String(s) => Some(s),
770 cst::Str::Invalid(s) => {
772 errs.push(self.to_ast_err(ToASTErrorKind::InvalidString(s.to_string())));
773 None
774 }
775 }
776 }
777}
778
779#[derive(Debug)]
785pub(crate) enum ExprOrSpecial<'a> {
786 Expr { expr: ast::Expr, loc: Loc },
788 Var { var: ast::Var, loc: Loc },
790 Name { name: ast::Name, loc: Loc },
792 StrLit { lit: &'a SmolStr, loc: Loc },
795}
796
797impl ExprOrSpecial<'_> {
798 fn to_ast_err(&self, kind: impl Into<ToASTErrorKind>) -> ToASTError {
799 ToASTError::new(
800 kind.into(),
801 match self {
802 ExprOrSpecial::Expr { loc, .. } => loc.clone(),
803 ExprOrSpecial::Var { loc, .. } => loc.clone(),
804 ExprOrSpecial::Name { loc, .. } => loc.clone(),
805 ExprOrSpecial::StrLit { loc, .. } => loc.clone(),
806 },
807 )
808 }
809
810 fn into_expr(self, errs: &mut ParseErrors) -> Option<ast::Expr> {
811 match self {
812 Self::Expr { expr, .. } => Some(expr),
813 Self::Var { var, loc } => Some(construct_expr_var(var, loc)),
814 Self::Name { name, loc } => {
815 errs.push(ToASTError::new(
816 ToASTErrorKind::ArbitraryVariable(name.to_string().into()),
817 loc,
818 ));
819 None
820 }
821 Self::StrLit { lit, loc } => match to_unescaped_string(lit) {
822 Ok(s) => Some(construct_expr_string(s, loc)),
823 Err(escape_errs) => {
824 errs.extend(
825 escape_errs
826 .into_iter()
827 .map(|e| ToASTError::new(ToASTErrorKind::Unescape(e), loc.clone())),
828 );
829 None
830 }
831 },
832 }
833 }
834
835 pub(crate) fn into_valid_attr(self, errs: &mut ParseErrors) -> Option<SmolStr> {
837 match self {
838 Self::Var { var, .. } => Some(construct_string_from_var(var)),
839 Self::Name { name, loc } => name.into_valid_attr(errs, loc),
840 Self::StrLit { lit, loc } => match to_unescaped_string(lit) {
841 Ok(s) => Some(s),
842 Err(escape_errs) => {
843 errs.extend(
844 escape_errs
845 .into_iter()
846 .map(|e| ToASTError::new(ToASTErrorKind::Unescape(e), loc.clone())),
847 );
848 None
849 }
850 },
851 Self::Expr { expr, loc } => {
852 errs.push(ToASTError::new(
853 ToASTErrorKind::InvalidAttribute(expr.to_string().into()),
854 loc,
855 ));
856 None
857 }
858 }
859 }
860
861 fn into_pattern(self, errs: &mut ParseErrors) -> Option<Vec<PatternElem>> {
862 match &self {
863 Self::StrLit { lit, .. } => match to_pattern(lit) {
864 Ok(pat) => Some(pat),
865 Err(escape_errs) => {
866 errs.extend(
867 escape_errs
868 .into_iter()
869 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e))),
870 );
871 None
872 }
873 },
874 Self::Var { var, .. } => {
875 errs.push(self.to_ast_err(ToASTErrorKind::InvalidPattern(var.to_string())));
876 None
877 }
878 Self::Name { name, .. } => {
879 errs.push(self.to_ast_err(ToASTErrorKind::InvalidPattern(name.to_string())));
880 None
881 }
882 Self::Expr { expr, .. } => {
883 errs.push(self.to_ast_err(ToASTErrorKind::InvalidPattern(expr.to_string())));
884 None
885 }
886 }
887 }
888 fn into_string_literal(self, errs: &mut ParseErrors) -> Option<SmolStr> {
890 match &self {
891 Self::StrLit { lit, .. } => match to_unescaped_string(lit) {
892 Ok(s) => Some(s),
893 Err(escape_errs) => {
894 errs.extend(
895 escape_errs
896 .into_iter()
897 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e))),
898 );
899 None
900 }
901 },
902 Self::Var { var, .. } => {
903 errs.push(self.to_ast_err(ToASTErrorKind::InvalidString(var.to_string())));
904 None
905 }
906 Self::Name { name, .. } => {
907 errs.push(self.to_ast_err(ToASTErrorKind::InvalidString(name.to_string())));
908 None
909 }
910 Self::Expr { expr, .. } => {
911 errs.push(self.to_ast_err(ToASTErrorKind::InvalidString(expr.to_string())));
912 None
913 }
914 }
915 }
916
917 fn into_name(self, errs: &mut ParseErrors) -> Option<ast::Name> {
918 match self {
919 Self::StrLit { lit, .. } => {
920 errs.push(self.to_ast_err(ToASTErrorKind::IsInvalidName(lit.to_string())));
921 None
922 }
923 Self::Var { var, .. } => Some(ast::Name::unqualified_name(var.into())),
924 Self::Name { name, .. } => Some(name),
925 Self::Expr { ref expr, .. } => {
926 errs.push(self.to_ast_err(ToASTErrorKind::IsInvalidName(expr.to_string())));
927 None
928 }
929 }
930 }
931}
932
933impl Node<Option<cst::Expr>> {
934 fn to_ref(&self, var: ast::Var, errs: &mut ParseErrors) -> Option<EntityUID> {
936 self.to_ref_or_refs::<SingleEntity>(errs, var).map(|x| x.0)
937 }
938
939 fn to_ref_or_slot(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<EntityReference> {
940 self.to_ref_or_refs::<EntityReference>(errs, var)
941 }
942
943 fn to_refs(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<OneOrMultipleRefs> {
944 self.to_ref_or_refs::<OneOrMultipleRefs>(errs, var)
945 }
946
947 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
948 let expr = self.as_inner()?;
952
953 match &*expr.expr {
954 cst::ExprData::Or(o) => o.to_ref_or_refs::<T>(errs, var),
955 cst::ExprData::If(_, _, _) => {
956 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
957 T::err_str(),
958 "an `if` expression",
959 None::<String>,
960 )));
961 None
962 }
963 }
964 }
965
966 pub fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
968 self.to_expr_or_special(errs)?.into_expr(errs)
969 }
970 pub(crate) fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
971 let expr = self.as_inner()?;
975
976 match &*expr.expr {
977 cst::ExprData::Or(or) => or.to_expr_or_special(errs),
978 cst::ExprData::If(i, t, e) => {
979 let maybe_guard = i.to_expr(errs);
980 let maybe_then = t.to_expr(errs);
981 let maybe_else = e.to_expr(errs);
982
983 match (maybe_guard, maybe_then, maybe_else) {
984 (Some(i), Some(t), Some(e)) => Some(ExprOrSpecial::Expr {
985 expr: construct_expr_if(i, t, e, self.loc.clone()),
986 loc: self.loc.clone(),
987 }),
988 _ => None,
989 }
990 }
991 }
992 }
993}
994
995trait RefKind: Sized {
999 fn err_str() -> &'static str;
1000 fn create_single_ref(e: EntityUID, errs: &mut ParseErrors, loc: &Loc) -> Option<Self>;
1001 fn create_multiple_refs(es: Vec<EntityUID>, errs: &mut ParseErrors, loc: &Loc) -> Option<Self>;
1002 fn create_slot(errs: &mut ParseErrors, loc: &Loc) -> Option<Self>;
1003}
1004
1005struct SingleEntity(pub EntityUID);
1006
1007impl RefKind for SingleEntity {
1008 fn err_str() -> &'static str {
1009 "an entity uid"
1010 }
1011
1012 fn create_single_ref(e: EntityUID, _errs: &mut ParseErrors, _loc: &Loc) -> Option<Self> {
1013 Some(SingleEntity(e))
1014 }
1015
1016 fn create_multiple_refs(
1017 _es: Vec<EntityUID>,
1018 errs: &mut ParseErrors,
1019 loc: &Loc,
1020 ) -> Option<Self> {
1021 errs.push(ToASTError::new(
1022 RefCreationError::one_expected(Ref::Single, Ref::Set).into(),
1023 loc.clone(),
1024 ));
1025 None
1026 }
1027
1028 fn create_slot(errs: &mut ParseErrors, loc: &Loc) -> Option<Self> {
1029 errs.push(ToASTError::new(
1030 RefCreationError::one_expected(Ref::Single, Ref::Template).into(),
1031 loc.clone(),
1032 ));
1033 None
1034 }
1035}
1036
1037impl RefKind for EntityReference {
1038 fn err_str() -> &'static str {
1039 "an entity uid or matching template slot"
1040 }
1041
1042 fn create_slot(_: &mut ParseErrors, _loc: &Loc) -> Option<Self> {
1043 Some(EntityReference::Slot)
1044 }
1045
1046 fn create_single_ref(e: EntityUID, _errs: &mut ParseErrors, _loc: &Loc) -> Option<Self> {
1047 Some(EntityReference::euid(e))
1048 }
1049
1050 fn create_multiple_refs(
1051 _es: Vec<EntityUID>,
1052 errs: &mut ParseErrors,
1053 loc: &Loc,
1054 ) -> Option<Self> {
1055 errs.push(ToASTError::new(
1056 RefCreationError::two_expected(Ref::Single, Ref::Template, Ref::Set).into(),
1057 loc.clone(),
1058 ));
1059 None
1060 }
1061}
1062
1063#[derive(Debug)]
1065enum OneOrMultipleRefs {
1066 Single(EntityUID),
1067 Multiple(Vec<EntityUID>),
1068}
1069
1070impl RefKind for OneOrMultipleRefs {
1071 fn err_str() -> &'static str {
1072 "an entity uid or set of entity uids"
1073 }
1074
1075 fn create_slot(errs: &mut ParseErrors, loc: &Loc) -> Option<Self> {
1076 errs.push(ToASTError::new(
1077 RefCreationError::two_expected(Ref::Single, Ref::Set, Ref::Template).into(),
1078 loc.clone(),
1079 ));
1080 None
1081 }
1082
1083 fn create_single_ref(e: EntityUID, _errs: &mut ParseErrors, _loc: &Loc) -> Option<Self> {
1084 Some(OneOrMultipleRefs::Single(e))
1085 }
1086
1087 fn create_multiple_refs(
1088 es: Vec<EntityUID>,
1089 _errs: &mut ParseErrors,
1090 _loc: &Loc,
1091 ) -> Option<Self> {
1092 Some(OneOrMultipleRefs::Multiple(es))
1093 }
1094}
1095
1096impl Node<Option<cst::Or>> {
1097 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1098 let or = self.as_inner()?;
1102
1103 let maybe_first = or.initial.to_expr_or_special(errs);
1104 let mut more = or.extended.iter().filter_map(|i| i.to_expr(errs));
1105 let maybe_second = more.next();
1107 let rest: Vec<_> = more.collect();
1109
1110 match (maybe_first, maybe_second, rest.len(), or.extended.len()) {
1111 (f, None, _, 0) => f,
1112 (Some(f), Some(s), r, e) if 1 + r == e => {
1113 f.into_expr(errs).map(|e| ExprOrSpecial::Expr {
1114 expr: construct_expr_or(e, s, rest, &self.loc),
1115 loc: self.loc.clone(),
1116 })
1117 }
1118 _ => None,
1119 }
1120 }
1121
1122 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1123 let or = self.as_inner()?;
1127
1128 match or.extended.len() {
1129 0 => or.initial.to_ref_or_refs::<T>(errs, var),
1130 _n => {
1131 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1132 T::err_str(),
1133 "a `||` expression",
1134 None::<String>,
1135 )));
1136 None
1137 }
1138 }
1139 }
1140}
1141
1142impl Node<Option<cst::And>> {
1143 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1144 let and = self.as_inner()?;
1148
1149 match and.extended.len() {
1150 0 => and.initial.to_ref_or_refs::<T>(errs, var),
1151 _n => {
1152 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1153 T::err_str(),
1154 "a `&&` expression",
1155 None::<String>,
1156 )));
1157 None
1158 }
1159 }
1160 }
1161
1162 fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
1163 self.to_expr_or_special(errs)?.into_expr(errs)
1164 }
1165 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1166 let and = self.as_inner()?;
1170
1171 let maybe_first = and.initial.to_expr_or_special(errs);
1172 let mut more = and.extended.iter().filter_map(|i| i.to_expr(errs));
1173 let maybe_second = more.next();
1175 let rest: Vec<_> = more.collect();
1177
1178 match (maybe_first, maybe_second, rest.len(), and.extended.len()) {
1179 (f, None, _, 0) => f,
1180 (Some(f), Some(s), r, e) if 1 + r == e => {
1181 f.into_expr(errs).map(|e| ExprOrSpecial::Expr {
1182 expr: construct_expr_and(e, s, rest, &self.loc),
1183 loc: self.loc.clone(),
1184 })
1185 }
1186 _ => None,
1187 }
1188 }
1189}
1190
1191impl Node<Option<cst::Relation>> {
1192 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1193 let rel = self.as_inner()?;
1197
1198 match rel {
1199 cst::Relation::Common { initial, extended } => match extended.len() {
1200 0 => initial.to_ref_or_refs::<T>(errs, var),
1201 _n => {
1202 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1203 T::err_str(),
1204 "a binary operator",
1205 None::<String>,
1206 )));
1207 None
1208 }
1209 },
1210 cst::Relation::Has { .. } => {
1211 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1212 T::err_str(),
1213 "a `has` expression",
1214 None::<String>,
1215 )));
1216 None
1217 }
1218 cst::Relation::Like { .. } => {
1219 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1220 T::err_str(),
1221 "a `like` expression",
1222 None::<String>,
1223 )));
1224 None
1225 }
1226 cst::Relation::IsIn { .. } => {
1227 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1228 T::err_str(),
1229 "an `is` expression",
1230 None::<String>,
1231 )));
1232 None
1233 }
1234 }
1235 }
1236
1237 fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
1238 self.to_expr_or_special(errs)?.into_expr(errs)
1239 }
1240 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1241 let rel = self.as_inner()?;
1245
1246 match rel {
1247 cst::Relation::Common { initial, extended } => {
1248 let maybe_first = initial.to_expr_or_special(errs);
1249 let mut more = extended
1250 .iter()
1251 .filter_map(|(op, i)| i.to_expr(errs).map(|e| (op, e)));
1252 let maybe_second = more.next();
1254 let _rest: Vec<_> = more.collect();
1256
1257 match (maybe_first, maybe_second, extended.len()) {
1258 (_, _, len) if len > 1 => {
1259 errs.push(self.to_ast_err(ToASTErrorKind::AmbiguousOperators));
1260 None
1261 }
1262 (_, None, 1) => None,
1264 (f, None, 0) => f,
1265 (Some(f), Some((op, s)), _) => f.into_expr(errs).map(|e| {
1266 Some(ExprOrSpecial::Expr {
1267 expr: construct_expr_rel(e, *op, s, self.loc.clone(), errs)?,
1268 loc: self.loc.clone(),
1269 })
1270 })?,
1271 _ => None,
1272 }
1273 }
1274 cst::Relation::Has { target, field } => {
1275 match (
1276 target.to_expr(errs),
1277 field.to_expr_or_special(errs)?.into_valid_attr(errs),
1278 ) {
1279 (Some(t), Some(s)) => Some(ExprOrSpecial::Expr {
1280 expr: construct_expr_has(t, s, self.loc.clone()),
1281 loc: self.loc.clone(),
1282 }),
1283 _ => None,
1284 }
1285 }
1286 cst::Relation::Like { target, pattern } => {
1287 match (
1288 target.to_expr(errs),
1289 pattern.to_expr_or_special(errs)?.into_pattern(errs),
1290 ) {
1291 (Some(t), Some(s)) => Some(ExprOrSpecial::Expr {
1292 expr: construct_expr_like(t, s, self.loc.clone()),
1293 loc: self.loc.clone(),
1294 }),
1295 _ => None,
1296 }
1297 }
1298 cst::Relation::IsIn {
1299 target,
1300 entity_type,
1301 in_entity,
1302 } => match (
1303 target.to_expr(errs),
1304 entity_type.to_expr_or_special(errs)?.into_name(errs),
1305 ) {
1306 (Some(t), Some(n)) => match in_entity {
1307 Some(in_entity) => in_entity.to_expr(errs).map(|in_entity| {
1308 Some(ExprOrSpecial::Expr {
1309 expr: construct_expr_and(
1310 construct_expr_is(t.clone(), n, self.loc.clone()),
1311 construct_expr_rel(
1312 t,
1313 cst::RelOp::In,
1314 in_entity,
1315 self.loc.clone(),
1316 errs,
1317 )?,
1318 std::iter::empty(),
1319 &self.loc,
1320 ),
1321 loc: self.loc.clone(),
1322 })
1323 })?,
1324 None => Some(ExprOrSpecial::Expr {
1325 expr: construct_expr_is(t, n, self.loc.clone()),
1326 loc: self.loc.clone(),
1327 }),
1328 },
1329 _ => None,
1330 },
1331 }
1332 }
1333}
1334
1335impl Node<Option<cst::Add>> {
1336 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1337 let add = self.as_inner()?;
1341
1342 match add.extended.len() {
1343 0 => add.initial.to_ref_or_refs::<T>(errs, var),
1344 _n => {
1345 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(T::err_str(), "a `+/-` expression", Some("entity types and namespaces cannot use `+` or `-` characters -- perhaps try `_` or `::` instead?"))));
1346 None
1347 }
1348 }
1349 }
1350
1351 fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
1352 self.to_expr_or_special(errs)?.into_expr(errs)
1353 }
1354 pub(crate) fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1355 let add = self.as_inner()?;
1359
1360 let maybe_first = add.initial.to_expr_or_special(errs);
1361 let more: Vec<(cst::AddOp, _)> = add
1363 .extended
1364 .iter()
1365 .filter_map(|&(op, ref i)| i.to_expr(errs).map(|e| (op, e)))
1366 .collect();
1367 if !more.is_empty() {
1368 Some(ExprOrSpecial::Expr {
1369 expr: construct_expr_add(maybe_first?.into_expr(errs)?, more, &self.loc),
1370 loc: self.loc.clone(),
1371 })
1372 } else {
1373 maybe_first
1374 }
1375 }
1376}
1377
1378impl Node<Option<cst::Mult>> {
1379 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1380 let mult = self.as_inner()?;
1384
1385 match mult.extended.len() {
1386 0 => mult.initial.to_ref_or_refs::<T>(errs, var),
1387 _n => {
1388 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1389 T::err_str(),
1390 "a `*` expression",
1391 None::<String>,
1392 )));
1393 None
1394 }
1395 }
1396 }
1397
1398 fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
1399 self.to_expr_or_special(errs)?.into_expr(errs)
1400 }
1401 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1402 let mult = self.as_inner()?;
1406
1407 let maybe_first = mult.initial.to_expr_or_special(errs);
1408 let more = mult
1409 .extended
1410 .iter()
1411 .filter_map(|&(op, ref i)| i.to_expr(errs).map(|e| (op, e)));
1412
1413 let (more, new_errs): (Vec<_>, Vec<_>) = more
1414 .map(|(op, expr)| match op {
1415 cst::MultOp::Times => Ok(expr),
1416 cst::MultOp::Divide => Err(self.to_ast_err(ToASTErrorKind::UnsupportedDivision)),
1417 cst::MultOp::Mod => Err(self.to_ast_err(ToASTErrorKind::UnsupportedModulo)),
1418 })
1419 .partition_result();
1420 errs.extend(new_errs);
1421 if !more.is_empty() {
1422 let first = maybe_first?.into_expr(errs)?;
1424 Some(ExprOrSpecial::Expr {
1425 expr: construct_expr_mul(first, more, &self.loc),
1426 loc: self.loc.clone(),
1427 })
1428 } else {
1429 maybe_first
1430 }
1431 }
1432}
1433
1434impl Node<Option<cst::Unary>> {
1435 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1436 let unary = self.as_inner()?;
1440
1441 match &unary.op {
1442 Some(op) => {
1443 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1444 T::err_str(),
1445 format!("a `{op}` expression"),
1446 None::<String>,
1447 )));
1448 None
1449 }
1450 None => unary.item.to_ref_or_refs::<T>(errs, var),
1451 }
1452 }
1453
1454 fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
1455 self.to_expr_or_special(errs)?.into_expr(errs)
1456 }
1457 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1458 let unary = self.as_inner()?;
1462
1463 let mut maybe_item = || unary.item.to_expr_or_special(errs);
1465
1466 match unary.op {
1467 None => maybe_item(),
1468 Some(cst::NegOp::Bang(0)) => maybe_item(),
1469 Some(cst::NegOp::Dash(0)) => maybe_item(),
1470 Some(cst::NegOp::Bang(n)) => {
1471 let item = maybe_item().and_then(|i| i.into_expr(errs));
1472 if n % 2 == 0 {
1473 item.map(|i| ExprOrSpecial::Expr {
1474 expr: construct_expr_not(
1475 construct_expr_not(i, self.loc.clone()),
1476 self.loc.clone(),
1477 ),
1478 loc: self.loc.clone(),
1479 })
1480 } else {
1481 item.map(|i| ExprOrSpecial::Expr {
1483 expr: construct_expr_not(i, self.loc.clone()),
1484 loc: self.loc.clone(),
1485 })
1486 }
1487 }
1488 Some(cst::NegOp::Dash(c)) => {
1489 let (last, rc) = if let Some(cst::Literal::Num(n)) = unary.item.to_lit() {
1495 match n.cmp(&(i64::MAX as u64 + 1)) {
1496 Ordering::Equal => (
1497 Some(construct_expr_num(i64::MIN, unary.item.loc.clone())),
1498 c - 1,
1499 ),
1500 Ordering::Less => (
1501 Some(construct_expr_num(-(*n as i64), unary.item.loc.clone())),
1502 c - 1,
1503 ),
1504 Ordering::Greater => {
1505 errs.push(self.to_ast_err(ToASTErrorKind::IntegerLiteralTooLarge(*n)));
1506 (None, 0)
1507 }
1508 }
1509 } else {
1510 (maybe_item().and_then(|i| i.into_expr(errs)), c)
1513 };
1514 (0..rc)
1516 .fold(last, |r, _| {
1517 r.map(|e| construct_expr_neg(e, self.loc.clone()))
1518 })
1519 .map(|expr| ExprOrSpecial::Expr {
1520 expr,
1521 loc: self.loc.clone(),
1522 })
1523 }
1524 Some(cst::NegOp::OverBang) => {
1525 errs.push(self.to_ast_err(ToASTErrorKind::UnaryOpLimit(ast::UnaryOp::Not)));
1526 None
1527 }
1528 Some(cst::NegOp::OverDash) => {
1529 errs.push(self.to_ast_err(ToASTErrorKind::UnaryOpLimit(ast::UnaryOp::Neg)));
1530 None
1531 }
1532 }
1533 }
1534}
1535
1536enum AstAccessor {
1538 Field(ast::Id),
1539 Call(Vec<ast::Expr>),
1540 Index(SmolStr),
1541}
1542
1543impl Node<Option<cst::Member>> {
1544 pub fn to_lit(&self) -> Option<&cst::Literal> {
1549 let m = self.as_ref().node.as_ref()?;
1550 if !m.access.is_empty() {
1551 return None;
1552 }
1553 match m.item.as_ref().node.as_ref()? {
1554 cst::Primary::Literal(lit) => lit.as_ref().node.as_ref(),
1555 _ => None,
1556 }
1557 }
1558
1559 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1560 let mem = self.as_inner()?;
1564
1565 match mem.access.len() {
1566 0 => mem.item.to_ref_or_refs::<T>(errs, var),
1567 _n => {
1568 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(T::err_str(), "a `.` expression", Some("entity types and namespaces cannot use `.` characters -- perhaps try `_` or `::` instead?"))));
1569 None
1570 }
1571 }
1572 }
1573
1574 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1575 let mem = self.as_inner()?;
1579
1580 let maybe_prim = mem.item.to_expr_or_special(errs);
1581
1582 let mut accessors: Vec<_> = mem.access.iter().map(|a| a.to_access(errs)).collect();
1584
1585 let mut head = maybe_prim;
1587 let mut tail = &mut accessors[..];
1591
1592 loop {
1602 use AstAccessor::*;
1603 use ExprOrSpecial::*;
1604 match (&mut head, tail) {
1605 (_, []) => break head,
1607 (_, [None, Some(Call(_)), rest @ ..]) => {
1609 head = None;
1610 tail = rest;
1611 }
1612 (_, [None, rest @ ..]) => {
1614 head = None;
1615 tail = rest;
1616 }
1617 (Some(Name { name, .. }), [Some(Call(a)), rest @ ..]) => {
1619 let args = std::mem::take(a);
1621 let nn = mem::replace(
1623 name,
1624 ast::Name::unqualified_name(ast::Id::new_unchecked("")),
1625 );
1626 head = nn.into_func(args, errs, self.loc.clone()).map(|expr| Expr {
1627 expr,
1628 loc: self.loc.clone(),
1629 });
1630 tail = rest;
1631 }
1632 (Some(Var { var, .. }), [Some(Call(_)), rest @ ..]) => {
1634 errs.push(self.to_ast_err(ToASTErrorKind::VariableCall(*var)));
1635 head = None;
1636 tail = rest;
1637 }
1638 (_, [Some(Call(_)), rest @ ..]) => {
1640 errs.push(self.to_ast_err(ToASTErrorKind::ExpressionCall));
1641 head = None;
1642 tail = rest;
1643 }
1644 (None, [Some(Field(_)), Some(Call(_)), rest @ ..]) => {
1646 tail = rest;
1647 }
1648 (Some(Name { name, .. }), [Some(Field(f)), Some(Call(_)), rest @ ..]) => {
1650 errs.push(self.to_ast_err(ToASTErrorKind::NoMethods(name.clone(), f.clone())));
1651 head = None;
1652 tail = rest;
1653 }
1654 (Some(Var { var, loc: var_loc }), [Some(Field(i)), Some(Call(a)), rest @ ..]) => {
1656 let var = mem::replace(var, ast::Var::Principal);
1658 let args = std::mem::take(a);
1659 let id = mem::replace(i, ast::Id::new_unchecked(""));
1661 head = id
1662 .to_meth(
1663 construct_expr_var(var, var_loc.clone()),
1664 args,
1665 errs,
1666 &self.loc,
1667 )
1668 .map(|expr| Expr {
1669 expr,
1670 loc: self.loc.clone(),
1671 });
1672 tail = rest;
1673 }
1674 (Some(Expr { expr, .. }), [Some(Field(i)), Some(Call(a)), rest @ ..]) => {
1676 let args = std::mem::take(a);
1678 let expr = mem::replace(expr, ast::Expr::val(false));
1679 let id = mem::replace(i, ast::Id::new_unchecked(""));
1681 head = id.to_meth(expr, args, errs, &self.loc).map(|expr| Expr {
1682 expr,
1683 loc: self.loc.clone(),
1684 });
1685 tail = rest;
1686 }
1687 (
1689 Some(StrLit { lit, loc: lit_loc }),
1690 [Some(Field(i)), Some(Call(a)), rest @ ..],
1691 ) => {
1692 let args = std::mem::take(a);
1693 let id = mem::replace(i, ast::Id::new_unchecked(""));
1694 let maybe_expr = match to_unescaped_string(lit) {
1695 Ok(s) => Some(construct_expr_string(s, lit_loc.clone())),
1696 Err(escape_errs) => {
1697 errs.extend(
1698 escape_errs
1699 .into_iter()
1700 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e))),
1701 );
1702 None
1703 }
1704 };
1705 head = maybe_expr.and_then(|e| {
1706 id.to_meth(e, args, errs, &self.loc).map(|expr| Expr {
1707 expr,
1708 loc: self.loc.clone(),
1709 })
1710 });
1711 tail = rest;
1712 }
1713 (None, [Some(Field(_)) | Some(Index(_)), rest @ ..]) => {
1715 tail = rest;
1716 }
1717 (Some(Name { name, .. }), [Some(Field(f)), rest @ ..]) => {
1719 errs.push(self.to_ast_err(ToASTErrorKind::InvalidAccess(
1720 name.clone(),
1721 f.to_string().into(),
1722 )));
1723 head = None;
1724 tail = rest;
1725 }
1726 (Some(Name { name, .. }), [Some(Index(i)), rest @ ..]) => {
1727 errs.push(
1728 self.to_ast_err(ToASTErrorKind::InvalidIndex(name.clone(), i.clone())),
1729 );
1730 head = None;
1731 tail = rest;
1732 }
1733 (Some(Var { var, loc: var_loc }), [Some(Field(i)), rest @ ..]) => {
1735 let var = mem::replace(var, ast::Var::Principal);
1736 let id = mem::replace(i, ast::Id::new_unchecked(""));
1737 head = Some(Expr {
1738 expr: construct_expr_attr(
1739 construct_expr_var(var, var_loc.clone()),
1740 id.into_smolstr(),
1741 self.loc.clone(),
1742 ),
1743 loc: self.loc.clone(),
1744 });
1745 tail = rest;
1746 }
1747 (Some(Expr { expr, .. }), [Some(Field(i)), rest @ ..]) => {
1749 let expr = mem::replace(expr, ast::Expr::val(false));
1750 let id = mem::replace(i, ast::Id::new_unchecked(""));
1751 head = Some(Expr {
1752 expr: construct_expr_attr(expr, id.into_smolstr(), self.loc.clone()),
1753 loc: self.loc.clone(),
1754 });
1755 tail = rest;
1756 }
1757 (Some(StrLit { lit, loc: lit_loc }), [Some(Field(i)), rest @ ..]) => {
1759 let id = mem::replace(i, ast::Id::new_unchecked(""));
1760 let maybe_expr = match to_unescaped_string(lit) {
1761 Ok(s) => Some(construct_expr_string(s, lit_loc.clone())),
1762 Err(escape_errs) => {
1763 errs.extend(
1764 escape_errs
1765 .into_iter()
1766 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e))),
1767 );
1768 None
1769 }
1770 };
1771 head = maybe_expr.map(|e| Expr {
1772 expr: construct_expr_attr(e, id.into_smolstr(), self.loc.clone()),
1773 loc: self.loc.clone(),
1774 });
1775 tail = rest;
1776 }
1777 (Some(Var { var, loc: var_loc }), [Some(Index(i)), rest @ ..]) => {
1779 let var = mem::replace(var, ast::Var::Principal);
1780 let s = mem::take(i);
1781 head = Some(Expr {
1782 expr: construct_expr_attr(
1783 construct_expr_var(var, var_loc.clone()),
1784 s,
1785 self.loc.clone(),
1786 ),
1787 loc: self.loc.clone(),
1788 });
1789 tail = rest;
1790 }
1791 (Some(Expr { expr, .. }), [Some(Index(i)), rest @ ..]) => {
1793 let expr = mem::replace(expr, ast::Expr::val(false));
1794 let s = mem::take(i);
1795 head = Some(Expr {
1796 expr: construct_expr_attr(expr, s, self.loc.clone()),
1797 loc: self.loc.clone(),
1798 });
1799 tail = rest;
1800 }
1801 (Some(StrLit { lit, loc: lit_loc }), [Some(Index(i)), rest @ ..]) => {
1803 let id = mem::take(i);
1804 let maybe_expr = match to_unescaped_string(lit) {
1805 Ok(s) => Some(construct_expr_string(s, lit_loc.clone())),
1806 Err(escape_errs) => {
1807 errs.extend(
1808 escape_errs
1809 .into_iter()
1810 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e))),
1811 );
1812 None
1813 }
1814 };
1815 head = maybe_expr.map(|e| Expr {
1816 expr: construct_expr_attr(e, id, self.loc.clone()),
1817 loc: self.loc.clone(),
1818 });
1819 tail = rest;
1820 }
1821 }
1822 }
1823 }
1824}
1825
1826impl Node<Option<cst::MemAccess>> {
1827 fn to_access(&self, errs: &mut ParseErrors) -> Option<AstAccessor> {
1828 let acc = self.as_inner()?;
1832
1833 match acc {
1834 cst::MemAccess::Field(i) => {
1835 let ident = i.to_valid_ident(errs);
1836 ident.map(AstAccessor::Field)
1837 }
1838 cst::MemAccess::Call(args) => {
1839 let conv_args: Vec<_> = args.iter().filter_map(|e| e.to_expr(errs)).collect();
1840 if conv_args.len() == args.len() {
1841 Some(AstAccessor::Call(conv_args))
1842 } else {
1843 None
1844 }
1845 }
1846 cst::MemAccess::Index(index) => {
1847 let s = index.to_expr_or_special(errs)?.into_string_literal(errs);
1848 s.map(AstAccessor::Index)
1849 }
1850 }
1851 }
1852}
1853
1854impl Node<Option<cst::Primary>> {
1855 fn to_ref_or_refs<T: RefKind>(&self, errs: &mut ParseErrors, var: ast::Var) -> Option<T> {
1856 let prim = self.as_inner()?;
1860
1861 match prim {
1862 cst::Primary::Slot(s) => {
1863 let slot_ref = T::create_slot(errs, &self.loc)?;
1869 let slot = s.as_inner()?;
1870 if slot.matches(var) {
1871 Some(slot_ref)
1872 } else {
1873 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1874 T::err_str(),
1875 format!("{slot} instead of ?{var}"),
1876 None::<String>,
1877 )));
1878 None
1879 }
1880 }
1881 cst::Primary::Literal(lit) => {
1882 let found = match lit.as_inner() {
1883 Some(lit) => format!("literal `{lit}`"),
1884 None => "empty node".to_string(),
1885 };
1886 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1887 T::err_str(),
1888 found,
1889 None::<String>,
1890 )));
1891 None
1892 }
1893 cst::Primary::Ref(x) => T::create_single_ref(x.to_ref(errs)?, errs, &self.loc),
1894 cst::Primary::Name(name) => {
1895 let found = match name.as_inner() {
1896 Some(name) => format!("name `{name}`"),
1897 None => "name".to_string(),
1898 };
1899 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1900 T::err_str(),
1901 found,
1902 None::<String>,
1903 )));
1904 None
1905 }
1906 cst::Primary::Expr(x) => x.to_ref_or_refs::<T>(errs, var),
1907 cst::Primary::EList(lst) => {
1908 let v: Option<Vec<EntityUID>> =
1909 lst.iter().map(|expr| expr.to_ref(var, errs)).collect();
1910 T::create_multiple_refs(v?, errs, &self.loc)
1911 }
1912 cst::Primary::RInits(_) => {
1913 errs.push(self.to_ast_err(ToASTErrorKind::wrong_node(
1914 T::err_str(),
1915 "record initializer",
1916 None::<String>,
1917 )));
1918 None
1919 }
1920 }
1921 }
1922
1923 pub(crate) fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
1924 self.to_expr_or_special(errs)?.into_expr(errs)
1925 }
1926 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
1927 let prim = self.as_inner()?;
1931
1932 match prim {
1933 cst::Primary::Literal(lit) => lit.to_expr_or_special(errs),
1934 cst::Primary::Ref(r) => r.to_expr(errs).map(|expr| ExprOrSpecial::Expr {
1935 expr,
1936 loc: r.loc.clone(),
1937 }),
1938 cst::Primary::Slot(s) => s.clone().into_expr(errs).map(|expr| ExprOrSpecial::Expr {
1939 expr,
1940 loc: s.loc.clone(),
1941 }),
1942 #[allow(clippy::manual_map)]
1943 cst::Primary::Name(n) => {
1944 if let Some(var) = n.to_var(&mut ParseErrors::new()) {
1946 Some(ExprOrSpecial::Var {
1947 var,
1948 loc: self.loc.clone(),
1949 })
1950 } else if let Some(name) = n.to_name(errs) {
1951 Some(ExprOrSpecial::Name {
1952 name,
1953 loc: self.loc.clone(),
1954 })
1955 } else {
1956 None
1957 }
1958 }
1959 cst::Primary::Expr(e) => e.to_expr(errs).map(|expr| ExprOrSpecial::Expr {
1960 expr,
1961 loc: e.loc.clone(),
1962 }),
1963 cst::Primary::EList(es) => {
1964 let list: Vec<_> = es.iter().filter_map(|e| e.to_expr(errs)).collect();
1965 if list.len() == es.len() {
1966 Some(ExprOrSpecial::Expr {
1967 expr: construct_expr_set(list, self.loc.clone()),
1968 loc: self.loc.clone(),
1969 })
1970 } else {
1971 None
1972 }
1973 }
1974 cst::Primary::RInits(is) => {
1975 let rec: Vec<_> = is.iter().filter_map(|i| i.to_init(errs)).collect();
1976 if rec.len() == is.len() {
1977 match construct_expr_record(rec, self.loc.clone()) {
1978 Ok(expr) => Some(ExprOrSpecial::Expr {
1979 expr,
1980 loc: self.loc.clone(),
1981 }),
1982 Err(e) => {
1983 errs.push(e);
1984 None
1985 }
1986 }
1987 } else {
1988 errs.push(self.to_ast_err(ToASTErrorKind::InvalidAttributesInRecordLiteral));
1989 None
1990 }
1991 }
1992 }
1993 }
1994
1995 pub fn to_string_literal(&self, errs: &mut ParseErrors) -> Option<SmolStr> {
1997 let prim = self.as_inner()?;
2001
2002 match prim {
2003 cst::Primary::Literal(lit) => lit.to_expr_or_special(errs)?.into_string_literal(errs),
2004 _ => None,
2005 }
2006 }
2007}
2008
2009impl Node<Option<cst::Slot>> {
2010 fn into_expr(self, errs: &mut ParseErrors) -> Option<ast::Expr> {
2011 match self.as_inner()?.try_into() {
2012 Ok(slot_id) => Some(
2013 ast::ExprBuilder::new()
2014 .with_source_loc(self.loc)
2015 .slot(slot_id),
2016 ),
2017 Err(e) => {
2018 errs.push(self.to_ast_err(e));
2019 None
2020 }
2021 }
2022 }
2023}
2024
2025impl TryFrom<&cst::Slot> for ast::SlotId {
2026 type Error = ToASTErrorKind;
2027
2028 fn try_from(slot: &cst::Slot) -> Result<Self, Self::Error> {
2029 match slot {
2030 cst::Slot::Principal => Ok(ast::SlotId::principal()),
2031 cst::Slot::Resource => Ok(ast::SlotId::resource()),
2032 cst::Slot::Other(slot) => Err(ToASTErrorKind::InvalidSlot(slot.clone())),
2033 }
2034 }
2035}
2036
2037impl From<ast::SlotId> for cst::Slot {
2038 fn from(slot: ast::SlotId) -> cst::Slot {
2039 match slot {
2040 ast::SlotId(ast::ValidSlotId::Principal) => cst::Slot::Principal,
2041 ast::SlotId(ast::ValidSlotId::Resource) => cst::Slot::Resource,
2042 }
2043 }
2044}
2045
2046impl Node<Option<cst::Name>> {
2047 fn to_type_constraint(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
2049 match self.as_inner() {
2050 Some(_) => {
2051 errs.push(self.to_ast_err(ToASTErrorKind::TypeConstraints));
2052 None
2053 }
2054 None => Some(construct_expr_bool(true, self.loc.clone())),
2055 }
2056 }
2057
2058 pub(crate) fn to_name(&self, errs: &mut ParseErrors) -> Option<ast::Name> {
2059 let name = self.as_inner()?;
2063
2064 let path: Vec<_> = name
2065 .path
2066 .iter()
2067 .filter_map(|i| i.to_valid_ident(errs))
2068 .collect();
2069 let maybe_name = name.name.to_valid_ident(errs);
2070
2071 match (maybe_name, path.len()) {
2073 (Some(r), len) if len == name.path.len() => {
2074 Some(construct_name(path, r, self.loc.clone()))
2075 }
2076 _ => None,
2077 }
2078 }
2079 fn to_ident(&self, errs: &mut ParseErrors) -> Option<&cst::Ident> {
2080 let name = self.as_inner()?;
2084
2085 for id in &name.path {
2086 id.to_valid_ident(errs);
2089 }
2090
2091 if !name.path.is_empty() {
2092 errs.push(self.to_ast_err(ToASTErrorKind::InvalidPath));
2093 return None;
2094 }
2095
2096 name.name.as_inner()
2097 }
2098 fn to_var(&self, errs: &mut ParseErrors) -> Option<ast::Var> {
2099 let name = self.to_ident(errs)?;
2100
2101 match name {
2102 cst::Ident::Principal => Some(ast::Var::Principal),
2103 cst::Ident::Action => Some(ast::Var::Action),
2104 cst::Ident::Resource => Some(ast::Var::Resource),
2105 cst::Ident::Context => Some(ast::Var::Context),
2106 n => {
2107 errs.push(self.to_ast_err(ToASTErrorKind::ArbitraryVariable(n.to_string().into())));
2108 None
2109 }
2110 }
2111 }
2112}
2113
2114impl ast::Name {
2115 fn into_valid_attr(self, errs: &mut ParseErrors, loc: Loc) -> Option<SmolStr> {
2117 if !self.path.is_empty() {
2118 errs.push(ToASTError::new(
2119 ToASTErrorKind::PathAsAttribute(self.to_string()),
2120 loc,
2121 ));
2122 None
2123 } else {
2124 Some(self.id.into_smolstr())
2125 }
2126 }
2127
2128 fn into_func(
2129 self,
2130 args: Vec<ast::Expr>,
2131 errs: &mut ParseErrors,
2132 loc: Loc,
2133 ) -> Option<ast::Expr> {
2134 if self.path.is_empty() {
2136 let id = self.id.as_ref();
2137 if EXTENSION_STYLES.methods.contains(id)
2138 || matches!(id, "contains" | "containsAll" | "containsAny")
2139 {
2140 errs.push(ToASTError::new(
2141 ToASTErrorKind::FunctionCallOnMethod(self.id),
2142 loc,
2143 ));
2144 return None;
2145 }
2146 }
2147 if EXTENSION_STYLES.functions.contains(&self) {
2148 Some(construct_ext_func(self, args, loc))
2149 } else {
2150 errs.push(ToASTError::new(ToASTErrorKind::NotAFunction(self), loc));
2151 None
2152 }
2153 }
2154}
2155
2156impl Node<Option<cst::Ref>> {
2157 pub fn to_ref(&self, errs: &mut ParseErrors) -> Option<ast::EntityUID> {
2159 let refr = self.as_inner()?;
2163
2164 match refr {
2165 cst::Ref::Uid { path, eid } => {
2166 let maybe_path = path.to_name(errs);
2167 let maybe_eid = match eid
2168 .as_valid_string(errs)
2169 .map(|s| to_unescaped_string(s))
2170 .transpose()
2171 {
2172 Ok(opt) => opt,
2173 Err(escape_errs) => {
2174 errs.extend(
2175 escape_errs
2176 .into_iter()
2177 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e))),
2178 );
2179 None
2180 }
2181 };
2182
2183 match (maybe_path, maybe_eid) {
2184 (Some(p), Some(e)) => Some(construct_refr(p, e, self.loc.clone())),
2185 _ => None,
2186 }
2187 }
2188 cst::Ref::Ref { .. } => {
2189 errs.push(self.to_ast_err(ToASTErrorKind::UnsupportedEntityLiterals));
2190 None
2191 }
2192 }
2193 }
2194 fn to_expr(&self, errs: &mut ParseErrors) -> Option<ast::Expr> {
2195 self.to_ref(errs)
2196 .map(|euid| construct_expr_ref(euid, self.loc.clone()))
2197 }
2198}
2199
2200impl Node<Option<cst::Literal>> {
2201 fn to_expr_or_special(&self, errs: &mut ParseErrors) -> Option<ExprOrSpecial<'_>> {
2202 let lit = self.as_inner()?;
2206
2207 match lit {
2208 cst::Literal::True => Some(ExprOrSpecial::Expr {
2209 expr: construct_expr_bool(true, self.loc.clone()),
2210 loc: self.loc.clone(),
2211 }),
2212 cst::Literal::False => Some(ExprOrSpecial::Expr {
2213 expr: construct_expr_bool(false, self.loc.clone()),
2214 loc: self.loc.clone(),
2215 }),
2216 cst::Literal::Num(n) => match Integer::try_from(*n) {
2217 Ok(i) => Some(ExprOrSpecial::Expr {
2218 expr: construct_expr_num(i, self.loc.clone()),
2219 loc: self.loc.clone(),
2220 }),
2221 Err(_) => {
2222 errs.push(self.to_ast_err(ToASTErrorKind::IntegerLiteralTooLarge(*n)));
2223 None
2224 }
2225 },
2226 cst::Literal::Str(s) => {
2227 let maybe_str = s.as_valid_string(errs);
2228 maybe_str.map(|lit| ExprOrSpecial::StrLit {
2229 lit,
2230 loc: self.loc.clone(),
2231 })
2232 }
2233 }
2234 }
2235}
2236
2237impl Node<Option<cst::RecInit>> {
2238 fn to_init(&self, errs: &mut ParseErrors) -> Option<(SmolStr, ast::Expr)> {
2239 let lit = self.as_inner()?;
2243
2244 let maybe_attr = lit.0.to_expr_or_special(errs)?.into_valid_attr(errs);
2245 let maybe_value = lit.1.to_expr(errs);
2246
2247 match (maybe_attr, maybe_value) {
2248 (Some(s), Some(v)) => Some((s, v)),
2249 _ => None,
2250 }
2251 }
2252}
2253
2254#[allow(clippy::too_many_arguments)]
2257fn construct_template_policy(
2258 id: ast::PolicyID,
2259 annotations: ast::Annotations,
2260 effect: ast::Effect,
2261 principal: ast::PrincipalConstraint,
2262 action: ast::ActionConstraint,
2263 resource: ast::ResourceConstraint,
2264 conds: Vec<ast::Expr>,
2265 loc: &Loc,
2266) -> ast::Template {
2267 let construct_template = |non_scope_constraint| {
2268 ast::Template::new(
2269 id,
2270 Some(loc.clone()),
2271 annotations,
2272 effect,
2273 principal,
2274 action,
2275 resource,
2276 non_scope_constraint,
2277 )
2278 };
2279 let mut conds_iter = conds.into_iter();
2280 if let Some(first_expr) = conds_iter.next() {
2281 construct_template(match conds_iter.next() {
2284 Some(e) => construct_expr_and(first_expr, e, conds_iter, loc),
2285 None => first_expr,
2286 })
2287 } else {
2288 construct_template(construct_expr_bool(true, loc.clone()))
2290 }
2291}
2292fn construct_string_from_var(v: ast::Var) -> SmolStr {
2293 match v {
2294 ast::Var::Principal => "principal".into(),
2295 ast::Var::Action => "action".into(),
2296 ast::Var::Resource => "resource".into(),
2297 ast::Var::Context => "context".into(),
2298 }
2299}
2300fn construct_name(path: Vec<ast::Id>, id: ast::Id, loc: Loc) -> ast::Name {
2301 ast::Name {
2302 id,
2303 path: Arc::new(path),
2304 loc: Some(loc),
2305 }
2306}
2307fn construct_refr(p: ast::Name, n: SmolStr, loc: Loc) -> ast::EntityUID {
2308 let eid = ast::Eid::new(n);
2309 ast::EntityUID::from_components(p, eid, Some(loc))
2310}
2311fn construct_expr_ref(r: ast::EntityUID, loc: Loc) -> ast::Expr {
2312 ast::ExprBuilder::new().with_source_loc(loc).val(r)
2313}
2314fn construct_expr_num(n: Integer, loc: Loc) -> ast::Expr {
2315 ast::ExprBuilder::new().with_source_loc(loc).val(n)
2316}
2317fn construct_expr_string(s: SmolStr, loc: Loc) -> ast::Expr {
2318 ast::ExprBuilder::new().with_source_loc(loc).val(s)
2319}
2320fn construct_expr_bool(b: bool, loc: Loc) -> ast::Expr {
2321 ast::ExprBuilder::new().with_source_loc(loc).val(b)
2322}
2323fn construct_expr_neg(e: ast::Expr, loc: Loc) -> ast::Expr {
2324 ast::ExprBuilder::new().with_source_loc(loc).neg(e)
2325}
2326fn construct_expr_not(e: ast::Expr, loc: Loc) -> ast::Expr {
2327 ast::ExprBuilder::new().with_source_loc(loc).not(e)
2328}
2329fn construct_expr_var(v: ast::Var, loc: Loc) -> ast::Expr {
2330 ast::ExprBuilder::new().with_source_loc(loc).var(v)
2331}
2332fn construct_expr_if(i: ast::Expr, t: ast::Expr, e: ast::Expr, loc: Loc) -> ast::Expr {
2333 ast::ExprBuilder::new().with_source_loc(loc).ite(i, t, e)
2334}
2335fn construct_expr_or(
2336 f: ast::Expr,
2337 s: ast::Expr,
2338 chained: impl IntoIterator<Item = ast::Expr>,
2339 loc: &Loc,
2340) -> ast::Expr {
2341 let first = ast::ExprBuilder::new()
2342 .with_source_loc(loc.clone())
2343 .or(f, s);
2344 chained.into_iter().fold(first, |a, n| {
2345 ast::ExprBuilder::new()
2346 .with_source_loc(loc.clone())
2347 .or(a, n)
2348 })
2349}
2350fn construct_expr_and(
2351 f: ast::Expr,
2352 s: ast::Expr,
2353 chained: impl IntoIterator<Item = ast::Expr>,
2354 loc: &Loc,
2355) -> ast::Expr {
2356 let first = ast::ExprBuilder::new()
2357 .with_source_loc(loc.clone())
2358 .and(f, s);
2359 chained.into_iter().fold(first, |a, n| {
2360 ast::ExprBuilder::new()
2361 .with_source_loc(loc.clone())
2362 .and(a, n)
2363 })
2364}
2365fn construct_expr_rel(
2366 f: ast::Expr,
2367 rel: cst::RelOp,
2368 s: ast::Expr,
2369 loc: Loc,
2370 errs: &mut ParseErrors,
2371) -> Option<ast::Expr> {
2372 let builder = ast::ExprBuilder::new().with_source_loc(loc.clone());
2373 match rel {
2374 cst::RelOp::Less => Some(builder.less(f, s)),
2375 cst::RelOp::LessEq => Some(builder.lesseq(f, s)),
2376 cst::RelOp::GreaterEq => Some(builder.greatereq(f, s)),
2377 cst::RelOp::Greater => Some(builder.greater(f, s)),
2378 cst::RelOp::NotEq => Some(builder.noteq(f, s)),
2379 cst::RelOp::Eq => Some(builder.is_eq(f, s)),
2380 cst::RelOp::In => Some(builder.is_in(f, s)),
2381 cst::RelOp::InvalidSingleEq => {
2382 errs.push(ToASTError::new(ToASTErrorKind::InvalidSingleEq, loc));
2383 None
2384 }
2385 }
2386}
2387fn construct_expr_add(
2389 f: ast::Expr,
2390 chained: impl IntoIterator<Item = (cst::AddOp, ast::Expr)>,
2391 loc: &Loc,
2392) -> ast::Expr {
2393 let mut expr = f;
2394 for (op, next_expr) in chained {
2395 let builder = ast::ExprBuilder::new().with_source_loc(loc.clone());
2396 expr = match op {
2397 cst::AddOp::Plus => builder.add(expr, next_expr),
2398 cst::AddOp::Minus => builder.sub(expr, next_expr),
2399 };
2400 }
2401 expr
2402}
2403fn construct_expr_mul(
2405 f: ast::Expr,
2406 chained: impl IntoIterator<Item = ast::Expr>,
2407 loc: &Loc,
2408) -> ast::Expr {
2409 let mut expr = f;
2410 for next_expr in chained {
2411 expr = ast::ExprBuilder::new()
2412 .with_source_loc(loc.clone())
2413 .mul(expr, next_expr);
2414 }
2415 expr
2416}
2417fn construct_expr_has(t: ast::Expr, s: SmolStr, loc: Loc) -> ast::Expr {
2418 ast::ExprBuilder::new().with_source_loc(loc).has_attr(t, s)
2419}
2420fn construct_expr_attr(e: ast::Expr, s: SmolStr, loc: Loc) -> ast::Expr {
2421 ast::ExprBuilder::new().with_source_loc(loc).get_attr(e, s)
2422}
2423fn construct_expr_like(e: ast::Expr, s: Vec<PatternElem>, loc: Loc) -> ast::Expr {
2424 ast::ExprBuilder::new().with_source_loc(loc).like(e, s)
2425}
2426fn construct_expr_is(e: ast::Expr, n: ast::Name, loc: Loc) -> ast::Expr {
2427 ast::ExprBuilder::new()
2428 .with_source_loc(loc)
2429 .is_entity_type(e, n)
2430}
2431fn construct_ext_func(name: ast::Name, args: Vec<ast::Expr>, loc: Loc) -> ast::Expr {
2432 ast::ExprBuilder::new()
2434 .with_source_loc(loc)
2435 .call_extension_fn(name, args)
2436}
2437
2438fn construct_method_contains(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
2439 ast::ExprBuilder::new()
2440 .with_source_loc(loc)
2441 .contains(e0, e1)
2442}
2443fn construct_method_contains_all(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
2444 ast::ExprBuilder::new()
2445 .with_source_loc(loc)
2446 .contains_all(e0, e1)
2447}
2448fn construct_method_contains_any(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
2449 ast::ExprBuilder::new()
2450 .with_source_loc(loc)
2451 .contains_any(e0, e1)
2452}
2453
2454fn construct_ext_meth(n: String, args: Vec<ast::Expr>, loc: Loc) -> ast::Expr {
2456 let id = ast::Id::new_unchecked(n);
2457 let name = ast::Name::unqualified_name(id);
2458 ast::ExprBuilder::new()
2460 .with_source_loc(loc)
2461 .call_extension_fn(name, args)
2462}
2463fn construct_expr_set(s: Vec<ast::Expr>, loc: Loc) -> ast::Expr {
2464 ast::ExprBuilder::new().with_source_loc(loc).set(s)
2465}
2466fn construct_expr_record(
2467 kvs: Vec<(SmolStr, ast::Expr)>,
2468 loc: Loc,
2469) -> Result<ast::Expr, ToASTError> {
2470 ast::ExprBuilder::new()
2471 .with_source_loc(loc.clone())
2472 .record(kvs)
2473 .map_err(|e| ToASTError::new(e.into(), loc))
2474}
2475
2476#[allow(clippy::panic)]
2478#[allow(clippy::indexing_slicing)]
2480#[cfg(test)]
2481mod tests {
2482 use super::*;
2483 use crate::{
2484 ast::Expr,
2485 parser::{err::ParseErrors, test_utils::*, *},
2486 test_utils::*,
2487 };
2488 use cool_asserts::assert_matches;
2489 use std::str::FromStr;
2490
2491 #[test]
2492 fn show_expr1() {
2493 let mut errs = ParseErrors::new();
2494 let expr: ast::Expr = text_to_cst::parse_expr(
2495 r#"
2496 if 7 then 6 > 5 else !5 || "thursday" && ((8) >= "fish")
2497 "#,
2498 )
2499 .expect("failed parser")
2500 .to_expr(&mut errs)
2501 .unwrap_or_else(|| {
2502 panic!(
2503 "failed convert to AST:\n{:?}",
2504 miette::Report::new(errs.clone())
2505 )
2506 });
2507 assert!(errs.is_empty());
2508 println!("{:?}", expr);
2510 }
2511
2512 #[test]
2513 fn show_expr2() {
2514 let mut errs = ParseErrors::new();
2515 let expr: ast::Expr = text_to_cst::parse_expr(
2516 r#"
2517 [2,3,4].foo["hello"]
2518 "#,
2519 )
2520 .expect("failed parser")
2521 .to_expr(&mut errs)
2522 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2523 println!("{:?}", expr);
2525 }
2526
2527 #[test]
2528 fn show_expr3() {
2529 let mut errs = ParseErrors::new();
2531 let expr = text_to_cst::parse_expr(
2532 r#"
2533 "first".some_ident
2534 "#,
2535 )
2536 .expect("failed parser")
2537 .to_expr(&mut errs)
2538 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2539 match expr.expr_kind() {
2540 ast::ExprKind::GetAttr { attr, .. } => {
2541 assert_eq!(attr, "some_ident");
2542 }
2543 _ => panic!("should be a get expr"),
2544 }
2545 }
2546
2547 #[test]
2548 fn show_expr4() {
2549 let mut errs = ParseErrors::new();
2550 let expr = text_to_cst::parse_expr(
2551 r#"
2552 1.some_ident
2553 "#,
2554 )
2555 .expect("failed parser")
2556 .to_expr(&mut errs)
2557 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2558 match expr.expr_kind() {
2559 ast::ExprKind::GetAttr { attr, .. } => {
2560 assert_eq!(attr, "some_ident");
2561 }
2562 _ => panic!("should be a get expr"),
2563 }
2564 }
2565
2566 #[test]
2567 fn show_expr5() {
2568 let mut errs = ParseErrors::new();
2569 let expr = text_to_cst::parse_expr(
2570 r#"
2571 "first"["some string"]
2572 "#,
2573 )
2574 .expect("failed parser")
2575 .to_expr(&mut errs)
2576 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2577 match expr.expr_kind() {
2578 ast::ExprKind::GetAttr { attr, .. } => {
2579 assert_eq!(attr, "some string");
2580 }
2581 _ => panic!("should be a get expr"),
2582 }
2583 }
2584
2585 #[test]
2586 fn show_expr6() {
2587 let mut errs = ParseErrors::new();
2588 let expr: ast::Expr = text_to_cst::parse_expr(
2589 r#"
2590 {"one":1,"two":2} has one
2591 "#,
2592 )
2593 .expect("failed parser")
2594 .to_expr(&mut errs)
2595 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2596
2597 match expr.expr_kind() {
2598 ast::ExprKind::HasAttr { attr, .. } => {
2599 assert_eq!(attr, "one");
2600 }
2601 _ => panic!("should be a has expr"),
2602 }
2603 }
2604
2605 #[test]
2606 fn show_expr7() {
2607 let mut errs = ParseErrors::new();
2608 let expr = text_to_cst::parse_expr(
2609 r#"
2610 {"one":1,"two":2}.one
2611 "#,
2612 )
2613 .expect("failed parser")
2614 .to_expr(&mut errs)
2615 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2616
2617 match expr.expr_kind() {
2618 ast::ExprKind::GetAttr { attr, .. } => {
2619 assert_eq!(attr, "one");
2620 }
2621 _ => panic!("should be a get expr"),
2622 }
2623 }
2624
2625 #[test]
2626 fn show_expr8() {
2627 let mut errs = ParseErrors::new();
2628 let expr: ast::Expr = text_to_cst::parse_expr(
2630 r#"
2631 {"one":1,"two":2}["one"]
2632 "#,
2633 )
2634 .expect("failed parser")
2635 .to_expr(&mut errs)
2636 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2637
2638 match expr.expr_kind() {
2639 ast::ExprKind::GetAttr { attr, .. } => {
2640 assert_eq!(attr, "one");
2641 }
2642 _ => panic!("should be a get expr"),
2643 }
2644 }
2645
2646 #[test]
2647 fn show_expr9() {
2648 let mut errs = ParseErrors::new();
2650 let expr: ast::Expr = text_to_cst::parse_expr(
2651 r#"
2652 {"this is a valid map key+.-_%()":1,"two":2}["this is a valid map key+.-_%()"]
2653 "#,
2654 )
2655 .expect("failed parser")
2656 .to_expr(&mut errs)
2657 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2658
2659 match expr.expr_kind() {
2660 ast::ExprKind::GetAttr { attr, .. } => {
2661 assert_eq!(attr, "this is a valid map key+.-_%()");
2662 }
2663 _ => panic!("should be a get expr"),
2664 }
2665 }
2666
2667 #[test]
2668 fn show_expr10() {
2669 let mut errs = ParseErrors::new();
2670 let expr = text_to_cst::parse_expr(
2671 r#"
2672 {if true then a else b:"b"} ||
2673 {if false then a else b:"b"}
2674 "#,
2675 )
2676 .expect("failed parser")
2677 .to_expr(&mut errs);
2678
2679 assert_matches!(expr, None => {
2680 assert_eq!(errs.len(), 6, "{:?}", miette::Report::new(errs));
2683 });
2684 }
2685
2686 #[test]
2687 fn show_expr11() {
2688 let mut errs = ParseErrors::new();
2689 let expr = text_to_cst::parse_expr(
2690 r#"
2691 {principal:"principal"}
2692 "#,
2693 )
2694 .expect("failed parser")
2695 .to_expr(&mut errs)
2696 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2697
2698 match expr.expr_kind() {
2699 ast::ExprKind::Record { .. } => {}
2700 _ => panic!("should be record, got: {expr:?}"),
2701 }
2702 }
2703
2704 #[test]
2705 fn show_expr12() {
2706 let mut errs = ParseErrors::new();
2707 let expr = text_to_cst::parse_expr(
2708 r#"
2709 {"principal":"principal"}
2710 "#,
2711 )
2712 .expect("failed parser")
2713 .to_expr(&mut errs)
2714 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2715
2716 match expr.expr_kind() {
2717 ast::ExprKind::Record { .. } => {}
2718 _ => panic!("should be record, got: {expr:?}"),
2719 }
2720 }
2721
2722 #[test]
2723 fn reserved_idents1() {
2724 let mut errs = ParseErrors::new();
2725 let parse = text_to_cst::parse_expr(
2726 r#"
2727 The::true::path::to::"enlightenment".false
2728 "#,
2729 )
2730 .expect("failed parse");
2731
2732 let convert = parse.to_expr(&mut errs);
2733 assert_matches!(convert, None => {
2735 assert_eq!(errs.len(), 2, "{:?}", miette::Report::new(errs));
2736 });
2737 }
2738
2739 #[test]
2740 fn reserved_idents2() {
2741 let mut errs = ParseErrors::new();
2742 let src = r#"
2743 if {if: true}.if then {"if":false}["if"] else {when:true}.permit
2744 "#;
2745 let parse = text_to_cst::parse_expr(src).expect("failed parse");
2746
2747 assert_matches!(parse.to_expr(&mut errs), None => {
2748 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: `if`").exactly_one_underline("if: true").build());
2749 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: `if`").exactly_one_underline("if").build());
2750 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("record literal has invalid attributes").exactly_one_underline("{if: true}").build());
2751 });
2752 }
2753
2754 #[test]
2755 fn reserved_idents3() {
2756 let mut errs = ParseErrors::new();
2757 let src = r#"
2758 if {where: true}.like || {has:false}.in then {"like":false}["in"] else {then:true}.else
2759 "#;
2760 let parse = text_to_cst::parse_expr(src).expect("failed parse");
2761
2762 assert_matches!(parse.to_expr(&mut errs), None => {
2763 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: `has`").exactly_one_underline("has").build());
2764 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: `like`").exactly_one_underline("like").build());
2765 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: `in`").exactly_one_underline("in").build());
2766 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: `then`").exactly_one_underline("then").build());
2767 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("this identifier is reserved and cannot be used: `else`").exactly_one_underline("else").build());
2768 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("record literal has invalid attributes").exactly_one_underline("{has:false}").build());
2769 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("record literal has invalid attributes").exactly_one_underline("{then:true}").build());
2770 });
2771 }
2772
2773 #[test]
2774 fn show_policy1() {
2775 let mut errs = ParseErrors::new();
2776 let src = r#"
2777 permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
2778 "#;
2779 let parse = text_to_cst::parse_policy(src).expect("failed parse");
2780 println!("{:#}", parse.as_inner().expect("internal parse error"));
2781 assert_matches!(parse.to_policy(ast::PolicyID::from_string("id"), &mut errs), None => {
2782 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("type constraints using `:` are not supported").help("try using `is` instead").exactly_one_underline("p").build());
2783 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("type constraints using `:` are not supported").help("try using `is` instead").exactly_one_underline("a").build());
2784 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("type constraints using `:` are not supported").help("try using `is` instead").exactly_one_underline("r").build());
2785 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("arbitrary variables are not supported; the valid Cedar variables are `principal`, `action`, `resource`, and `context`").help("did you mean to enclose `w` in quotes to make a string?").exactly_one_underline("w").build());
2786 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("arbitrary variables are not supported; the valid Cedar variables are `principal`, `action`, `resource`, and `context`").help("did you mean to enclose `u` in quotes to make a string?").exactly_one_underline("u").build());
2787 expect_some_error_matches(src, &errs, &ExpectedErrorMessageBuilder::error("not a valid policy condition: `advice`").help("condition must be either `when` or `unless`").exactly_one_underline("advice").build());
2788 });
2789 }
2790
2791 #[test]
2792 fn show_policy2() {
2793 let mut errs = ParseErrors::new();
2794 let src = r#"
2795 permit(principal,action,resource)when{true};
2796 "#;
2797 let parse = text_to_cst::parse_policy(src).expect("failed parse");
2798 println!("{:#}", parse.as_inner().expect("internal parse error"));
2799 assert_matches!(parse.to_policy(ast::PolicyID::from_string("id"), &mut errs), Some(_) => {
2800 assert!(errs.is_empty(), "{:?}", miette::Report::new(errs));
2801 });
2802 }
2803
2804 #[test]
2805 fn show_policy3() {
2806 let mut errs = ParseErrors::new();
2807 let src = r#"
2808 permit(principal in User::"jane",action,resource);
2809 "#;
2810 let parse = text_to_cst::parse_policy(src).expect("failed parse");
2811 println!("{:#}", parse.as_inner().expect("internal parse error"));
2812 assert_matches!(parse.to_policy(ast::PolicyID::from_string("id"), &mut errs), Some(_) => {
2813 assert!(errs.is_empty(), "{:?}", miette::Report::new(errs));
2814 });
2815 }
2816
2817 #[test]
2818 fn show_policy4() {
2819 let mut errs = ParseErrors::new();
2820 let src = r#"
2821 forbid(principal in User::"jane",action,resource)unless{
2822 context.group != "friends"
2823 };
2824 "#;
2825 let parse = text_to_cst::parse_policy(src).expect("failed parse");
2826 println!("{:#}", parse.as_inner().expect("internal parse error"));
2827 assert_matches!(parse.to_policy(ast::PolicyID::from_string("id"), &mut errs), Some(_) => {
2828 assert!(errs.is_empty(), "{:?}", miette::Report::new(errs));
2829 });
2830 }
2831
2832 #[test]
2833 fn policy_annotations() {
2834 let mut errs = ParseErrors::new();
2836 let policy = text_to_cst::parse_policy(
2837 r#"
2838 @anno("good annotation")permit(principal,action,resource);
2839 "#,
2840 )
2841 .expect("should parse")
2842 .to_policy(ast::PolicyID::from_string("id"), &mut errs)
2843 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2844 assert_matches!(
2845 policy.annotation(&ast::AnyId::new_unchecked("anno")),
2846 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "good annotation")
2847 );
2848
2849 let mut errs = ParseErrors::new();
2851 let policy = text_to_cst::parse_policy(
2852 r#"
2853 @anno("good annotation")
2854 @anno2("good annotation")
2855 @anno("oops, duplicate")
2856 permit(principal,action,resource);
2857 "#,
2858 )
2859 .expect("should parse")
2860 .to_policy(ast::PolicyID::from_string("id"), &mut errs);
2861 assert_matches!(policy, None => {
2862 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
2864 });
2865
2866 let mut errs = ParseErrors::new();
2868 let policyset = text_to_cst::parse_policies(
2869 r#"
2870 @anno1("first")
2871 permit(principal,action,resource);
2872
2873 @anno2("second")
2874 permit(principal,action,resource);
2875
2876 @anno3a("third-a")
2877 @anno3b("third-b")
2878 permit(principal,action,resource);
2879 "#,
2880 )
2881 .expect("should parse")
2882 .to_policyset(&mut errs)
2883 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2884 assert_matches!(
2885 policyset
2886 .get(&ast::PolicyID::from_string("policy0"))
2887 .expect("should be a policy")
2888 .annotation(&ast::AnyId::new_unchecked("anno0")),
2889 None
2890 );
2891 assert_matches!(
2892 policyset
2893 .get(&ast::PolicyID::from_string("policy0"))
2894 .expect("should be a policy")
2895 .annotation(&ast::AnyId::new_unchecked("anno1")),
2896 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "first")
2897 );
2898 assert_matches!(
2899 policyset
2900 .get(&ast::PolicyID::from_string("policy1"))
2901 .expect("should be a policy")
2902 .annotation(&ast::AnyId::new_unchecked("anno2")),
2903 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "second")
2904 );
2905 assert_matches!(
2906 policyset
2907 .get(&ast::PolicyID::from_string("policy2"))
2908 .expect("should be a policy")
2909 .annotation(&ast::AnyId::new_unchecked("anno3a")),
2910 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "third-a")
2911 );
2912 assert_matches!(
2913 policyset
2914 .get(&ast::PolicyID::from_string("policy2"))
2915 .expect("should be a policy")
2916 .annotation(&ast::AnyId::new_unchecked("anno3b")),
2917 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "third-b")
2918 );
2919 assert_matches!(
2920 policyset
2921 .get(&ast::PolicyID::from_string("policy2"))
2922 .expect("should be a policy")
2923 .annotation(&ast::AnyId::new_unchecked("anno3c")),
2924 None
2925 );
2926 assert_eq!(
2927 policyset
2928 .get(&ast::PolicyID::from_string("policy2"))
2929 .expect("should be a policy")
2930 .annotations()
2931 .count(),
2932 2
2933 );
2934
2935 assert_matches!(
2937 text_to_cst::parse_policy(
2938 r#"
2939 @hi mom("this should be invalid")
2940 permit(principal, action, resource);
2941 "#,
2942 ),
2943 Err(_)
2944 );
2945 assert_matches!(
2946 text_to_cst::parse_policy(
2947 r#"
2948 @hi+mom("this should be invalid")
2949 permit(principal, action, resource);
2950 "#,
2951 ),
2952 Err(_)
2953 );
2954
2955 let mut errs = ParseErrors::new();
2957 let policyset = text_to_cst::parse_policies(
2958 r#"
2959 @if("this is the annotation for `if`")
2960 @then("this is the annotation for `then`")
2961 @else("this is the annotation for `else`")
2962 @true("this is the annotation for `true`")
2963 @false("this is the annotation for `false`")
2964 @in("this is the annotation for `in`")
2965 @is("this is the annotation for `is`")
2966 @like("this is the annotation for `like`")
2967 @has("this is the annotation for `has`")
2968 @principal("this is the annotation for `principal`") // not reserved at time of this writing, but we test it anyway
2969 permit(principal, action, resource);
2970 "#,
2971 ).expect("should parse")
2972 .to_policyset(&mut errs)
2973 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2974 let policy0 = policyset
2975 .get(&ast::PolicyID::from_string("policy0"))
2976 .expect("should be the right policy ID");
2977 assert_matches!(
2978 policy0.annotation(&ast::AnyId::new_unchecked("if")),
2979 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `if`")
2980 );
2981 assert_matches!(
2982 policy0.annotation(&ast::AnyId::new_unchecked("then")),
2983 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `then`")
2984 );
2985 assert_matches!(
2986 policy0.annotation(&ast::AnyId::new_unchecked("else")),
2987 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `else`")
2988 );
2989 assert_matches!(
2990 policy0.annotation(&ast::AnyId::new_unchecked("true")),
2991 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `true`")
2992 );
2993 assert_matches!(
2994 policy0.annotation(&ast::AnyId::new_unchecked("false")),
2995 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `false`")
2996 );
2997 assert_matches!(
2998 policy0.annotation(&ast::AnyId::new_unchecked("in")),
2999 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `in`")
3000 );
3001 assert_matches!(
3002 policy0.annotation(&ast::AnyId::new_unchecked("is")),
3003 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `is`")
3004 );
3005 assert_matches!(
3006 policy0.annotation(&ast::AnyId::new_unchecked("like")),
3007 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `like`")
3008 );
3009 assert_matches!(
3010 policy0.annotation(&ast::AnyId::new_unchecked("has")),
3011 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `has`")
3012 );
3013 assert_matches!(
3014 policy0.annotation(&ast::AnyId::new_unchecked("principal")),
3015 Some(ast::Annotation { val, .. }) => assert_eq!(val.as_ref(), "this is the annotation for `principal`")
3016 );
3017 }
3018
3019 #[test]
3020 fn fail_scope1() {
3021 let mut errs = ParseErrors::new();
3022 let parse = text_to_cst::parse_policy(
3023 r#"
3024 permit(
3025 principal in [User::"jane",Group::"friends"],
3026 action,
3027 resource
3028 );
3029 "#,
3030 )
3031 .expect("failed parse");
3032 println!("\n{:#}", parse.as_inner().expect("internal parse error"));
3033 let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
3034 assert_matches!(convert, None => {
3035 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3036 });
3037 }
3038
3039 #[test]
3040 fn fail_scope2() {
3041 let mut errs = ParseErrors::new();
3042 let parse = text_to_cst::parse_policy(
3043 r#"
3044 permit(
3045 principal in User::"jane",
3046 action == if true then Photo::"view" else Photo::"edit",
3047 resource
3048 );
3049 "#,
3050 )
3051 .expect("failed parse");
3052 println!("{:#}", parse.as_inner().expect("internal parse error"));
3053 let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
3054 assert_matches!(convert, None => {
3055 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3056 });
3057 }
3058
3059 #[test]
3060 fn fail_scope3() {
3061 let mut errs = ParseErrors::new();
3062 let parse = text_to_cst::parse_policy(
3063 r#"
3064 permit(principal,action,resource,context);
3065 "#,
3066 )
3067 .expect("failed parse");
3068 let convert = parse.to_policy(ast::PolicyID::from_string("id"), &mut errs);
3069 assert_matches!(convert, None => {
3070 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3071 });
3072 }
3073
3074 #[test]
3075 fn method_call2() {
3076 let mut errs = ParseErrors::new();
3077 let e = text_to_cst::parse_expr(
3078 r#"
3079 principal.contains(resource)
3080 "#,
3081 )
3082 .expect("parse error")
3084 .to_expr(&mut errs);
3085 assert!(
3087 e.is_some(),
3088 "{:?}", miette::Report::new(errs.clone()),
3090 );
3091 assert!(errs.is_empty());
3092
3093 let e = text_to_cst::parse_expr(
3094 r#"
3095 contains(principal,resource)
3096 "#,
3097 )
3098 .expect("parse error")
3100 .to_expr(&mut errs);
3101 assert_matches!(e, None => {
3103 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3104 });
3105 }
3106
3107 #[test]
3108 fn construct_record_1() {
3109 let mut errs = ParseErrors::new();
3110 let e = text_to_cst::parse_expr(
3111 r#"
3112 {one:"one"}
3113 "#,
3114 )
3115 .expect("parse error")
3117 .to_expr(&mut errs)
3118 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3119 if let ast::ExprKind::Record { .. } = e.expr_kind() {
3121 } else {
3123 panic!("not a record")
3124 }
3125 println!("{e}");
3126 }
3127
3128 #[test]
3129 fn construct_record_2() {
3130 let mut errs = ParseErrors::new();
3131 let e = text_to_cst::parse_expr(
3132 r#"
3133 {"one":"one"}
3134 "#,
3135 )
3136 .expect("parse error")
3138 .to_expr(&mut errs)
3139 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3140 if let ast::ExprKind::Record { .. } = e.expr_kind() {
3142 } else {
3144 panic!("not a record")
3145 }
3146 println!("{e}");
3147 }
3148
3149 #[test]
3150 fn construct_record_3() {
3151 let mut errs = ParseErrors::new();
3152 let e = text_to_cst::parse_expr(
3153 r#"
3154 {"one":"one",two:"two"}
3155 "#,
3156 )
3157 .expect("parse error")
3159 .to_expr(&mut errs)
3160 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3161 if let ast::ExprKind::Record { .. } = e.expr_kind() {
3163 } else {
3165 panic!("not a record")
3166 }
3167 println!("{e}");
3168 }
3169
3170 #[test]
3171 fn construct_record_4() {
3172 let mut errs = ParseErrors::new();
3173 let e = text_to_cst::parse_expr(
3174 r#"
3175 {one:"one","two":"two"}
3176 "#,
3177 )
3178 .expect("parse error")
3180 .to_expr(&mut errs)
3181 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3182 if let ast::ExprKind::Record { .. } = e.expr_kind() {
3184 } else {
3186 panic!("not a record")
3187 }
3188 println!("{e}");
3189 }
3190
3191 #[test]
3192 fn construct_record_5() {
3193 let mut errs = ParseErrors::new();
3194 let e = text_to_cst::parse_expr(
3195 r#"
3196 {one:"b\"","b\"":2}
3197 "#,
3198 )
3199 .expect("parse error")
3201 .to_expr(&mut errs)
3202 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3203 if let ast::ExprKind::Record { .. } = e.expr_kind() {
3205 } else {
3207 panic!("not a record")
3208 }
3209 println!("{e}");
3210 }
3211
3212 #[test]
3213 fn construct_invalid_get_1() {
3214 let mut errs = ParseErrors::new();
3215 let e = text_to_cst::parse_expr(
3216 r#"
3217 {"one":1, "two":"two"}[0]
3218 "#,
3219 )
3220 .expect("failed parser")
3221 .to_expr(&mut errs);
3222 assert_matches!(e, None => {
3224 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3225 });
3226 }
3227
3228 #[test]
3229 fn construct_invalid_get_2() {
3230 let mut errs = ParseErrors::new();
3231 let e = text_to_cst::parse_expr(
3232 r#"
3233 {"one":1, "two":"two"}[-1]
3234 "#,
3235 )
3236 .expect("failed parser")
3237 .to_expr(&mut errs);
3238 assert_matches!(e, None => {
3240 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3241 });
3242 }
3243
3244 #[test]
3245 fn construct_invalid_get_3() {
3246 let mut errs = ParseErrors::new();
3247 let e = text_to_cst::parse_expr(
3248 r#"
3249 {"one":1, "two":"two"}[true]
3250 "#,
3251 )
3252 .expect("failed parser")
3253 .to_expr(&mut errs);
3254 assert_matches!(e, None => {
3256 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3257 });
3258 }
3259
3260 #[test]
3261 fn construct_invalid_get_4() {
3262 let mut errs = ParseErrors::new();
3263 let e = text_to_cst::parse_expr(
3264 r#"
3265 {"one":1, "two":"two"}[one]
3266 "#,
3267 )
3268 .expect("failed parser")
3269 .to_expr(&mut errs);
3270 assert_matches!(e, None => {
3272 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3273 });
3274 }
3275
3276 #[test]
3277 fn construct_has_1() {
3278 let mut errs = ParseErrors::new();
3279 let expr: ast::Expr = text_to_cst::parse_expr(
3280 r#"
3281 {"one":1,"two":2} has "arbitrary+ _string"
3282 "#,
3283 )
3284 .expect("failed parser")
3285 .to_expr(&mut errs)
3286 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3287
3288 match expr.expr_kind() {
3289 ast::ExprKind::HasAttr { attr, .. } => {
3290 assert_eq!(attr, "arbitrary+ _string");
3291 }
3292 _ => panic!("should be a has expr"),
3293 }
3294 }
3295
3296 #[test]
3297 fn construct_has_2() {
3298 let mut errs = ParseErrors::new();
3299 let e = text_to_cst::parse_expr(
3300 r#"
3301 {"one":1,"two":2} has 1
3302 "#,
3303 )
3304 .expect("failed parser")
3305 .to_expr(&mut errs);
3306 assert_matches!(e, None => {
3308 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3309 });
3310 }
3311
3312 #[test]
3313 fn construct_like_1() {
3314 let mut errs = ParseErrors::new();
3315 let expr: ast::Expr = text_to_cst::parse_expr(
3316 r#"
3317 "354 hams" like "*5*"
3318 "#,
3319 )
3320 .expect("failed parser")
3321 .to_expr(&mut errs)
3322 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3323 match expr.expr_kind() {
3324 ast::ExprKind::Like { pattern, .. } => {
3325 assert_eq!(pattern.to_string(), "*5*");
3326 }
3327 _ => panic!("should be a like expr"),
3328 }
3329 }
3330
3331 #[test]
3332 fn construct_like_2() {
3333 let mut errs = ParseErrors::new();
3334 let e = text_to_cst::parse_expr(
3335 r#"
3336 "354 hams" like 354
3337 "#,
3338 )
3339 .expect("failed parser")
3340 .to_expr(&mut errs);
3341 assert_matches!(e, None => {
3343 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3344 });
3345 }
3346
3347 #[test]
3348 fn construct_like_3() {
3349 let mut errs = ParseErrors::new();
3350 let expr: ast::Expr = text_to_cst::parse_expr(
3351 r#"
3352 "string\\with\\backslashes" like "string\\with\\backslashes"
3353 "#,
3354 )
3355 .expect("failed parser")
3356 .to_expr(&mut errs)
3357 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3358 match expr.expr_kind() {
3359 ast::ExprKind::Like { pattern, .. } => {
3360 assert_eq!(pattern.to_string(), r"string\\with\\backslashes");
3361 }
3362 _ => panic!("should be a like expr"),
3363 }
3364 }
3365
3366 #[test]
3367 fn construct_like_4() {
3368 let mut errs = ParseErrors::new();
3369 let expr: ast::Expr = text_to_cst::parse_expr(
3370 r#"
3371 "string\\with\\backslashes" like "string\*with\*backslashes"
3372 "#,
3373 )
3374 .expect("failed parser")
3375 .to_expr(&mut errs)
3376 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3377 match expr.expr_kind() {
3378 ast::ExprKind::Like { pattern, .. } => {
3379 assert_eq!(pattern.to_string(), r"string\*with\*backslashes");
3380 }
3381 _ => panic!("should be a like expr"),
3382 }
3383 }
3384
3385 #[test]
3386 fn construct_like_5() {
3387 let mut errs = ParseErrors::new();
3388 let e = text_to_cst::parse_expr(
3389 r#"
3390 "string\*with\*escaped\*stars" like "string\*with\*escaped\*stars"
3391 "#,
3392 )
3393 .expect("failed parser")
3394 .to_expr(&mut errs);
3395 assert_matches!(e, None => {
3397 assert_eq!(errs.len(), 3, "{:?}", miette::Report::new(errs)); });
3399 }
3400
3401 #[test]
3402 fn construct_like_6() {
3403 let mut errs = ParseErrors::new();
3404 let expr: ast::Expr = text_to_cst::parse_expr(
3405 r#"
3406 "string*with*stars" like "string\*with\*stars"
3407 "#,
3408 )
3409 .expect("failed parser")
3410 .to_expr(&mut errs)
3411 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3412 match expr.expr_kind() {
3413 ast::ExprKind::Like { pattern, .. } => {
3414 assert_eq!(pattern.to_string(), "string\\*with\\*stars");
3415 }
3416 _ => panic!("should be a like expr"),
3417 }
3418 }
3419
3420 #[test]
3421 fn construct_like_7() {
3422 let mut errs = ParseErrors::new();
3423 let expr: ast::Expr = text_to_cst::parse_expr(
3424 r#"
3425 "string\\*with\\*backslashes\\*and\\*stars" like "string\\\*with\\\*backslashes\\\*and\\\*stars"
3426 "#,
3427 )
3428 .expect("failed parser")
3429 .to_expr(&mut errs)
3430 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3431 match expr.expr_kind() {
3432 ast::ExprKind::Like { pattern, .. } => {
3433 assert_eq!(
3434 pattern.to_string(),
3435 r"string\\\*with\\\*backslashes\\\*and\\\*stars"
3436 );
3437 }
3438 _ => panic!("should be a like expr"),
3439 }
3440 }
3441
3442 #[test]
3443 fn pattern_roundtrip() {
3444 let mut errs = ParseErrors::new();
3445 let test_pattern = &vec![
3446 PatternElem::Char('h'),
3447 PatternElem::Char('e'),
3448 PatternElem::Char('l'),
3449 PatternElem::Char('l'),
3450 PatternElem::Char('o'),
3451 PatternElem::Char('\\'),
3452 PatternElem::Char('0'),
3453 PatternElem::Char('*'),
3454 PatternElem::Char('\\'),
3455 PatternElem::Char('*'),
3456 ];
3457 let e1 = ast::Expr::like(ast::Expr::val("hello"), test_pattern.clone());
3458 let s1 = format!("{e1}");
3459 assert_eq!(s1, r#""hello" like "hello\\0\*\\\*""#);
3461 let e2 = text_to_cst::parse_expr(&s1)
3462 .expect("failed parser")
3463 .to_expr(&mut errs)
3464 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3465 match e2.expr_kind() {
3466 ast::ExprKind::Like { pattern, .. } => {
3467 assert_eq!(pattern.get_elems(), test_pattern);
3468 }
3469 _ => panic!("should be a like expr"),
3470 }
3471 let s2 = format!("{e2}");
3472 assert_eq!(s1, s2);
3473 }
3474
3475 #[test]
3476 fn issue_wf_5046() {
3477 let policy = parse_policy(
3478 Some("WF-5046".into()),
3479 r#"permit(
3480 principal,
3481 action in [Action::"action"],
3482 resource in G::""
3483 ) when {
3484 true && ("" like "/gisterNatives\\*D")
3485 };"#,
3486 );
3487 assert!(policy.is_ok());
3488 }
3489
3490 #[test]
3491 fn entity_access() {
3492 let mut errs = ParseErrors::new();
3496 let expr: ast::Expr = text_to_cst::parse_expr(
3497 r#"
3498 User::"jane" has age
3499 "#,
3500 )
3501 .expect("failed parser")
3502 .to_expr(&mut errs)
3503 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3504 match expr.expr_kind() {
3505 ast::ExprKind::HasAttr { attr, .. } => {
3506 assert_eq!(attr, "age");
3507 }
3508 _ => panic!("should be a has expr"),
3509 }
3510
3511 let mut errs = ParseErrors::new();
3513 let expr: ast::Expr = text_to_cst::parse_expr(
3514 r#"
3515 User::"jane" has "arbitrary+ _string"
3516 "#,
3517 )
3518 .expect("failed parser")
3519 .to_expr(&mut errs)
3520 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3521 match expr.expr_kind() {
3522 ast::ExprKind::HasAttr { attr, .. } => {
3523 assert_eq!(attr, "arbitrary+ _string");
3524 }
3525 _ => panic!("should be a has expr"),
3526 }
3527
3528 let mut errs = ParseErrors::new();
3530 let e = text_to_cst::parse_expr(
3531 r#"
3532 User::"jane" has 1
3533 "#,
3534 )
3535 .expect("failed parser")
3536 .to_expr(&mut errs);
3537 assert_matches!(e, None => {
3538 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3539 });
3540
3541 let mut errs = ParseErrors::new();
3543 let expr: ast::Expr = text_to_cst::parse_expr(
3544 r#"
3545 User::"jane".age
3546 "#,
3547 )
3548 .expect("failed parser")
3549 .to_expr(&mut errs)
3550 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3551 match expr.expr_kind() {
3552 ast::ExprKind::GetAttr { attr, .. } => {
3553 assert_eq!(attr, "age");
3554 }
3555 _ => panic!("should be a get expr"),
3556 }
3557
3558 let mut errs = ParseErrors::new();
3560 let expr: ast::Expr = text_to_cst::parse_expr(
3561 r#"
3562 User::"jane"["arbitrary+ _string"]
3563 "#,
3564 )
3565 .expect("failed parser")
3566 .to_expr(&mut errs)
3567 .unwrap_or_else(|| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
3568 match expr.expr_kind() {
3569 ast::ExprKind::GetAttr { attr, .. } => {
3570 assert_eq!(attr, "arbitrary+ _string");
3571 }
3572 _ => panic!("should be a get expr"),
3573 }
3574
3575 let mut errs = ParseErrors::new();
3577 let e = text_to_cst::parse_expr(
3578 r#"
3579 User::"jane"[age]
3580 "#,
3581 )
3582 .expect("failed parser")
3583 .to_expr(&mut errs);
3584 assert_matches!(e, None => {
3585 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3586 });
3587 }
3588
3589 #[test]
3590 fn relational_ops1() {
3591 let mut errs = ParseErrors::new();
3592 let e = text_to_cst::parse_expr(
3593 r#"
3594 3 >= 2 >= 1
3595 "#,
3596 )
3597 .expect("parse error")
3599 .to_expr(&mut errs);
3600 assert_matches!(e, None => {
3602 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3603 });
3604 }
3605
3606 #[test]
3607 fn relational_ops2() {
3608 let mut errs = ParseErrors::new();
3609 let e = text_to_cst::parse_expr(
3610 r#"
3611 3 >= ("dad" in "dad")
3612 "#,
3613 )
3614 .expect("parse error")
3616 .to_expr(&mut errs);
3617 assert!(
3619 e.is_some(),
3620 "{:?}", miette::Report::new(errs.clone()),
3622 );
3623 assert!(errs.is_empty());
3624 }
3625
3626 #[test]
3627 fn relational_ops3() {
3628 let mut errs = ParseErrors::new();
3629 let e = text_to_cst::parse_expr(
3630 r#"
3631 (3 >= 2) == true
3632 "#,
3633 )
3634 .expect("parse error")
3636 .to_expr(&mut errs);
3637 assert!(
3639 e.is_some(),
3640 "{:?}", miette::Report::new(errs.clone()),
3642 );
3643 assert!(errs.is_empty());
3644 }
3645
3646 #[test]
3647 fn relational_ops4() {
3648 let mut errs = ParseErrors::new();
3649 let e = text_to_cst::parse_expr(
3650 r#"
3651 if 4 < 3 then 4 != 3 else 4 == 3 < 4
3652 "#,
3653 )
3654 .expect("parse error")
3656 .to_expr(&mut errs);
3657 assert_matches!(e, None => {
3659 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3660 });
3661 }
3662
3663 #[test]
3664 fn arithmetic() {
3665 let mut errs = ParseErrors::new();
3666 let e = text_to_cst::parse_expr(r#" 2 + 4 "#)
3667 .expect("parse error")
3669 .to_expr(&mut errs);
3670 assert!(
3672 e.is_some(),
3673 "{:?}", miette::Report::new(errs.clone()),
3675 );
3676 assert!(errs.is_empty());
3677
3678 let e = text_to_cst::parse_expr(r#" 2 + -5 "#)
3679 .expect("parse error")
3681 .to_expr(&mut errs);
3682 assert!(
3684 e.is_some(),
3685 "{:?}", miette::Report::new(errs.clone()),
3687 );
3688 assert!(errs.is_empty());
3689
3690 let e = text_to_cst::parse_expr(r#" 2 - 5 "#)
3691 .expect("parse error")
3693 .to_expr(&mut errs);
3694 assert!(
3696 e.is_some(),
3697 "{:?}", miette::Report::new(errs.clone()),
3699 );
3700 assert!(errs.is_empty());
3701
3702 let e = text_to_cst::parse_expr(r#" 2 * 5 "#)
3703 .expect("parse error")
3705 .to_expr(&mut errs);
3706 assert!(
3708 e.is_some(),
3709 "{:?}", miette::Report::new(errs.clone()),
3711 );
3712 assert!(errs.is_empty());
3713
3714 let e = text_to_cst::parse_expr(r#" 2 * -5 "#)
3715 .expect("parse error")
3717 .to_expr(&mut errs);
3718 assert!(
3720 e.is_some(),
3721 "{:?}", miette::Report::new(errs.clone()),
3723 );
3724 assert!(errs.is_empty());
3725
3726 let e = text_to_cst::parse_expr(r#" context.size * 4 "#)
3727 .expect("parse error")
3729 .to_expr(&mut errs);
3730 assert!(
3732 e.is_some(),
3733 "{:?}", miette::Report::new(errs.clone()),
3735 );
3736 assert!(errs.is_empty());
3737
3738 let e = text_to_cst::parse_expr(r#" 4 * context.size "#)
3739 .expect("parse error")
3741 .to_expr(&mut errs);
3742 assert!(
3744 e.is_some(),
3745 "{:?}", miette::Report::new(errs.clone()),
3747 );
3748 assert!(errs.is_empty());
3749
3750 let e = text_to_cst::parse_expr(r#" context.size * context.scale "#)
3751 .expect("parse error")
3753 .to_expr(&mut errs);
3754 assert!(
3756 e.is_some(),
3757 "{:?}", miette::Report::new(errs.clone()),
3759 );
3760 assert!(errs.is_empty());
3761
3762 let e = text_to_cst::parse_expr(r#" 5 + 10 + 90 "#)
3763 .expect("parse error")
3765 .to_expr(&mut errs);
3766 assert!(
3768 e.is_some(),
3769 "{:?}", miette::Report::new(errs.clone()),
3771 );
3772 assert!(errs.is_empty());
3773
3774 let e = text_to_cst::parse_expr(r#" 5 + 10 - 90 * -2 "#)
3775 .expect("parse error")
3777 .to_expr(&mut errs);
3778 assert!(
3780 e.is_some(),
3781 "{:?}", miette::Report::new(errs.clone()),
3783 );
3784 assert!(errs.is_empty());
3785
3786 let e = text_to_cst::parse_expr(r#" 5 + 10 * 90 - 2 "#)
3787 .expect("parse error")
3789 .to_expr(&mut errs);
3790 assert!(
3792 e.is_some(),
3793 "{:?}", miette::Report::new(errs.clone()),
3795 );
3796 assert!(errs.is_empty());
3797
3798 let e = text_to_cst::parse_expr(r#" 5 - 10 - 90 - 2 "#)
3799 .expect("parse error")
3801 .to_expr(&mut errs);
3802 assert!(
3804 e.is_some(),
3805 "{:?}", miette::Report::new(errs.clone()),
3807 );
3808 assert!(errs.is_empty());
3809
3810 let e = text_to_cst::parse_expr(r#" 5 * context.size * 10 "#)
3811 .expect("parse error")
3813 .to_expr(&mut errs);
3814 assert!(
3816 e.is_some(),
3817 "{:?}", miette::Report::new(errs.clone()),
3819 );
3820 assert!(errs.is_empty());
3821
3822 let e = text_to_cst::parse_expr(r#" context.size * 3 * context.scale "#)
3823 .expect("parse error")
3825 .to_expr(&mut errs);
3826 assert!(
3828 e.is_some(),
3829 "{:?}", miette::Report::new(errs.clone()),
3831 );
3832 assert!(errs.is_empty());
3833 }
3834
3835 const CORRECT_TEMPLATES: [&str; 7] = [
3836 r#"permit(principal == ?principal, action == Action::"action", resource == ?resource);"#,
3837 r#"permit(principal in ?principal, action == Action::"action", resource in ?resource);"#,
3838 r#"permit(principal in ?principal, action == Action::"action", resource in ?resource);"#,
3839 r#"permit(principal in p::"principal", action == Action::"action", resource in ?resource);"#,
3840 r#"permit(principal == p::"principal", action == Action::"action", resource in ?resource);"#,
3841 r#"permit(principal in ?principal, action == Action::"action", resource in r::"resource");"#,
3842 r#"permit(principal in ?principal, action == Action::"action", resource == r::"resource");"#,
3843 ];
3844
3845 #[test]
3846 fn template_tests() {
3847 for src in CORRECT_TEMPLATES {
3848 let mut errs = ParseErrors::new();
3849 let e = text_to_cst::parse_policy(src)
3850 .expect("parse_error")
3851 .to_policy_template(ast::PolicyID::from_string("i0"), &mut errs);
3852 if e.is_none() {
3853 panic!("Failed to create a policy template: {:?}", errs);
3854 }
3855 }
3856 }
3857
3858 #[test]
3859 fn var_type() {
3860 let mut errs = ParseErrors::new();
3861 let e = text_to_cst::parse_policy(
3862 r#"
3863 permit(principal,action,resource);
3864 "#,
3865 )
3866 .expect("parse error")
3868 .to_policy(ast::PolicyID::from_string("0"), &mut errs);
3869 assert!(
3871 e.is_some(),
3872 "{:?}", miette::Report::new(errs.clone()),
3874 );
3875 assert!(errs.is_empty());
3876
3877 let e = text_to_cst::parse_policy(
3878 r#"
3879 permit(principal:User,action,resource);
3880 "#,
3881 )
3882 .expect("parse error")
3884 .to_policy(ast::PolicyID::from_string("1"), &mut errs);
3885 assert_matches!(e, None => {
3887 assert_eq!(errs.len(), 1, "{:?}", miette::Report::new(errs));
3888 });
3889 }
3890 #[test]
3891 fn string_escapes() {
3892 let test_valid = |s: &str| {
3899 let r = parse_literal(&format!("\"{}\"", s.escape_default()));
3900 assert!(r.is_ok());
3901 assert_eq!(r.unwrap(), ast::Literal::String(s.into()));
3902 };
3903 test_valid("\t");
3904 test_valid("\0");
3905 test_valid("👍");
3906 test_valid("🐈");
3907 test_valid("\u{1F408}");
3908 test_valid("abc\tde\\fg");
3909 test_valid("aaa\u{1F408}bcd👍👍👍");
3910 let test_invalid = |s: &str, en: usize| {
3912 let r = parse_literal(&format!("\"{}\"", s));
3913 assert!(r.is_err());
3914 assert_eq!(r.unwrap_err().len(), en);
3915 };
3916 test_invalid("\\a", 1);
3918 test_invalid("\\b", 1);
3920 test_invalid("\\\\aa\\p", 1);
3922 test_invalid(r"\aaa\u{}", 2);
3924 }
3925
3926 #[test]
3927 fn unescape_err_positions() {
3928 let assert_invalid_escape = |p_src, underline| {
3929 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
3930 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("the input `\\q` is not a valid escape: InvalidEscape").exactly_one_underline(underline).build());
3931 });
3932 };
3933 assert_invalid_escape(
3934 r#"@foo("\q")permit(principal, action, resource);"#,
3935 r#"@foo("\q")"#,
3936 );
3937 assert_invalid_escape(
3938 r#"permit(principal, action, resource) when { "\q" };"#,
3939 r#""\q""#,
3940 );
3941 assert_invalid_escape(
3942 r#"permit(principal, action, resource) when { "\q".contains(0) };"#,
3943 r#""\q".contains(0)"#,
3944 );
3945 assert_invalid_escape(
3946 r#"permit(principal, action, resource) when { "\q".bar };"#,
3947 r#""\q".bar"#,
3948 );
3949 assert_invalid_escape(
3950 r#"permit(principal, action, resource) when { "\q"["a"] };"#,
3951 r#""\q"["a"]"#,
3952 );
3953 assert_invalid_escape(
3954 r#"permit(principal, action, resource) when { "" like "\q" };"#,
3955 r#""\q""#,
3956 );
3957 assert_invalid_escape(
3958 r#"permit(principal, action, resource) when { {}["\q"] };"#,
3959 r#""\q""#,
3960 );
3961 assert_invalid_escape(
3962 r#"permit(principal, action, resource) when { {"\q": 0} };"#,
3963 r#""\q""#,
3964 );
3965 assert_invalid_escape(
3966 r#"permit(principal, action, resource) when { User::"\q" };"#,
3967 r#"User::"\q""#,
3968 );
3969 }
3970
3971 #[track_caller] fn expect_action_error(test: &str, euid_strs: Vec<&str>, underlines: Vec<&str>) {
3973 let euids = euid_strs
3974 .iter()
3975 .map(|euid_str| {
3976 EntityUID::from_str(euid_str).expect("Test was provided with invalid euid")
3977 })
3978 .collect::<Vec<_>>();
3979 assert_matches!(parse_policyset(test), Err(es) => {
3980 assert_eq!(es.len(), euids.len(),
3981 "should have produced exactly {} parse errors, produced {}:\n{:?}",
3982 euids.len(),
3983 es.len(),
3984 miette::Report::new(es)
3985 );
3986 for (euid, underline) in euids.into_iter().zip(underlines.into_iter()) {
3987 expect_some_error_matches(
3988 test,
3989 &es,
3990 &ExpectedErrorMessageBuilder::error(&format!("expected an entity uid with the type `Action` but got `{euid}`")).help(
3991 "action entities must have type `Action`, optionally in a namespace",
3992 ).exactly_one_underline(underline).build(),
3993 );
3994 }
3995 });
3996 }
3997
3998 #[test]
3999 fn action_must_be_action() {
4000 parse_policyset(r#"permit(principal, action == Action::"view", resource);"#)
4001 .expect("Valid policy failed to parse");
4002 parse_policyset(r#"permit(principal, action == Foo::Action::"view", resource);"#)
4003 .expect("Valid policy failed to parse");
4004 parse_policyset(r#"permit(principal, action in Action::"view", resource);"#)
4005 .expect("Valid policy failed to parse");
4006 parse_policyset(r#"permit(principal, action in Foo::Action::"view", resource);"#)
4007 .expect("Valid policy failed to parse");
4008 parse_policyset(r#"permit(principal, action in [Foo::Action::"view"], resource);"#)
4009 .expect("Valid policy failed to parse");
4010 parse_policyset(
4011 r#"permit(principal, action in [Foo::Action::"view", Action::"view"], resource);"#,
4012 )
4013 .expect("Valid policy failed to parse");
4014 expect_action_error(
4015 r#"permit(principal, action == Foo::"view", resource);"#,
4016 vec!["Foo::\"view\""],
4017 vec!["action == Foo::\"view\""], );
4019 expect_action_error(
4020 r#"permit(principal, action == Action::Foo::"view", resource);"#,
4021 vec!["Action::Foo::\"view\""],
4022 vec!["action == Action::Foo::\"view\""], );
4024 expect_action_error(
4025 r#"permit(principal, action == Bar::Action::Foo::"view", resource);"#,
4026 vec!["Bar::Action::Foo::\"view\""],
4027 vec!["action == Bar::Action::Foo::\"view\""], );
4029 expect_action_error(
4030 r#"permit(principal, action in Bar::Action::Foo::"view", resource);"#,
4031 vec!["Bar::Action::Foo::\"view\""],
4032 vec!["action in Bar::Action::Foo::\"view\""], );
4034 expect_action_error(
4035 r#"permit(principal, action in [Bar::Action::Foo::"view"], resource);"#,
4036 vec!["Bar::Action::Foo::\"view\""],
4037 vec!["action in [Bar::Action::Foo::\"view\"]"], );
4039 expect_action_error(
4040 r#"permit(principal, action in [Bar::Action::Foo::"view", Action::"check"], resource);"#,
4041 vec!["Bar::Action::Foo::\"view\""],
4042 vec!["action in [Bar::Action::Foo::\"view\", Action::\"check\"]"], );
4044 expect_action_error(
4045 r#"permit(principal, action in [Bar::Action::Foo::"view", Foo::"delete", Action::"check"], resource);"#,
4046 vec!["Bar::Action::Foo::\"view\"", "Foo::\"delete\""],
4047 vec!["action in [Bar::Action::Foo::\"view\", Foo::\"delete\", Action::\"check\"]"], );
4049 }
4050
4051 #[test]
4052 fn method_style() {
4053 let src = r#"permit(principal, action, resource)
4054 when { contains(true) < 1 };"#;
4055 assert_matches!(parse_policyset(src), Err(e) => {
4056 expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error(
4057 "`contains` is a method, not a function",
4058 ).help(
4059 "use a method-style call: `e.contains(..)`",
4060 ).exactly_one_underline("contains(true)").build());
4061 });
4062 }
4063
4064 #[test]
4065 fn test_mul() {
4066 for (str, expected) in [
4067 ("--2*3", Expr::mul(Expr::neg(Expr::val(-2)), Expr::val(3))),
4068 (
4069 "1 * 2 * false",
4070 Expr::mul(Expr::mul(Expr::val(1), Expr::val(2)), Expr::val(false)),
4071 ),
4072 (
4073 "0 * 1 * principal",
4074 Expr::mul(
4075 Expr::mul(Expr::val(0), Expr::val(1)),
4076 Expr::var(ast::Var::Principal),
4077 ),
4078 ),
4079 (
4080 "0 * (-1) * principal",
4081 Expr::mul(
4082 Expr::mul(Expr::val(0), Expr::val(-1)),
4083 Expr::var(ast::Var::Principal),
4084 ),
4085 ),
4086 (
4087 "0 * 6 * context.foo",
4088 Expr::mul(
4089 Expr::mul(Expr::val(0), Expr::val(6)),
4090 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
4091 ),
4092 ),
4093 (
4094 "(0 * 6) * context.foo",
4095 Expr::mul(
4096 Expr::mul(Expr::val(0), Expr::val(6)),
4097 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
4098 ),
4099 ),
4100 (
4101 "0 * (6 * context.foo)",
4102 Expr::mul(
4103 Expr::val(0),
4104 Expr::mul(
4105 Expr::val(6),
4106 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
4107 ),
4108 ),
4109 ),
4110 (
4111 "0 * (context.foo * 6)",
4112 Expr::mul(
4113 Expr::val(0),
4114 Expr::mul(
4115 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
4116 Expr::val(6),
4117 ),
4118 ),
4119 ),
4120 (
4121 "1 * 2 * 3 * context.foo * 4 * 5 * 6",
4122 Expr::mul(
4123 Expr::mul(
4124 Expr::mul(
4125 Expr::mul(
4126 Expr::mul(Expr::mul(Expr::val(1), Expr::val(2)), Expr::val(3)),
4127 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
4128 ),
4129 Expr::val(4),
4130 ),
4131 Expr::val(5),
4132 ),
4133 Expr::val(6),
4134 ),
4135 ),
4136 (
4137 "principal * (1 + 2)",
4138 Expr::mul(
4139 Expr::var(ast::Var::Principal),
4140 Expr::add(Expr::val(1), Expr::val(2)),
4141 ),
4142 ),
4143 (
4144 "principal * -(-1)",
4145 Expr::mul(Expr::var(ast::Var::Principal), Expr::neg(Expr::val(-1))),
4146 ),
4147 (
4148 "principal * --1",
4149 Expr::mul(Expr::var(ast::Var::Principal), Expr::neg(Expr::val(-1))),
4150 ),
4151 (
4152 r#"false * "bob""#,
4153 Expr::mul(Expr::val(false), Expr::val("bob")),
4154 ),
4155 ] {
4156 let mut errs = ParseErrors::new();
4157 let e = text_to_cst::parse_expr(str)
4158 .expect("should construct a CST")
4159 .to_expr(&mut errs)
4160 .unwrap_or_else(|| {
4161 panic!(
4162 "failed convert to AST:\n{:?}",
4163 miette::Report::new(errs.clone())
4164 )
4165 });
4166 assert!(errs.is_empty());
4167 assert!(
4168 e.eq_shape(&expected),
4169 "{e:?} and {expected:?} should have the same shape",
4170 );
4171 }
4172 }
4173
4174 #[test]
4175 fn test_not() {
4176 for (es, expr) in [
4177 (
4178 "!1 + 2 == 3",
4179 Expr::is_eq(
4180 Expr::add(Expr::not(Expr::val(1)), Expr::val(2)),
4181 Expr::val(3),
4182 ),
4183 ),
4184 (
4185 "!!1 + 2 == 3",
4186 Expr::is_eq(
4187 Expr::add(Expr::not(Expr::not(Expr::val(1))), Expr::val(2)),
4188 Expr::val(3),
4189 ),
4190 ),
4191 (
4192 "!!!1 + 2 == 3",
4193 Expr::is_eq(
4194 Expr::add(Expr::not(Expr::val(1)), Expr::val(2)),
4195 Expr::val(3),
4196 ),
4197 ),
4198 (
4199 "!!!!1 + 2 == 3",
4200 Expr::is_eq(
4201 Expr::add(Expr::not(Expr::not(Expr::val(1))), Expr::val(2)),
4202 Expr::val(3),
4203 ),
4204 ),
4205 (
4206 "!!(-1) + 2 == 3",
4207 Expr::is_eq(
4208 Expr::add(Expr::not(Expr::not(Expr::val(-1))), Expr::val(2)),
4209 Expr::val(3),
4210 ),
4211 ),
4212 ] {
4213 let mut errs = ParseErrors::new();
4214 let e = text_to_cst::parse_expr(es)
4215 .expect("should construct a CST")
4216 .to_expr(&mut errs)
4217 .unwrap_or_else(|| {
4218 panic!("failed convert to AST:\n{:?}", miette::Report::new(errs))
4219 });
4220 assert!(
4221 e.eq_shape(&expr),
4222 "{:?} and {:?} should have the same shape.",
4223 e,
4224 expr
4225 );
4226 }
4227 }
4228
4229 #[test]
4230 fn test_neg() {
4231 for (es, expr) in [
4232 ("-(1 + 2)", Expr::neg(Expr::add(Expr::val(1), Expr::val(2)))),
4233 ("1-(2)", Expr::sub(Expr::val(1), Expr::val(2))),
4234 ("1-2", Expr::sub(Expr::val(1), Expr::val(2))),
4235 ("(-1)", Expr::val(-1)),
4236 ("-(-1)", Expr::neg(Expr::val(-1))),
4237 ("--1", Expr::neg(Expr::val(-1))),
4238 ("--(--1)", Expr::neg(Expr::neg(Expr::neg(Expr::val(-1))))),
4239 ("2--1", Expr::sub(Expr::val(2), Expr::val(-1))),
4240 ("-9223372036854775808", Expr::val(-(9223372036854775808))),
4241 (
4244 "--9223372036854775808",
4245 Expr::neg(Expr::val(-9223372036854775808)),
4246 ),
4247 (
4248 "-(9223372036854775807)",
4249 Expr::neg(Expr::val(9223372036854775807)),
4250 ),
4251 ] {
4252 let mut errs = ParseErrors::new();
4253 let e = text_to_cst::parse_expr(es)
4254 .expect("should construct a CST")
4255 .to_expr(&mut errs)
4256 .unwrap_or_else(|| {
4257 panic!("failed convert to AST:\n{:?}", miette::Report::new(errs))
4258 });
4259 assert!(
4260 e.eq_shape(&expr),
4261 "{:?} and {:?} should have the same shape.",
4262 e,
4263 expr
4264 );
4265 }
4266
4267 for (es, em) in [
4268 (
4269 "-9223372036854775809",
4270 ExpectedErrorMessageBuilder::error(
4271 "integer literal `9223372036854775809` is too large",
4272 )
4273 .help("maximum allowed integer literal is `9223372036854775807`")
4274 .exactly_one_underline("-9223372036854775809")
4275 .build(),
4276 ),
4277 (
4282 "-(9223372036854775808)",
4283 ExpectedErrorMessageBuilder::error(
4284 "integer literal `9223372036854775808` is too large",
4285 )
4286 .help("maximum allowed integer literal is `9223372036854775807`")
4287 .exactly_one_underline("9223372036854775808")
4288 .build(),
4289 ),
4290 ] {
4291 let mut errs = ParseErrors::new();
4292 let e = text_to_cst::parse_expr(es)
4293 .expect("should construct a CST")
4294 .to_expr(&mut errs);
4295 assert_matches!(e, None => {
4296 expect_err(es, &miette::Report::new(errs), &em);
4297 });
4298 }
4299 }
4300
4301 #[test]
4302 fn test_is_condition_ok() {
4303 for (es, expr) in [
4304 (
4305 r#"User::"alice" is User"#,
4306 Expr::is_entity_type(
4307 Expr::val(r#"User::"alice""#.parse::<EntityUID>().unwrap()),
4308 "User".parse().unwrap(),
4309 ),
4310 ),
4311 (
4312 r#"principal is User"#,
4313 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
4314 ),
4315 (
4316 r#"principal.foo is User"#,
4317 Expr::is_entity_type(
4318 Expr::get_attr(Expr::var(ast::Var::Principal), "foo".into()),
4319 "User".parse().unwrap(),
4320 ),
4321 ),
4322 (
4323 r#"1 is User"#,
4324 Expr::is_entity_type(Expr::val(1), "User".parse().unwrap()),
4325 ),
4326 (
4327 r#"principal is User in Group::"friends""#,
4328 Expr::and(
4329 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
4330 Expr::is_in(
4331 Expr::var(ast::Var::Principal),
4332 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
4333 ),
4334 ),
4335 ),
4336 (
4337 r#"principal is User && principal in Group::"friends""#,
4338 Expr::and(
4339 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
4340 Expr::is_in(
4341 Expr::var(ast::Var::Principal),
4342 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
4343 ),
4344 ),
4345 ),
4346 (
4347 r#"principal is User || principal in Group::"friends""#,
4348 Expr::or(
4349 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
4350 Expr::is_in(
4351 Expr::var(ast::Var::Principal),
4352 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
4353 ),
4354 ),
4355 ),
4356 (
4357 r#"true && principal is User in principal"#,
4358 Expr::and(
4359 Expr::val(true),
4360 Expr::and(
4361 Expr::is_entity_type(
4362 Expr::var(ast::Var::Principal),
4363 "User".parse().unwrap(),
4364 ),
4365 Expr::is_in(
4366 Expr::var(ast::Var::Principal),
4367 Expr::var(ast::Var::Principal),
4368 ),
4369 ),
4370 ),
4371 ),
4372 (
4373 r#"principal is User in principal && true"#,
4374 Expr::and(
4375 Expr::and(
4376 Expr::is_entity_type(
4377 Expr::var(ast::Var::Principal),
4378 "User".parse().unwrap(),
4379 ),
4380 Expr::is_in(
4381 Expr::var(ast::Var::Principal),
4382 Expr::var(ast::Var::Principal),
4383 ),
4384 ),
4385 Expr::val(true),
4386 ),
4387 ),
4388 (
4389 r#"principal is A::B::C::User"#,
4390 Expr::is_entity_type(
4391 Expr::var(ast::Var::Principal),
4392 "A::B::C::User".parse().unwrap(),
4393 ),
4394 ),
4395 (
4396 r#"principal is A::B::C::User in Group::"friends""#,
4397 Expr::and(
4398 Expr::is_entity_type(
4399 Expr::var(ast::Var::Principal),
4400 "A::B::C::User".parse().unwrap(),
4401 ),
4402 Expr::is_in(
4403 Expr::var(ast::Var::Principal),
4404 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
4405 ),
4406 ),
4407 ),
4408 (
4409 r#"if principal is User then 1 else 2"#,
4410 Expr::ite(
4411 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
4412 Expr::val(1),
4413 Expr::val(2),
4414 ),
4415 ),
4416 (
4417 r#"if principal is User in Group::"friends" then 1 else 2"#,
4418 Expr::ite(
4419 Expr::and(
4420 Expr::is_entity_type(
4421 Expr::var(ast::Var::Principal),
4422 "User".parse().unwrap(),
4423 ),
4424 Expr::is_in(
4425 Expr::var(ast::Var::Principal),
4426 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
4427 ),
4428 ),
4429 Expr::val(1),
4430 Expr::val(2),
4431 ),
4432 ),
4433 (
4434 r#"principal::"alice" is principal"#,
4435 Expr::is_entity_type(
4436 Expr::val(r#"principal::"alice""#.parse::<EntityUID>().unwrap()),
4437 "principal".parse().unwrap(),
4438 ),
4439 ),
4440 (
4441 r#"foo::principal::"alice" is foo::principal"#,
4442 Expr::is_entity_type(
4443 Expr::val(r#"foo::principal::"alice""#.parse::<EntityUID>().unwrap()),
4444 "foo::principal".parse().unwrap(),
4445 ),
4446 ),
4447 (
4448 r#"principal::foo::"alice" is principal::foo"#,
4449 Expr::is_entity_type(
4450 Expr::val(r#"principal::foo::"alice""#.parse::<EntityUID>().unwrap()),
4451 "principal::foo".parse().unwrap(),
4452 ),
4453 ),
4454 (
4455 r#"resource::"thing" is resource"#,
4456 Expr::is_entity_type(
4457 Expr::val(r#"resource::"thing""#.parse::<EntityUID>().unwrap()),
4458 "resource".parse().unwrap(),
4459 ),
4460 ),
4461 (
4462 r#"action::"do" is action"#,
4463 Expr::is_entity_type(
4464 Expr::val(r#"action::"do""#.parse::<EntityUID>().unwrap()),
4465 "action".parse().unwrap(),
4466 ),
4467 ),
4468 (
4469 r#"context::"stuff" is context"#,
4470 Expr::is_entity_type(
4471 Expr::val(r#"context::"stuff""#.parse::<EntityUID>().unwrap()),
4472 "context".parse().unwrap(),
4473 ),
4474 ),
4475 ] {
4476 let e = parse_expr(es).unwrap();
4477 assert!(
4478 e.eq_shape(&expr),
4479 "{:?} and {:?} should have the same shape.",
4480 e,
4481 expr
4482 );
4483 }
4484 }
4485
4486 #[test]
4487 fn is_scope() {
4488 for (src, p, a, r) in [
4489 (
4490 r#"permit(principal is User, action, resource);"#,
4491 PrincipalConstraint::is_entity_type("User".parse().unwrap()),
4492 ActionConstraint::any(),
4493 ResourceConstraint::any(),
4494 ),
4495 (
4496 r#"permit(principal is principal, action, resource);"#,
4497 PrincipalConstraint::is_entity_type("principal".parse().unwrap()),
4498 ActionConstraint::any(),
4499 ResourceConstraint::any(),
4500 ),
4501 (
4502 r#"permit(principal is A::User, action, resource);"#,
4503 PrincipalConstraint::is_entity_type("A::User".parse().unwrap()),
4504 ActionConstraint::any(),
4505 ResourceConstraint::any(),
4506 ),
4507 (
4508 r#"permit(principal is User in Group::"thing", action, resource);"#,
4509 PrincipalConstraint::is_entity_type_in(
4510 "User".parse().unwrap(),
4511 r#"Group::"thing""#.parse().unwrap(),
4512 ),
4513 ActionConstraint::any(),
4514 ResourceConstraint::any(),
4515 ),
4516 (
4517 r#"permit(principal is principal in Group::"thing", action, resource);"#,
4518 PrincipalConstraint::is_entity_type_in(
4519 "principal".parse().unwrap(),
4520 r#"Group::"thing""#.parse().unwrap(),
4521 ),
4522 ActionConstraint::any(),
4523 ResourceConstraint::any(),
4524 ),
4525 (
4526 r#"permit(principal is A::User in Group::"thing", action, resource);"#,
4527 PrincipalConstraint::is_entity_type_in(
4528 "A::User".parse().unwrap(),
4529 r#"Group::"thing""#.parse().unwrap(),
4530 ),
4531 ActionConstraint::any(),
4532 ResourceConstraint::any(),
4533 ),
4534 (
4535 r#"permit(principal is User in ?principal, action, resource);"#,
4536 PrincipalConstraint::is_entity_type_in_slot("User".parse().unwrap()),
4537 ActionConstraint::any(),
4538 ResourceConstraint::any(),
4539 ),
4540 (
4541 r#"permit(principal, action, resource is Folder);"#,
4542 PrincipalConstraint::any(),
4543 ActionConstraint::any(),
4544 ResourceConstraint::is_entity_type("Folder".parse().unwrap()),
4545 ),
4546 (
4547 r#"permit(principal, action, resource is Folder in Folder::"inner");"#,
4548 PrincipalConstraint::any(),
4549 ActionConstraint::any(),
4550 ResourceConstraint::is_entity_type_in(
4551 "Folder".parse().unwrap(),
4552 r#"Folder::"inner""#.parse().unwrap(),
4553 ),
4554 ),
4555 (
4556 r#"permit(principal, action, resource is Folder in ?resource);"#,
4557 PrincipalConstraint::any(),
4558 ActionConstraint::any(),
4559 ResourceConstraint::is_entity_type_in_slot("Folder".parse().unwrap()),
4560 ),
4561 ] {
4562 let policy = parse_policy_or_template(None, src).unwrap();
4563 assert_eq!(policy.principal_constraint(), &p);
4564 assert_eq!(policy.action_constraint(), &a);
4565 assert_eq!(policy.resource_constraint(), &r);
4566 }
4567 }
4568
4569 #[test]
4570 fn is_err() {
4571 let invalid_is_policies = [
4572 (
4573 r#"permit(principal in Group::"friends" is User, action, resource);"#,
4574 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found an `is` expression").exactly_one_underline(r#"Group::"friends" is User"#).build(),
4575 ),
4576 (
4577 r#"permit(principal, action, resource in Folder::"folder" is File);"#,
4578 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found an `is` expression").exactly_one_underline(r#"Folder::"folder" is File"#).build(),
4579 ),
4580 (
4581 r#"permit(principal is User == User::"Alice", action, resource);"#,
4582 ExpectedErrorMessageBuilder::error(
4583 "`is` cannot appear in the scope at the same time as `==`",
4584 ).help(
4585 "try moving `is` into a `when` condition"
4586 ).exactly_one_underline("principal is User == User::\"Alice\"").build(),
4587 ),
4588 (
4589 r#"permit(principal, action, resource is Doc == Doc::"a");"#,
4590 ExpectedErrorMessageBuilder::error(
4591 "`is` cannot appear in the scope at the same time as `==`",
4592 ).help(
4593 "try moving `is` into a `when` condition"
4594 ).exactly_one_underline("resource is Doc == Doc::\"a\"").build(),
4595 ),
4596 (
4597 r#"permit(principal is User::"alice", action, resource);"#,
4598 ExpectedErrorMessageBuilder::error(
4599 r#"right hand side of an `is` expression must be an entity type name, but got `User::"alice"`"#,
4600 ).help(
4601 "try using `==` to test for equality"
4602 ).exactly_one_underline("User::\"alice\"").build(),
4603 ),
4604 (
4605 r#"permit(principal, action, resource is File::"f");"#,
4606 ExpectedErrorMessageBuilder::error(
4607 r#"right hand side of an `is` expression must be an entity type name, but got `File::"f"`"#,
4608 ).help(
4609 "try using `==` to test for equality"
4610 ).exactly_one_underline("File::\"f\"").build(),
4611 ),
4612 (
4613 r#"permit(principal is User in 1, action, resource);"#,
4614 ExpectedErrorMessageBuilder::error(
4615 "expected an entity uid or matching template slot, found literal `1`",
4616 ).exactly_one_underline("1").build(),
4617 ),
4618 (
4619 r#"permit(principal, action, resource is File in 1);"#,
4620 ExpectedErrorMessageBuilder::error(
4621 "expected an entity uid or matching template slot, found literal `1`",
4622 ).exactly_one_underline("1").build(),
4623 ),
4624 (
4625 r#"permit(principal is User in User, action, resource);"#,
4626 ExpectedErrorMessageBuilder::error(
4627 "expected an entity uid or matching template slot, found name `User`",
4628 ).exactly_one_underline("User").build(),
4629 ),
4630 (
4631 r#"permit(principal is User::"Alice" in Group::"f", action, resource);"#,
4632 ExpectedErrorMessageBuilder::error(
4633 r#"right hand side of an `is` expression must be an entity type name, but got `User::"Alice"`"#,
4634 ).help(
4635 "try using `==` to test for equality"
4636 ).exactly_one_underline("User::\"Alice\"").build(),
4637 ),
4638 (
4639 r#"permit(principal, action, resource is File in File);"#,
4640 ExpectedErrorMessageBuilder::error(
4641 "expected an entity uid or matching template slot, found name `File`",
4642 ).exactly_one_underline("File").build(),
4643 ),
4644 (
4645 r#"permit(principal, action, resource is File::"file" in Folder::"folder");"#,
4646 ExpectedErrorMessageBuilder::error(
4647 r#"right hand side of an `is` expression must be an entity type name, but got `File::"file"`"#,
4648 ).help(
4649 "try using `==` to test for equality"
4650 ).exactly_one_underline("File::\"file\"").build(),
4651 ),
4652 (
4653 r#"permit(principal is 1, action, resource);"#,
4654 ExpectedErrorMessageBuilder::error(
4655 r#"right hand side of an `is` expression must be an entity type name, but got `1`"#,
4656 ).help(
4657 "try using `==` to test for equality"
4658 ).exactly_one_underline("1").build(),
4659 ),
4660 (
4661 r#"permit(principal, action, resource is 1);"#,
4662 ExpectedErrorMessageBuilder::error(
4663 r#"right hand side of an `is` expression must be an entity type name, but got `1`"#,
4664 ).help(
4665 "try using `==` to test for equality"
4666 ).exactly_one_underline("1").build(),
4667 ),
4668 (
4669 r#"permit(principal, action is Action, resource);"#,
4670 ExpectedErrorMessageBuilder::error(
4671 "`is` cannot appear in the action scope",
4672 ).help(
4673 "try moving `action is ..` into a `when` condition"
4674 ).exactly_one_underline("action is Action").build(),
4675 ),
4676 (
4677 r#"permit(principal, action is Action::"a", resource);"#,
4678 ExpectedErrorMessageBuilder::error(
4679 "`is` cannot appear in the action scope",
4680 ).help(
4681 "try moving `action is ..` into a `when` condition"
4682 ).exactly_one_underline("action is Action::\"a\"").build(),
4683 ),
4684 (
4685 r#"permit(principal, action is Action in Action::"A", resource);"#,
4686 ExpectedErrorMessageBuilder::error(
4687 "`is` cannot appear in the action scope",
4688 ).help(
4689 "try moving `action is ..` into a `when` condition"
4690 ).exactly_one_underline("action is Action in Action::\"A\"").build(),
4691 ),
4692 (
4693 r#"permit(principal, action is Action in Action, resource);"#,
4694 ExpectedErrorMessageBuilder::error(
4695 "`is` cannot appear in the action scope",
4696 ).help(
4697 "try moving `action is ..` into a `when` condition"
4698 ).exactly_one_underline("action is Action in Action").build(),
4699 ),
4700 (
4701 r#"permit(principal, action is Action::"a" in Action::"b", resource);"#,
4702 ExpectedErrorMessageBuilder::error(
4703 "`is` cannot appear in the action scope",
4704 ).help(
4705 "try moving `action is ..` into a `when` condition"
4706 ).exactly_one_underline("action is Action::\"a\" in Action::\"b\"").build(),
4707 ),
4708 (
4709 r#"permit(principal, action is Action in ?action, resource);"#,
4710 ExpectedErrorMessageBuilder::error(
4711 "`is` cannot appear in the action scope",
4712 ).help(
4713 "try moving `action is ..` into a `when` condition"
4714 ).exactly_one_underline("action is Action in ?action").build(),
4715 ),
4716 (
4717 r#"permit(principal, action is ?action, resource);"#,
4718 ExpectedErrorMessageBuilder::error(
4719 "`is` cannot appear in the action scope",
4720 ).help(
4721 "try moving `action is ..` into a `when` condition"
4722 ).exactly_one_underline("action is ?action").build(),
4723 ),
4724 (
4725 r#"permit(principal is User in ?resource, action, resource);"#,
4726 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?resource instead of ?principal").exactly_one_underline("?resource").build(),
4727 ),
4728 (
4729 r#"permit(principal, action, resource is Folder in ?principal);"#,
4730 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?principal instead of ?resource").exactly_one_underline("?principal").build(),
4731 ),
4732 (
4733 r#"permit(principal is ?principal, action, resource);"#,
4734 ExpectedErrorMessageBuilder::error(
4735 "right hand side of an `is` expression must be an entity type name, but got `?principal`",
4736 ).help(
4737 "try using `==` to test for equality"
4738 ).exactly_one_underline("?principal").build(),
4739 ),
4740 (
4741 r#"permit(principal, action, resource is ?resource);"#,
4742 ExpectedErrorMessageBuilder::error(
4743 "right hand side of an `is` expression must be an entity type name, but got `?resource`",
4744 ).help(
4745 "try using `==` to test for equality"
4746 ).exactly_one_underline("?resource").build(),
4747 ),
4748 (
4749 r#"permit(principal, action, resource) when { principal is 1 };"#,
4750 ExpectedErrorMessageBuilder::error(
4751 r#"right hand side of an `is` expression must be an entity type name, but got `1`"#,
4752 ).help(
4753 "try using `==` to test for equality"
4754 ).exactly_one_underline("1").build(),
4755 ),
4756 (
4757 r#"permit(principal, action, resource) when { principal is User::"alice" in Group::"friends" };"#,
4758 ExpectedErrorMessageBuilder::error(
4759 r#"right hand side of an `is` expression must be an entity type name, but got `User::"alice"`"#,
4760 ).help(
4761 "try using `==` to test for equality"
4762 ).exactly_one_underline("User::\"alice\"").build(),
4763 ),
4764 (
4765 r#"permit(principal, action, resource) when { principal is ! User::"alice" in Group::"friends" };"#,
4766 ExpectedErrorMessageBuilder::error(
4767 r#"right hand side of an `is` expression must be an entity type name, but got `!User::"alice"`"#,
4768 ).help(
4769 "try using `==` to test for equality"
4770 ).exactly_one_underline("! User::\"alice\"").build(),
4771 ),
4772 (
4773 r#"permit(principal, action, resource) when { principal is User::"alice" + User::"alice" in Group::"friends" };"#,
4774 ExpectedErrorMessageBuilder::error(
4775 r#"right hand side of an `is` expression must be an entity type name, but got `User::"alice" + User::"alice"`"#,
4776 ).help(
4777 "try using `==` to test for equality"
4778 ).exactly_one_underline("User::\"alice\" + User::\"alice\"").build(),
4779 ),
4780 (
4781 r#"permit(principal, action, resource) when { principal is User in User::"alice" in Group::"friends" };"#,
4782 ExpectedErrorMessageBuilder::error(
4783 "unexpected token `in`"
4784 ).exactly_one_underline("in").build(),
4785 ),
4786 (
4787 r#"permit(principal, action, resource) when { principal is User == User::"alice" in Group::"friends" };"#,
4788 ExpectedErrorMessageBuilder::error(
4789 "unexpected token `==`"
4790 ).exactly_one_underline("==").build(),
4791 ),
4792 ];
4793 for (p_src, expected) in invalid_is_policies {
4794 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4795 expect_err(p_src, &miette::Report::new(e), &expected);
4796 });
4797 }
4798 }
4799
4800 #[test]
4801 fn issue_255() {
4802 let policy = r#"
4803 permit (
4804 principal == name-with-dashes::"Alice",
4805 action,
4806 resource
4807 );
4808 "#;
4809 assert_matches!(
4810 parse_policy(None, policy),
4811 Err(e) => {
4812 expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error(
4813 "expected an entity uid or matching template slot, found a `+/-` expression",
4814 ).help(
4815 "entity types and namespaces cannot use `+` or `-` characters -- perhaps try `_` or `::` instead?",
4816 ).exactly_one_underline("name-with-dashes::\"Alice\"").build());
4817 }
4818 );
4819 }
4820
4821 #[test]
4822 fn invalid_methods_function_calls() {
4823 let invalid_exprs = [
4824 (
4825 r#"contains([], 1)"#,
4826 ExpectedErrorMessageBuilder::error("`contains` is a method, not a function")
4827 .help("use a method-style call: `e.contains(..)`")
4828 .exactly_one_underline("contains([], 1)")
4829 .build(),
4830 ),
4831 (
4832 r#"[].contains()"#,
4833 ExpectedErrorMessageBuilder::error(
4834 "call to `contains` requires exactly 1 argument, but got 0 arguments",
4835 )
4836 .exactly_one_underline("[].contains()")
4837 .build(),
4838 ),
4839 (
4840 r#"[].contains(1, 2)"#,
4841 ExpectedErrorMessageBuilder::error(
4842 "call to `contains` requires exactly 1 argument, but got 2 arguments",
4843 )
4844 .exactly_one_underline("[].contains(1, 2)")
4845 .build(),
4846 ),
4847 (
4848 r#"[].containsAll()"#,
4849 ExpectedErrorMessageBuilder::error(
4850 "call to `containsAll` requires exactly 1 argument, but got 0 arguments",
4851 )
4852 .exactly_one_underline("[].containsAll()")
4853 .build(),
4854 ),
4855 (
4856 r#"[].containsAll(1, 2)"#,
4857 ExpectedErrorMessageBuilder::error(
4858 "call to `containsAll` requires exactly 1 argument, but got 2 arguments",
4859 )
4860 .exactly_one_underline("[].containsAll(1, 2)")
4861 .build(),
4862 ),
4863 (
4864 r#"[].containsAny()"#,
4865 ExpectedErrorMessageBuilder::error(
4866 "call to `containsAny` requires exactly 1 argument, but got 0 arguments",
4867 )
4868 .exactly_one_underline("[].containsAny()")
4869 .build(),
4870 ),
4871 (
4872 r#"[].containsAny(1, 2)"#,
4873 ExpectedErrorMessageBuilder::error(
4874 "call to `containsAny` requires exactly 1 argument, but got 2 arguments",
4875 )
4876 .exactly_one_underline("[].containsAny(1, 2)")
4877 .build(),
4878 ),
4879 (
4880 r#""1.1.1.1".ip()"#,
4881 ExpectedErrorMessageBuilder::error("`ip` is a function, not a method")
4882 .help("use a function-style call: `ip(..)`")
4883 .exactly_one_underline(r#""1.1.1.1".ip()"#)
4884 .build(),
4885 ),
4886 (
4887 r#"greaterThan(1, 2)"#,
4888 ExpectedErrorMessageBuilder::error("`greaterThan` is a method, not a function")
4889 .help("use a method-style call: `e.greaterThan(..)`")
4890 .exactly_one_underline("greaterThan(1, 2)")
4891 .build(),
4892 ),
4893 (
4894 "[].bar()",
4895 ExpectedErrorMessageBuilder::error("not a valid method name: `bar`")
4896 .exactly_one_underline("[].bar()")
4897 .build(),
4898 ),
4899 (
4900 "bar([])",
4901 ExpectedErrorMessageBuilder::error("`bar` is not a function")
4902 .exactly_one_underline("bar([])")
4903 .build(),
4904 ),
4905 (
4906 "principal()",
4907 ExpectedErrorMessageBuilder::error("`principal(...)` is not a valid function call")
4908 .help("variables cannot be called as functions")
4909 .exactly_one_underline("principal()")
4910 .build(),
4911 ),
4912 (
4913 "(1+1)()",
4914 ExpectedErrorMessageBuilder::error(
4915 "function calls must be of the form: `<name>(arg1, arg2, ...)`",
4916 )
4917 .exactly_one_underline("(1+1)()")
4918 .build(),
4919 ),
4920 (
4921 "foo.bar()",
4922 ExpectedErrorMessageBuilder::error(
4923 "attempted to call `foo.bar`, but `foo` does not have any methods",
4924 )
4925 .exactly_one_underline("foo.bar()")
4926 .build(),
4927 ),
4928 ];
4929 for (src, expected) in invalid_exprs {
4930 assert_matches!(parse_expr(src), Err(e) => {
4931 expect_err(src, &miette::Report::new(e), &expected);
4932 });
4933 }
4934 }
4935
4936 #[test]
4937 fn invalid_slot() {
4938 let invalid_policies = [
4939 (
4940 r#"permit(principal == ?resource, action, resource);"#,
4941 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?resource instead of ?principal").exactly_one_underline("?resource").build(),
4942 ),
4943 (
4944 r#"permit(principal in ?resource, action, resource);"#,
4945 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?resource instead of ?principal").exactly_one_underline("?resource").build(),
4946 ),
4947 (
4948 r#"permit(principal == ?foo, action, resource);"#,
4949 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?foo instead of ?principal").exactly_one_underline("?foo").build(),
4950 ),
4951 (
4952 r#"permit(principal in ?foo, action, resource);"#,
4953 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?foo instead of ?principal").exactly_one_underline("?foo").build(),
4954 ),
4955
4956 (
4957 r#"permit(principal, action, resource == ?principal);"#,
4958 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?principal instead of ?resource").exactly_one_underline("?principal").build(),
4959 ),
4960 (
4961 r#"permit(principal, action, resource in ?principal);"#,
4962 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?principal instead of ?resource").exactly_one_underline("?principal").build(),
4963 ),
4964 (
4965 r#"permit(principal, action, resource == ?baz);"#,
4966 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?baz instead of ?resource").exactly_one_underline("?baz").build(),
4967 ),
4968 (
4969 r#"permit(principal, action, resource in ?baz);"#,
4970 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?baz instead of ?resource").exactly_one_underline("?baz").build(),
4971 ),
4972 (
4973 r#"permit(principal, action, resource) when { principal == ?foo};"#,
4974 ExpectedErrorMessageBuilder::error(
4975 "`?foo` is not a valid template slot",
4976 ).help(
4977 "a template slot may only be `?principal` or `?resource`",
4978 ).exactly_one_underline("?foo").build(),
4979 ),
4980
4981 (
4982 r#"permit(principal, action == ?action, resource);"#,
4983 ExpectedErrorMessageBuilder::error("expected single entity uid, got: template slot").exactly_one_underline("?action").build(),
4984 ),
4985 (
4986 r#"permit(principal, action in ?action, resource);"#,
4987 ExpectedErrorMessageBuilder::error("expected single entity uid or set of entity uids, got: template slot").exactly_one_underline("?action").build(),
4988 ),
4989 (
4990 r#"permit(principal, action == ?principal, resource);"#,
4991 ExpectedErrorMessageBuilder::error("expected single entity uid, got: template slot").exactly_one_underline("?principal").build(),
4992 ),
4993 (
4994 r#"permit(principal, action in ?principal, resource);"#,
4995 ExpectedErrorMessageBuilder::error("expected single entity uid or set of entity uids, got: template slot").exactly_one_underline("?principal").build(),
4996 ),
4997 (
4998 r#"permit(principal, action == ?resource, resource);"#,
4999 ExpectedErrorMessageBuilder::error("expected single entity uid, got: template slot").exactly_one_underline("?resource").build(),
5000 ),
5001 (
5002 r#"permit(principal, action in ?resource, resource);"#,
5003 ExpectedErrorMessageBuilder::error("expected single entity uid or set of entity uids, got: template slot").exactly_one_underline("?resource").build(),
5004 ),
5005 (
5006 r#"permit(principal, action in [?bar], resource);"#,
5007 ExpectedErrorMessageBuilder::error("expected single entity uid, got: template slot").exactly_one_underline("?bar").build(),
5008 ),
5009 ];
5010
5011 for (p_src, expected) in invalid_policies {
5012 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5013 expect_err(p_src, &miette::Report::new(e), &expected);
5014 });
5015 let forbid_src = format!("forbid{}", &p_src[6..]);
5016 assert_matches!(parse_policy_or_template(None, &forbid_src), Err(e) => {
5017 expect_err(forbid_src.as_str(), &miette::Report::new(e), &expected);
5018 });
5019 }
5020 }
5021
5022 #[test]
5023 fn missing_scope_constraint() {
5024 let p_src = "permit();";
5025 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5026 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("this policy is missing the `principal` variable in the scope").exactly_one_underline("").build());
5027 });
5028 let p_src = "permit(principal);";
5029 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5030 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("this policy is missing the `action` variable in the scope").exactly_one_underline("").build());
5031 });
5032 let p_src = "permit(principal, action);";
5033 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5034 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("this policy is missing the `resource` variable in the scope").exactly_one_underline("").build());
5035 });
5036 }
5037
5038 #[test]
5039 fn invalid_scope_constraint() {
5040 let p_src = "permit(foo, action, resource);";
5041 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5042 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5043 "expected a variable that is valid in the policy scope; found: `foo`",
5044 ).help(
5045 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
5046 ).exactly_one_underline("foo").build());
5047 });
5048 let p_src = "permit(foo::principal, action, resource);";
5049 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5050 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5051 "unexpected token `::`",
5052 ).exactly_one_underline("::").build());
5053 });
5054 let p_src = "permit(resource, action, resource);";
5055 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5056 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5057 "found the variable `resource` where the variable `principal` must be used",
5058 ).help(
5059 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
5060 ).exactly_one_underline("resource").build());
5061 });
5062
5063 let p_src = "permit(principal, principal, resource);";
5064 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5065 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5066 "found the variable `principal` where the variable `action` must be used",
5067 ).help(
5068 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
5069 ).exactly_one_underline("principal").build());
5070 });
5071 let p_src = "permit(principal, if, resource);";
5072 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5073 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5074 "expected a variable that is valid in the policy scope; found: `if`",
5075 ).help(
5076 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
5077 ).exactly_one_underline("if").build());
5078 });
5079
5080 let p_src = "permit(principal, action, like);";
5081 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5082 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5083 "expected a variable that is valid in the policy scope; found: `like`",
5084 ).help(
5085 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
5086 ).exactly_one_underline("like").build());
5087 });
5088 let p_src = "permit(principal, action, principal);";
5089 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5090 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5091 "found the variable `principal` where the variable `resource` must be used",
5092 ).help(
5093 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
5094 ).exactly_one_underline("principal").build());
5095 });
5096 let p_src = "permit(principal, action, action);";
5097 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5098 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5099 "found the variable `action` where the variable `resource` must be used",
5100 ).help(
5101 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
5102 ).exactly_one_underline("action").build());
5103 });
5104 }
5105
5106 #[test]
5107 fn invalid_scope_operator() {
5108 let p_src = r#"permit(principal > User::"alice", action, resource);"#;
5109 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5110 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5111 "not a valid policy scope constraint: >",
5112 ).help(
5113 "policy scope constraints must be either `==`, `in`, `is`, or `_ is _ in _`"
5114 ).exactly_one_underline("principal > User::\"alice\"").build());
5115 });
5116 let p_src = r#"permit(principal, action != Action::"view", resource);"#;
5117 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5118 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5119 "not a valid policy scope constraint: !=",
5120 ).help(
5121 "policy scope constraints must be either `==`, `in`, `is`, or `_ is _ in _`"
5122 ).exactly_one_underline("action != Action::\"view\"").build());
5123 });
5124 let p_src = r#"permit(principal, action, resource <= Folder::"things");"#;
5125 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5126 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5127 "not a valid policy scope constraint: <=",
5128 ).help(
5129 "policy scope constraints must be either `==`, `in`, `is`, or `_ is _ in _`"
5130 ).exactly_one_underline("resource <= Folder::\"things\"").build());
5131 });
5132 let p_src = r#"permit(principal = User::"alice", action, resource);"#;
5133 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5134 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5135 "'=' is not a valid operator in Cedar",
5136 ).help(
5137 "try using '==' instead",
5138 ).exactly_one_underline("principal = User::\"alice\"").build());
5139 });
5140 }
5141
5142 #[test]
5143 fn scope_action_eq_set() {
5144 let p_src = r#"permit(principal, action == [Action::"view", Action::"edit"], resource);"#;
5145 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5146 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("expected single entity uid, got: set of entity uids").exactly_one_underline(r#"[Action::"view", Action::"edit"]"#).build());
5147 });
5148 }
5149
5150 #[test]
5151 fn scope_action_in_set_set() {
5152 let p_src = r#"permit(principal, action in [[Action::"view"]], resource);"#;
5153 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
5154 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("expected single entity uid, got: set of entity uids").exactly_one_underline(r#"[Action::"view"]"#).build());
5155 });
5156 }
5157
5158 #[test]
5159 fn unsupported_ops() {
5160 let src = "1/2";
5161 assert_matches!(parse_expr(src), Err(e) => {
5162 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("division is not supported").exactly_one_underline("1/2").build());
5163 });
5164 let src = "7 % 3";
5165 assert_matches!(parse_expr(src), Err(e) => {
5166 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("remainder/modulo is not supported").exactly_one_underline("7 % 3").build());
5167 });
5168 }
5169
5170 #[test]
5171 fn over_unary() {
5172 let src = "!!!!!!false";
5173 assert_matches!(parse_expr(src), Err(e) => {
5174 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5175 "too many occurrences of `!_`",
5176 ).help(
5177 "cannot chain more the 4 applications of a unary operator"
5178 ).exactly_one_underline("!!!!!!false").build());
5179 });
5180 let src = "-------0";
5181 assert_matches!(parse_expr(src), Err(e) => {
5182 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5183 "too many occurrences of `-_`",
5184 ).help(
5185 "cannot chain more the 4 applications of a unary operator"
5186 ).exactly_one_underline("-------0").build());
5187 });
5188 }
5189
5190 #[test]
5191 fn arbitrary_variables() {
5192 #[track_caller]
5193 fn expect_arbitrary_var(name: &str) {
5194 assert_matches!(parse_expr(name), Err(e) => {
5195 expect_err(name, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5196 "arbitrary variables are not supported; the valid Cedar variables are `principal`, `action`, `resource`, and `context`",
5197 ).help(
5198 &format!("did you mean to enclose `{name}` in quotes to make a string?"),
5199 ).exactly_one_underline(name).build());
5200 })
5201 }
5202 expect_arbitrary_var("foo::principal");
5203 expect_arbitrary_var("bar::action");
5204 expect_arbitrary_var("baz::resource");
5205 expect_arbitrary_var("buz::context");
5206 expect_arbitrary_var("foo::principal");
5207 expect_arbitrary_var("foo::bar::principal");
5208 expect_arbitrary_var("principal::foo");
5209 expect_arbitrary_var("principal::foo::bar");
5210 expect_arbitrary_var("foo::principal::bar");
5211 expect_arbitrary_var("foo");
5212 expect_arbitrary_var("foo::bar");
5213 expect_arbitrary_var("foo::bar::baz");
5214 }
5215
5216 #[test]
5217 fn empty_clause() {
5218 #[track_caller]
5219 fn expect_empty_clause(policy: &str, clause: &str) {
5220 assert_matches!(parse_policy_or_template(None, policy), Err(e) => {
5221 expect_err(policy, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5222 &format!("`{clause}` condition clause cannot be empty")
5223 ).exactly_one_underline(&format!("{clause} {{}}")).build());
5224 })
5225 }
5226
5227 expect_empty_clause("permit(principal, action, resource) when {};", "when");
5228 expect_empty_clause("permit(principal, action, resource) unless {};", "unless");
5229 expect_empty_clause(
5230 "permit(principal, action, resource) when { principal has foo } when {};",
5231 "when",
5232 );
5233 expect_empty_clause(
5234 "permit(principal, action, resource) when { principal has foo } unless {};",
5235 "unless",
5236 );
5237 expect_empty_clause(
5238 "permit(principal, action, resource) when {} unless { resource.bar };",
5239 "when",
5240 );
5241 expect_empty_clause(
5242 "permit(principal, action, resource) unless {} unless { resource.bar };",
5243 "unless",
5244 );
5245 }
5246
5247 #[test]
5248 fn namespaced_attr() {
5249 #[track_caller]
5250 fn expect_namespaced_attr(expr: &str, name: &str) {
5251 assert_matches!(parse_expr(expr), Err(e) => {
5252 expect_err(expr, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5253 &format!("`{name}` cannot be used as an attribute as it contains a namespace")
5254 ).exactly_one_underline(name).build());
5255 })
5256 }
5257
5258 expect_namespaced_attr("principal has foo::bar", "foo::bar");
5259 expect_namespaced_attr("principal has foo::bar::baz", "foo::bar::baz");
5260 expect_namespaced_attr("principal has foo::principal", "foo::principal");
5261 expect_namespaced_attr("{foo::bar: 1}", "foo::bar");
5262
5263 let expr = "principal has if::foo";
5264 assert_matches!(parse_expr(expr), Err(e) => {
5265 expect_err(expr, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5266 "this identifier is reserved and cannot be used: `if`"
5267 ).exactly_one_underline("if").build());
5268 })
5269 }
5270
5271 #[test]
5272 fn reserved_ident_var() {
5273 #[track_caller]
5274 fn expect_reserved_ident(name: &str, reserved: &str) {
5275 assert_matches!(parse_expr(name), Err(e) => {
5276 expect_err(name, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
5277 &format!("this identifier is reserved and cannot be used: `{reserved}`"),
5278 ).exactly_one_underline(reserved).build());
5279 })
5280 }
5281 expect_reserved_ident("if::principal", "if");
5282 expect_reserved_ident("then::action", "then");
5283 expect_reserved_ident("else::resource", "else");
5284 expect_reserved_ident("true::context", "true");
5285 expect_reserved_ident("false::bar::principal", "false");
5286 expect_reserved_ident("foo::in::principal", "in");
5287 expect_reserved_ident("foo::is::bar::principal", "is");
5288 }
5289}