1use super::cst;
37use super::err::{parse_errors, ParseError, ParseErrors, ToASTError, ToASTErrorKind};
38use super::loc::Loc;
39use super::node::Node;
40use super::unescape::{to_pattern, to_unescaped_string};
41use super::util::{flatten_tuple_2, flatten_tuple_3, flatten_tuple_4};
42use crate::ast::{
43 self, ActionConstraint, CallStyle, Integer, PatternElem, PolicySetError, PrincipalConstraint,
44 PrincipalOrResourceConstraint, ResourceConstraint, UnreservedId,
45};
46use crate::est::extract_single_argument;
47use crate::fuzzy_match::fuzzy_search_limited;
48use itertools::Either;
49use nonempty::NonEmpty;
50use smol_str::{SmolStr, ToSmolStr};
51use std::cmp::Ordering;
52use std::collections::{BTreeMap, HashSet};
53use std::mem;
54use std::sync::Arc;
55
56mod to_ref_or_refs;
60use to_ref_or_refs::OneOrMultipleRefs;
61
62type Result<T> = std::result::Result<T, ParseErrors>;
64
65struct ExtStyles<'a> {
67 functions: HashSet<&'a ast::Name>,
69 methods: HashSet<ast::UnreservedId>,
71 functions_and_methods_as_str: HashSet<SmolStr>,
73}
74
75lazy_static::lazy_static! {
77 static ref EXTENSION_STYLES: ExtStyles<'static> = load_styles();
78}
79fn load_styles() -> ExtStyles<'static> {
80 let mut functions = HashSet::new();
81 let mut methods = HashSet::new();
82 let mut functions_and_methods_as_str = HashSet::new();
83 for func in crate::extensions::Extensions::all_available().all_funcs() {
84 functions_and_methods_as_str.insert(func.name().to_smolstr());
85 match func.style() {
86 CallStyle::FunctionStyle => {
87 functions.insert(func.name());
88 }
89 CallStyle::MethodStyle => {
90 debug_assert!(func.name().is_unqualified());
91 methods.insert(func.name().basename());
92 }
93 };
94 }
95 ExtStyles {
96 functions,
97 methods,
98 functions_and_methods_as_str,
99 }
100}
101
102impl Node<Option<cst::Policies>> {
103 pub fn with_generated_policyids(
106 &self,
107 ) -> Result<impl Iterator<Item = (ast::PolicyID, &Node<Option<cst::Policy>>)>> {
108 let policies = self.try_as_inner()?;
109
110 Ok(policies
111 .0
112 .iter()
113 .enumerate()
114 .map(|(count, node)| (ast::PolicyID::from_string(format!("policy{count}")), node)))
115 }
116
117 pub fn to_policyset(&self) -> Result<ast::PolicySet> {
119 let mut pset = ast::PolicySet::new();
120 let mut all_errs: Vec<ParseErrors> = vec![];
121 for (policy_id, policy) in self.with_generated_policyids()? {
125 match policy.to_policy_or_template(policy_id) {
127 Ok(Either::Right(template)) => {
128 if let Err(e) = pset.add_template(template) {
129 match e {
130 PolicySetError::Occupied { id } => all_errs.push(
131 self.to_ast_err(ToASTErrorKind::DuplicateTemplateId(id))
132 .into(),
133 ),
134 };
135 }
136 }
137 Ok(Either::Left(inline_policy)) => {
138 if let Err(e) = pset.add_static(inline_policy) {
139 match e {
140 PolicySetError::Occupied { id } => all_errs.push(
141 self.to_ast_err(ToASTErrorKind::DuplicatePolicyId(id))
142 .into(),
143 ),
144 };
145 }
146 }
147 Err(errs) => {
148 all_errs.push(errs);
149 }
150 };
151 }
152
153 if let Some(errs) = ParseErrors::flatten(all_errs) {
155 Err(errs)
156 } else {
157 Ok(pset)
158 }
159 }
160}
161
162impl Node<Option<cst::Policy>> {
163 pub fn to_policy_or_template(
165 &self,
166 id: ast::PolicyID,
167 ) -> Result<Either<ast::StaticPolicy, ast::Template>> {
168 let t = self.to_policy_template(id)?;
169 if t.slots().count() == 0 {
170 #[allow(clippy::expect_used)]
172 let p = ast::StaticPolicy::try_from(t).expect("internal invariant violation: a template with no slots should be a valid static policy");
173 Ok(Either::Left(p))
174 } else {
175 Ok(Either::Right(t))
176 }
177 }
178
179 pub fn to_policy(&self, id: ast::PolicyID) -> Result<ast::StaticPolicy> {
181 let maybe_template = self.to_policy_template(id);
182 let maybe_policy = maybe_template.map(ast::StaticPolicy::try_from);
183 match maybe_policy {
184 Ok(Ok(p)) => Ok(p),
186 Ok(Err(ast::UnexpectedSlotError::FoundSlot(slot))) => Err(ToASTError::new(
188 ToASTErrorKind::expected_static_policy(slot.clone()),
189 slot.loc.unwrap_or_else(|| self.loc.clone()),
190 )
191 .into()),
192 Err(mut errs) => {
195 let new_errs = errs
196 .iter()
197 .filter_map(|err| match err {
198 ParseError::ToAST(err) => match err.kind() {
199 ToASTErrorKind::SlotsInConditionClause(inner) => Some(ToASTError::new(
200 ToASTErrorKind::expected_static_policy(inner.slot.clone()),
201 err.source_loc().clone(),
202 )),
203 _ => None,
204 },
205 _ => None,
206 })
207 .collect::<Vec<_>>();
208 errs.extend(new_errs);
209 Err(errs)
210 }
211 }
212 }
213
214 pub fn to_policy_template(&self, id: ast::PolicyID) -> Result<ast::Template> {
217 let policy = self.try_as_inner()?;
218
219 let maybe_effect = policy.effect.to_effect();
221
222 let maybe_annotations = policy.get_ast_annotations(|value, loc| {
224 ast::Annotation::with_optional_value(value, Some(loc.clone()))
225 });
226
227 let maybe_scope = policy.extract_scope();
229
230 let maybe_conds = ParseErrors::transpose(policy.conds.iter().map(|c| {
232 let (e, is_when) = c.to_expr()?;
233 let slot_errs = e.slots().map(|slot| {
234 ToASTError::new(
235 ToASTErrorKind::slots_in_condition_clause(
236 slot.clone(),
237 if is_when { "when" } else { "unless" },
238 ),
239 slot.loc.unwrap_or_else(|| c.loc.clone()),
240 )
241 .into()
242 });
243 match ParseErrors::from_iter(slot_errs) {
244 Some(errs) => Err(errs),
245 None => Ok(e),
246 }
247 }));
248
249 let (effect, annotations, (principal, action, resource), conds) =
250 flatten_tuple_4(maybe_effect, maybe_annotations, maybe_scope, maybe_conds)?;
251 Ok(construct_template_policy(
252 id,
253 annotations.into(),
254 effect,
255 principal,
256 action,
257 resource,
258 conds,
259 &self.loc,
260 ))
261 }
262}
263
264impl cst::Policy {
265 pub fn extract_scope(
267 &self,
268 ) -> Result<(PrincipalConstraint, ActionConstraint, ResourceConstraint)> {
269 let mut end_of_last_var = self.effect.loc.end();
272
273 let mut vars = self.variables.iter();
274 let maybe_principal = if let Some(scope1) = vars.next() {
275 end_of_last_var = scope1.loc.end();
276 scope1.to_principal_constraint()
277 } else {
278 Err(ToASTError::new(
279 ToASTErrorKind::MissingScopeVariable(ast::Var::Principal),
280 self.effect.loc.span(end_of_last_var),
281 )
282 .into())
283 };
284 let maybe_action = if let Some(scope2) = vars.next() {
285 end_of_last_var = scope2.loc.end();
286 scope2.to_action_constraint()
287 } else {
288 Err(ToASTError::new(
289 ToASTErrorKind::MissingScopeVariable(ast::Var::Action),
290 self.effect.loc.span(end_of_last_var),
291 )
292 .into())
293 };
294 let maybe_resource = if let Some(scope3) = vars.next() {
295 scope3.to_resource_constraint()
296 } else {
297 Err(ToASTError::new(
298 ToASTErrorKind::MissingScopeVariable(ast::Var::Resource),
299 self.effect.loc.span(end_of_last_var),
300 )
301 .into())
302 };
303
304 let maybe_extra_vars = if let Some(errs) = ParseErrors::from_iter(
305 vars.map(|extra_var| {
307 extra_var
308 .try_as_inner()
309 .map(|def| extra_var.to_ast_err(ToASTErrorKind::ExtraScopeElement(def.clone())))
310 .unwrap_or_else(|e| e)
311 .into()
312 }),
313 ) {
314 Err(errs)
315 } else {
316 Ok(())
317 };
318 let (principal, action, resource, _) = flatten_tuple_4(
319 maybe_principal,
320 maybe_action,
321 maybe_resource,
322 maybe_extra_vars,
323 )?;
324 Ok((principal, action, resource))
325 }
326
327 pub fn get_ast_annotations<T>(
329 &self,
330 annotation_constructor: impl Fn(Option<SmolStr>, &Loc) -> T,
331 ) -> Result<BTreeMap<ast::AnyId, T>> {
332 let mut annotations = BTreeMap::new();
333 let mut all_errs: Vec<ParseErrors> = vec![];
334 for node in self.annotations.iter() {
335 match node.to_kv_pair(&annotation_constructor) {
336 Ok((k, v)) => {
337 use std::collections::btree_map::Entry;
338 match annotations.entry(k) {
339 Entry::Occupied(oentry) => {
340 all_errs.push(
341 ToASTError::new(
342 ToASTErrorKind::DuplicateAnnotation(oentry.key().clone()),
343 node.loc.clone(),
344 )
345 .into(),
346 );
347 }
348 Entry::Vacant(ventry) => {
349 ventry.insert(v);
350 }
351 }
352 }
353 Err(errs) => {
354 all_errs.push(errs);
355 }
356 }
357 }
358 match ParseErrors::flatten(all_errs) {
359 Some(errs) => Err(errs),
360 None => Ok(annotations),
361 }
362 }
363}
364
365impl Node<Option<cst::Annotation>> {
366 pub fn to_kv_pair<T>(
369 &self,
370 annotation_constructor: impl Fn(Option<SmolStr>, &Loc) -> T,
371 ) -> Result<(ast::AnyId, T)> {
372 let anno = self.try_as_inner()?;
373
374 let maybe_key = anno.key.to_any_ident();
375 let maybe_value = anno
376 .value
377 .as_ref()
378 .map(|a| {
379 a.as_valid_string().and_then(|s| {
380 to_unescaped_string(s).map_err(|unescape_errs| {
381 ParseErrors::new_from_nonempty(
382 unescape_errs.map(|e| self.to_ast_err(e).into()),
383 )
384 })
385 })
386 })
387 .transpose();
388
389 let (k, v) = flatten_tuple_2(maybe_key, maybe_value)?;
390 Ok((k, annotation_constructor(v, &self.loc)))
391 }
392}
393
394impl Node<Option<cst::Ident>> {
395 pub(crate) fn to_unreserved_ident(&self) -> Result<ast::UnreservedId> {
397 self.to_valid_ident()
398 .and_then(|id| id.try_into().map_err(|err| self.to_ast_err(err).into()))
399 }
400 pub fn to_valid_ident(&self) -> Result<ast::Id> {
402 let ident = self.try_as_inner()?;
403
404 match ident {
405 cst::Ident::If
406 | cst::Ident::True
407 | cst::Ident::False
408 | cst::Ident::Then
409 | cst::Ident::Else
410 | cst::Ident::In
411 | cst::Ident::Is
412 | cst::Ident::Has
413 | cst::Ident::Like => Err(self
414 .to_ast_err(ToASTErrorKind::ReservedIdentifier(ident.clone()))
415 .into()),
416 cst::Ident::Invalid(i) => Err(self
417 .to_ast_err(ToASTErrorKind::InvalidIdentifier(i.clone()))
418 .into()),
419 _ => Ok(ast::Id::new_unchecked(format!("{ident}"))),
420 }
421 }
422
423 pub fn to_any_ident(&self) -> Result<ast::AnyId> {
429 let ident = self.try_as_inner()?;
430
431 match ident {
432 cst::Ident::Invalid(i) => Err(self
433 .to_ast_err(ToASTErrorKind::InvalidIdentifier(i.clone()))
434 .into()),
435 _ => Ok(ast::AnyId::new_unchecked(format!("{ident}"))),
436 }
437 }
438
439 pub(crate) fn to_effect(&self) -> Result<ast::Effect> {
440 let effect = self.try_as_inner()?;
441
442 match effect {
443 cst::Ident::Permit => Ok(ast::Effect::Permit),
444 cst::Ident::Forbid => Ok(ast::Effect::Forbid),
445 _ => Err(self
446 .to_ast_err(ToASTErrorKind::InvalidEffect(effect.clone()))
447 .into()),
448 }
449 }
450
451 pub(crate) fn to_cond_is_when(&self) -> Result<bool> {
454 let cond = self.try_as_inner()?;
455
456 match cond {
457 cst::Ident::When => Ok(true),
458 cst::Ident::Unless => Ok(false),
459 _ => Err(self
460 .to_ast_err(ToASTErrorKind::InvalidCondition(cond.clone()))
461 .into()),
462 }
463 }
464
465 fn to_var(&self) -> Result<ast::Var> {
466 let ident = self.try_as_inner()?;
467
468 match ident {
469 cst::Ident::Principal => Ok(ast::Var::Principal),
470 cst::Ident::Action => Ok(ast::Var::Action),
471 cst::Ident::Resource => Ok(ast::Var::Resource),
472 ident => Err(self
473 .to_ast_err(ToASTErrorKind::InvalidScopeVariable(ident.clone()))
474 .into()),
475 }
476 }
477}
478
479impl ast::UnreservedId {
480 fn to_meth(&self, e: ast::Expr, args: Vec<ast::Expr>, loc: &Loc) -> Result<ast::Expr> {
481 match self.as_ref() {
482 "contains" => extract_single_argument(args.into_iter(), "contains", loc)
483 .map(|arg| construct_method_contains(e, arg, loc.clone())),
484 "containsAll" => extract_single_argument(args.into_iter(), "containsAll", loc)
485 .map(|arg| construct_method_contains_all(e, arg, loc.clone())),
486 "containsAny" => extract_single_argument(args.into_iter(), "containsAny", loc)
487 .map(|arg| construct_method_contains_any(e, arg, loc.clone())),
488 "getTag" => extract_single_argument(args.into_iter(), "getTag", loc)
489 .map(|arg| construct_method_get_tag(e, arg, loc.clone())),
490 "hasTag" => extract_single_argument(args.into_iter(), "hasTag", loc)
491 .map(|arg| construct_method_has_tag(e, arg, loc.clone())),
492 _ => {
493 if EXTENSION_STYLES.methods.contains(self) {
494 let args = NonEmpty {
495 head: e,
496 tail: args,
497 };
498 Ok(construct_ext_meth(self.clone(), args, loc.clone()))
499 } else {
500 let unqual_name = ast::Name::unqualified_name(self.clone());
501 if EXTENSION_STYLES.functions.contains(&unqual_name) {
502 Err(ToASTError::new(
503 ToASTErrorKind::MethodCallOnFunction(unqual_name.basename()),
504 loc.clone(),
505 )
506 .into())
507 } else {
508 fn suggest_method(
509 name: &ast::UnreservedId,
510 methods: &HashSet<ast::UnreservedId>,
511 ) -> Option<String> {
512 const SUGGEST_METHOD_MAX_DISTANCE: usize = 3;
513 let method_names =
514 methods.iter().map(ToString::to_string).collect::<Vec<_>>();
515 let suggested_method = fuzzy_search_limited(
516 &name.to_string(),
517 method_names.as_slice(),
518 Some(SUGGEST_METHOD_MAX_DISTANCE),
519 );
520 suggested_method.map(|m| format!("did you mean `{m}`?"))
521 }
522 let hint = suggest_method(&self, &EXTENSION_STYLES.methods);
523 Err(ToASTError::new(
524 ToASTErrorKind::UnknownMethod {
525 id: self.clone(),
526 hint,
527 },
528 loc.clone(),
529 )
530 .into())
531 }
532 }
533 }
534 }
535 }
536}
537
538#[derive(Debug)]
539enum PrincipalOrResource {
540 Principal(PrincipalConstraint),
541 Resource(ResourceConstraint),
542}
543
544impl Node<Option<cst::VariableDef>> {
545 fn to_principal_constraint(&self) -> Result<PrincipalConstraint> {
546 match self.to_principal_or_resource_constraint(ast::Var::Principal)? {
547 PrincipalOrResource::Principal(p) => Ok(p),
548 PrincipalOrResource::Resource(_) => Err(self
549 .to_ast_err(ToASTErrorKind::IncorrectVariable {
550 expected: ast::Var::Principal,
551 got: ast::Var::Resource,
552 })
553 .into()),
554 }
555 }
556
557 fn to_resource_constraint(&self) -> Result<ResourceConstraint> {
558 match self.to_principal_or_resource_constraint(ast::Var::Resource)? {
559 PrincipalOrResource::Principal(_) => Err(self
560 .to_ast_err(ToASTErrorKind::IncorrectVariable {
561 expected: ast::Var::Resource,
562 got: ast::Var::Principal,
563 })
564 .into()),
565 PrincipalOrResource::Resource(r) => Ok(r),
566 }
567 }
568
569 fn to_principal_or_resource_constraint(
570 &self,
571 expected: ast::Var,
572 ) -> Result<PrincipalOrResource> {
573 let vardef = self.try_as_inner()?;
574
575 let var = vardef.variable.to_var()?;
576
577 if let Some(unused_typename) = vardef.unused_type_name.as_ref() {
578 unused_typename.to_type_constraint()?;
579 }
580
581 let c = if let Some((op, rel_expr)) = &vardef.ineq {
582 if op == &cst::RelOp::In {
584 if let Ok(expr) = rel_expr.to_expr() {
585 if matches!(expr.expr_kind(), ast::ExprKind::Is { .. }) {
586 return Err(self.to_ast_err(ToASTErrorKind::InvertedIsIn).into());
587 }
588 }
589 }
590 let eref = rel_expr.to_ref_or_slot(var)?;
591 match (op, &vardef.entity_type) {
592 (cst::RelOp::Eq, None) => Ok(PrincipalOrResourceConstraint::Eq(eref)),
593 (cst::RelOp::Eq, Some(_)) => Err(self.to_ast_err(ToASTErrorKind::IsWithEq)),
594 (cst::RelOp::In, None) => Ok(PrincipalOrResourceConstraint::In(eref)),
595 (cst::RelOp::In, Some(entity_type)) => Ok(PrincipalOrResourceConstraint::IsIn(
596 Arc::new(entity_type.to_expr_or_special()?.into_entity_type()?),
597 eref,
598 )),
599 (cst::RelOp::InvalidSingleEq, _) => {
600 Err(self.to_ast_err(ToASTErrorKind::InvalidSingleEq))
601 }
602 (op, _) => Err(self.to_ast_err(ToASTErrorKind::InvalidScopeOperator(*op))),
603 }
604 } else if let Some(entity_type) = &vardef.entity_type {
605 Ok(PrincipalOrResourceConstraint::Is(Arc::new(
606 entity_type.to_expr_or_special()?.into_entity_type()?,
607 )))
608 } else {
609 Ok(PrincipalOrResourceConstraint::Any)
610 }?;
611 match var {
612 ast::Var::Principal => Ok(PrincipalOrResource::Principal(PrincipalConstraint::new(c))),
613 ast::Var::Resource => Ok(PrincipalOrResource::Resource(ResourceConstraint::new(c))),
614 got => Err(self
615 .to_ast_err(ToASTErrorKind::IncorrectVariable { expected, got })
616 .into()),
617 }
618 }
619
620 fn to_action_constraint(&self) -> Result<ast::ActionConstraint> {
621 let vardef = self.try_as_inner()?;
622
623 match vardef.variable.to_var() {
624 Ok(ast::Var::Action) => Ok(()),
625 Ok(got) => Err(self
626 .to_ast_err(ToASTErrorKind::IncorrectVariable {
627 expected: ast::Var::Action,
628 got,
629 })
630 .into()),
631 Err(errs) => Err(errs),
632 }?;
633
634 if let Some(typename) = vardef.unused_type_name.as_ref() {
635 typename.to_type_constraint()?;
636 }
637
638 if vardef.entity_type.is_some() {
639 return Err(self.to_ast_err(ToASTErrorKind::IsInActionScope).into());
640 }
641
642 if let Some((op, rel_expr)) = &vardef.ineq {
643 let action_constraint = match op {
644 cst::RelOp::In => {
645 if let Ok(expr) = rel_expr.to_expr() {
647 if matches!(expr.expr_kind(), ast::ExprKind::Is { .. }) {
648 return Err(self.to_ast_err(ToASTErrorKind::IsInActionScope).into());
649 }
650 }
651 match rel_expr.to_refs(ast::Var::Action)? {
652 OneOrMultipleRefs::Single(single_ref) => {
653 Ok(ActionConstraint::is_in([single_ref]))
654 }
655 OneOrMultipleRefs::Multiple(refs) => Ok(ActionConstraint::is_in(refs)),
656 }
657 }
658 cst::RelOp::Eq => {
659 let single_ref = rel_expr.to_ref(ast::Var::Action)?;
660 Ok(ActionConstraint::is_eq(single_ref))
661 }
662 cst::RelOp::InvalidSingleEq => {
663 Err(self.to_ast_err(ToASTErrorKind::InvalidSingleEq))
664 }
665 op => Err(self.to_ast_err(ToASTErrorKind::InvalidActionScopeOperator(*op))),
666 }?;
667 action_constraint
668 .contains_only_action_types()
669 .map_err(|non_action_euids| {
670 rel_expr
671 .to_ast_err(parse_errors::InvalidActionType {
672 euids: non_action_euids,
673 })
674 .into()
675 })
676 } else {
677 Ok(ActionConstraint::Any)
678 }
679 }
680}
681
682impl Node<Option<cst::Cond>> {
683 fn to_expr(&self) -> Result<(ast::Expr, bool)> {
688 let cond = self.try_as_inner()?;
689
690 let is_when = cond.cond.to_cond_is_when()?;
691
692 let maybe_expr = match &cond.expr {
693 Some(expr) => expr.to_expr(),
694 None => {
695 let ident = match cond.cond.as_inner() {
696 Some(ident) => ident.clone(),
697 None => {
698 if is_when {
702 cst::Ident::Ident("when".into())
703 } else {
704 cst::Ident::Ident("unless".into())
705 }
706 }
707 };
708 Err(self
709 .to_ast_err(ToASTErrorKind::EmptyClause(Some(ident)))
710 .into())
711 }
712 };
713
714 maybe_expr.map(|e| {
715 if is_when {
716 (e, true)
717 } else {
718 (construct_expr_not(e, self.loc.clone()), false)
719 }
720 })
721 }
722}
723
724impl Node<Option<cst::Str>> {
725 pub(crate) fn as_valid_string(&self) -> Result<&SmolStr> {
726 let id = self.try_as_inner()?;
727
728 match id {
729 cst::Str::String(s) => Ok(s),
730 cst::Str::Invalid(s) => Err(self
732 .to_ast_err(ToASTErrorKind::InvalidString(s.to_string()))
733 .into()),
734 }
735 }
736}
737
738#[derive(Debug)]
744pub(crate) enum ExprOrSpecial<'a> {
745 Expr { expr: ast::Expr, loc: Loc },
747 Var { var: ast::Var, loc: Loc },
749 Name { name: ast::Name, loc: Loc },
751 StrLit { lit: &'a SmolStr, loc: Loc },
754}
755
756impl ExprOrSpecial<'_> {
757 fn to_ast_err(&self, kind: impl Into<ToASTErrorKind>) -> ToASTError {
758 ToASTError::new(
759 kind.into(),
760 match self {
761 ExprOrSpecial::Expr { loc, .. } => loc.clone(),
762 ExprOrSpecial::Var { loc, .. } => loc.clone(),
763 ExprOrSpecial::Name { loc, .. } => loc.clone(),
764 ExprOrSpecial::StrLit { loc, .. } => loc.clone(),
765 },
766 )
767 }
768
769 fn into_expr(self) -> Result<ast::Expr> {
770 match self {
771 Self::Expr { expr, .. } => Ok(expr),
772 Self::Var { var, loc } => Ok(construct_expr_var(var, loc)),
773 Self::Name { name, loc } => Err(ToASTError::new(
774 ToASTErrorKind::ArbitraryVariable(name.to_string().into()),
775 loc,
776 )
777 .into()),
778 Self::StrLit { lit, loc } => {
779 match to_unescaped_string(lit) {
780 Ok(s) => Ok(construct_expr_string(s, loc)),
781 Err(escape_errs) => Err(ParseErrors::new_from_nonempty(escape_errs.map(|e| {
782 ToASTError::new(ToASTErrorKind::Unescape(e), loc.clone()).into()
783 }))),
784 }
785 }
786 }
787 }
788
789 pub(crate) fn into_valid_attr(self) -> Result<SmolStr> {
791 match self {
792 Self::Var { var, .. } => Ok(construct_string_from_var(var)),
793 Self::Name { name, loc } => name.into_valid_attr(loc),
794 Self::StrLit { lit, loc } => to_unescaped_string(lit).map_err(|escape_errs| {
795 ParseErrors::new_from_nonempty(
796 escape_errs
797 .map(|e| ToASTError::new(ToASTErrorKind::Unescape(e), loc.clone()).into()),
798 )
799 }),
800 Self::Expr { expr, loc } => Err(ToASTError::new(
801 ToASTErrorKind::InvalidAttribute(expr.to_string().into()),
802 loc,
803 )
804 .into()),
805 }
806 }
807
808 pub(crate) fn into_pattern(self) -> Result<Vec<PatternElem>> {
809 match &self {
810 Self::StrLit { lit, .. } => to_pattern(lit).map_err(|escape_errs| {
811 ParseErrors::new_from_nonempty(
812 escape_errs.map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e)).into()),
813 )
814 }),
815 Self::Var { var, .. } => Err(self
816 .to_ast_err(ToASTErrorKind::InvalidPattern(var.to_string()))
817 .into()),
818 Self::Name { name, .. } => Err(self
819 .to_ast_err(ToASTErrorKind::InvalidPattern(name.to_string()))
820 .into()),
821 Self::Expr { expr, .. } => Err(self
822 .to_ast_err(ToASTErrorKind::InvalidPattern(expr.to_string()))
823 .into()),
824 }
825 }
826 fn into_string_literal(self) -> Result<SmolStr> {
828 match &self {
829 Self::StrLit { lit, .. } => to_unescaped_string(lit).map_err(|escape_errs| {
830 ParseErrors::new_from_nonempty(
831 escape_errs.map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e)).into()),
832 )
833 }),
834 Self::Var { var, .. } => Err(self
835 .to_ast_err(ToASTErrorKind::InvalidString(var.to_string()))
836 .into()),
837 Self::Name { name, .. } => Err(self
838 .to_ast_err(ToASTErrorKind::InvalidString(name.to_string()))
839 .into()),
840 Self::Expr { expr, .. } => Err(self
841 .to_ast_err(ToASTErrorKind::InvalidString(expr.to_string()))
842 .into()),
843 }
844 }
845
846 fn into_entity_type(self) -> Result<ast::EntityType> {
847 self.into_name().map(ast::EntityType::from)
848 }
849
850 fn into_name(self) -> Result<ast::Name> {
851 match self {
852 Self::StrLit { lit, .. } => Err(self
853 .to_ast_err(ToASTErrorKind::InvalidIsType(lit.to_string()))
854 .into()),
855 Self::Var { var, .. } => Ok(ast::Name::unqualified_name(var.into())),
856 Self::Name { name, .. } => Ok(name),
857 Self::Expr { ref expr, .. } => Err(self
858 .to_ast_err(ToASTErrorKind::InvalidIsType(expr.to_string()))
859 .into()),
860 }
861 }
862}
863
864impl Node<Option<cst::Expr>> {
865 pub fn to_expr(&self) -> Result<ast::Expr> {
867 self.to_expr_or_special()?.into_expr()
868 }
869 pub(crate) fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
870 let expr = self.try_as_inner()?;
871
872 match &*expr.expr {
873 cst::ExprData::Or(or) => or.to_expr_or_special(),
874 cst::ExprData::If(i, t, e) => {
875 let maybe_guard = i.to_expr();
876 let maybe_then = t.to_expr();
877 let maybe_else = e.to_expr();
878
879 let (i, t, e) = flatten_tuple_3(maybe_guard, maybe_then, maybe_else)?;
880 Ok(ExprOrSpecial::Expr {
881 expr: construct_expr_if(i, t, e, self.loc.clone()),
882 loc: self.loc.clone(),
883 })
884 }
885 }
886 }
887}
888
889impl Node<Option<cst::Or>> {
890 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
891 let or = self.try_as_inner()?;
892
893 let maybe_first = or.initial.to_expr_or_special();
894 let maybe_rest = ParseErrors::transpose(or.extended.iter().map(|i| i.to_expr()));
895
896 let (first, rest) = flatten_tuple_2(maybe_first, maybe_rest)?;
897 let mut rest = rest.into_iter();
898 let second = rest.next();
899 match second {
900 None => Ok(first),
901 Some(second) => first.into_expr().map(|first| ExprOrSpecial::Expr {
902 expr: construct_expr_or(first, second, rest, &self.loc),
903 loc: self.loc.clone(),
904 }),
905 }
906 }
907}
908
909impl Node<Option<cst::And>> {
910 fn to_expr(&self) -> Result<ast::Expr> {
911 self.to_expr_or_special()?.into_expr()
912 }
913 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
914 let and = self.try_as_inner()?;
915
916 let maybe_first = and.initial.to_expr_or_special();
917 let maybe_rest = ParseErrors::transpose(and.extended.iter().map(|i| i.to_expr()));
918
919 let (first, rest) = flatten_tuple_2(maybe_first, maybe_rest)?;
920 let mut rest = rest.into_iter();
921 let second = rest.next();
922 match second {
923 None => Ok(first),
924 Some(second) => first.into_expr().map(|first| ExprOrSpecial::Expr {
925 expr: construct_expr_and(first, second, rest, &self.loc),
926 loc: self.loc.clone(),
927 }),
928 }
929 }
930}
931
932impl Node<Option<cst::Relation>> {
933 fn to_expr(&self) -> Result<ast::Expr> {
934 self.to_expr_or_special()?.into_expr()
935 }
936 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
937 let rel = self.try_as_inner()?;
938
939 match rel {
940 cst::Relation::Common { initial, extended } => {
941 let maybe_first = initial.to_expr_or_special();
942 let maybe_rest = ParseErrors::transpose(
943 extended.iter().map(|(op, i)| i.to_expr().map(|e| (op, e))),
944 );
945 let maybe_extra_elmts = if extended.len() > 1 {
946 Err(self.to_ast_err(ToASTErrorKind::AmbiguousOperators).into())
947 } else {
948 Ok(())
949 };
950 let (first, rest, _) = flatten_tuple_3(maybe_first, maybe_rest, maybe_extra_elmts)?;
951 let mut rest = rest.into_iter();
952 let second = rest.next();
953 match second {
954 None => Ok(first),
955 Some((&op, second)) => first.into_expr().and_then(|first| {
956 Ok(ExprOrSpecial::Expr {
957 expr: construct_expr_rel(first, op, second, self.loc.clone())?,
958 loc: self.loc.clone(),
959 })
960 }),
961 }
962 }
963 cst::Relation::Has { target, field } => {
964 let maybe_target = target.to_expr();
965 let maybe_field = field.to_expr_or_special()?.into_valid_attr();
966 let (target, field) = flatten_tuple_2(maybe_target, maybe_field)?;
967 Ok(ExprOrSpecial::Expr {
968 expr: construct_expr_has(target, field, self.loc.clone()),
969 loc: self.loc.clone(),
970 })
971 }
972 cst::Relation::Like { target, pattern } => {
973 let maybe_target = target.to_expr();
974 let maybe_pattern = pattern.to_expr_or_special()?.into_pattern();
975 let (target, pattern) = flatten_tuple_2(maybe_target, maybe_pattern)?;
976 Ok(ExprOrSpecial::Expr {
977 expr: construct_expr_like(target, pattern, self.loc.clone()),
978 loc: self.loc.clone(),
979 })
980 }
981 cst::Relation::IsIn {
982 target,
983 entity_type,
984 in_entity,
985 } => {
986 let maybe_target = target.to_expr();
987 let maybe_entity_type = entity_type.to_expr_or_special()?.into_entity_type();
988 let (t, n) = flatten_tuple_2(maybe_target, maybe_entity_type)?;
989 match in_entity {
990 Some(in_entity) => {
991 let in_expr = in_entity.to_expr()?;
992 Ok(ExprOrSpecial::Expr {
993 expr: construct_expr_and(
994 construct_expr_is(t.clone(), n, self.loc.clone()),
995 construct_expr_rel(t, cst::RelOp::In, in_expr, self.loc.clone())?,
996 std::iter::empty(),
997 &self.loc,
998 ),
999 loc: self.loc.clone(),
1000 })
1001 }
1002 None => Ok(ExprOrSpecial::Expr {
1003 expr: construct_expr_is(t, n, self.loc.clone()),
1004 loc: self.loc.clone(),
1005 }),
1006 }
1007 }
1008 }
1009 }
1010}
1011
1012impl Node<Option<cst::Add>> {
1013 fn to_expr(&self) -> Result<ast::Expr> {
1014 self.to_expr_or_special()?.into_expr()
1015 }
1016 pub(crate) fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
1017 let add = self.try_as_inner()?;
1018
1019 let maybe_first = add.initial.to_expr_or_special();
1020 let maybe_rest = ParseErrors::transpose(
1021 add.extended
1022 .iter()
1023 .map(|&(op, ref i)| i.to_expr().map(|e| (op, e))),
1024 );
1025 let (first, rest) = flatten_tuple_2(maybe_first, maybe_rest)?;
1026 if !rest.is_empty() {
1027 let first = first.into_expr()?;
1029 Ok(ExprOrSpecial::Expr {
1030 expr: construct_expr_add(first, rest, &self.loc),
1031 loc: self.loc.clone(),
1032 })
1033 } else {
1034 Ok(first)
1035 }
1036 }
1037}
1038
1039impl Node<Option<cst::Mult>> {
1040 fn to_expr(&self) -> Result<ast::Expr> {
1041 self.to_expr_or_special()?.into_expr()
1042 }
1043 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
1044 let mult = self.try_as_inner()?;
1045
1046 let maybe_first = mult.initial.to_expr_or_special();
1047 let maybe_rest = ParseErrors::transpose(mult.extended.iter().map(|&(op, ref i)| {
1048 i.to_expr().and_then(|e| match op {
1049 cst::MultOp::Times => Ok(e),
1050 cst::MultOp::Divide => {
1051 Err(self.to_ast_err(ToASTErrorKind::UnsupportedDivision).into())
1052 }
1053 cst::MultOp::Mod => Err(self.to_ast_err(ToASTErrorKind::UnsupportedModulo).into()),
1054 })
1055 }));
1056
1057 let (first, rest) = flatten_tuple_2(maybe_first, maybe_rest)?;
1058 if !rest.is_empty() {
1059 let first = first.into_expr()?;
1061 Ok(ExprOrSpecial::Expr {
1062 expr: construct_expr_mul(first, rest, &self.loc),
1063 loc: self.loc.clone(),
1064 })
1065 } else {
1066 Ok(first)
1067 }
1068 }
1069}
1070
1071impl Node<Option<cst::Unary>> {
1072 fn to_expr(&self) -> Result<ast::Expr> {
1073 self.to_expr_or_special()?.into_expr()
1074 }
1075 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
1076 let unary = self.try_as_inner()?;
1077
1078 match unary.op {
1079 None => unary.item.to_expr_or_special(),
1080 Some(cst::NegOp::Bang(n)) => {
1081 (0..n).fold(unary.item.to_expr_or_special(), |inner, _| {
1082 inner
1083 .and_then(|e| e.into_expr())
1084 .map(|expr| ExprOrSpecial::Expr {
1085 expr: construct_expr_not(expr, self.loc.clone()),
1086 loc: self.loc.clone(),
1087 })
1088 })
1089 }
1090 Some(cst::NegOp::Dash(0)) => unary.item.to_expr_or_special(),
1091 Some(cst::NegOp::Dash(c)) => {
1092 let (last, rc) = if let Some(cst::Literal::Num(n)) = unary.item.to_lit() {
1098 match n.cmp(&(i64::MAX as u64 + 1)) {
1099 Ordering::Equal => (
1100 Ok(construct_expr_num(i64::MIN, unary.item.loc.clone())),
1101 c - 1,
1102 ),
1103 Ordering::Less => (
1104 Ok(construct_expr_num(-(*n as i64), unary.item.loc.clone())),
1105 c - 1,
1106 ),
1107 Ordering::Greater => (
1108 Err(self
1109 .to_ast_err(ToASTErrorKind::IntegerLiteralTooLarge(*n))
1110 .into()),
1111 0,
1112 ),
1113 }
1114 } else {
1115 (
1118 unary.item.to_expr_or_special().and_then(|i| i.into_expr()),
1119 c,
1120 )
1121 };
1122 (0..rc)
1124 .fold(last, |r, _| {
1125 r.map(|e| (construct_expr_neg(e, self.loc.clone())))
1126 })
1127 .map(|expr| ExprOrSpecial::Expr {
1128 expr,
1129 loc: self.loc.clone(),
1130 })
1131 }
1132 Some(cst::NegOp::OverBang) => Err(self
1133 .to_ast_err(ToASTErrorKind::UnaryOpLimit(ast::UnaryOp::Not))
1134 .into()),
1135 Some(cst::NegOp::OverDash) => Err(self
1136 .to_ast_err(ToASTErrorKind::UnaryOpLimit(ast::UnaryOp::Neg))
1137 .into()),
1138 }
1139 }
1140}
1141
1142enum AstAccessor {
1144 Field(ast::UnreservedId),
1145 Call(Vec<ast::Expr>),
1146 Index(SmolStr),
1147}
1148
1149impl Node<Option<cst::Member>> {
1150 pub fn to_lit(&self) -> Option<&cst::Literal> {
1155 let m = self.as_ref().node.as_ref()?;
1156 if !m.access.is_empty() {
1157 return None;
1158 }
1159 match m.item.as_ref().node.as_ref()? {
1160 cst::Primary::Literal(lit) => lit.as_ref().node.as_ref(),
1161 _ => None,
1162 }
1163 }
1164
1165 fn build_expr_accessor<'a>(
1177 &self,
1178 head: ast::Expr,
1179 next: &mut AstAccessor,
1180 tail: &'a mut [AstAccessor],
1181 ) -> Result<(ast::Expr, &'a mut [AstAccessor])> {
1182 use AstAccessor::*;
1183 match (next, tail) {
1184 (Call(_), _) => Err(self.to_ast_err(ToASTErrorKind::ExpressionCall).into()),
1186
1187 (Field(id), [Call(args), rest @ ..]) => {
1189 let args = std::mem::take(args);
1191 let id = mem::replace(id, ast::UnreservedId::empty());
1193 Ok((id.to_meth(head, args, &self.loc)?, rest))
1194 }
1195
1196 (Field(id), rest) => {
1198 let id = mem::replace(id, ast::UnreservedId::empty());
1199 Ok((
1200 construct_expr_attr(head, id.to_smolstr(), self.loc.clone()),
1201 rest,
1202 ))
1203 }
1204
1205 (Index(i), rest) => {
1207 let i = mem::take(i);
1208 Ok((construct_expr_attr(head, i, self.loc.clone()), rest))
1209 }
1210 }
1211 }
1212
1213 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
1214 let mem = self.try_as_inner()?;
1215
1216 let maybe_prim = mem.item.to_expr_or_special();
1217 let maybe_accessors = ParseErrors::transpose(mem.access.iter().map(|a| a.to_access()));
1218
1219 let (prim, mut accessors) = flatten_tuple_2(maybe_prim, maybe_accessors)?;
1221
1222 let (mut head, mut tail) = {
1223 use AstAccessor::*;
1224 use ExprOrSpecial::*;
1225 match (prim, accessors.as_mut_slice()) {
1226 (prim, []) => return Ok(prim),
1228
1229 (Expr { expr, .. }, [next, rest @ ..]) => {
1233 self.build_expr_accessor(expr, next, rest)?
1234 }
1235 (StrLit { lit, loc }, [next, rest @ ..]) => {
1237 let str_lit_expr = match to_unescaped_string(lit) {
1238 Ok(s) => construct_expr_string(s, loc.clone()),
1239 Err(escape_errs) => {
1240 return Err(ParseErrors::new_from_nonempty(
1241 escape_errs
1242 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e)).into()),
1243 ))
1244 }
1245 };
1246 self.build_expr_accessor(str_lit_expr, next, rest)?
1247 }
1248
1249 (Name { name, .. }, [Call(args), rest @ ..]) => {
1251 let args = std::mem::take(args);
1253 (name.into_func(args, self.loc.clone())?, rest)
1254 }
1255 (Var { var, .. }, [Call(_), ..]) => {
1257 return Err(self.to_ast_err(ToASTErrorKind::VariableCall(var)).into());
1258 }
1259
1260 (Name { name, .. }, [Field(f), Call(_), ..]) => {
1262 return Err(self
1263 .to_ast_err(ToASTErrorKind::NoMethods(name.clone(), f.clone()))
1264 .into());
1265 }
1266 (Var { var, loc: var_loc }, [Field(id), Call(args), rest @ ..]) => {
1268 let args = std::mem::take(args);
1269 let id = mem::replace(id, ast::UnreservedId::empty());
1271 (
1272 id.to_meth(construct_expr_var(var, var_loc.clone()), args, &self.loc)?,
1273 rest,
1274 )
1275 }
1276
1277 (Var { var, loc: var_loc }, [Field(i), rest @ ..]) => {
1279 let id = mem::replace(i, ast::UnreservedId::empty());
1280 (
1281 construct_expr_attr(
1282 construct_expr_var(var, var_loc.clone()),
1283 id.to_smolstr(),
1284 self.loc.clone(),
1285 ),
1286 rest,
1287 )
1288 }
1289 (Name { name, .. }, [Field(f), ..]) => {
1291 return Err(self
1292 .to_ast_err(ToASTErrorKind::InvalidAccess(
1293 name.clone(),
1294 f.to_string().into(),
1295 ))
1296 .into());
1297 }
1298 (Name { name, .. }, [Index(i), ..]) => {
1300 return Err(self
1301 .to_ast_err(ToASTErrorKind::InvalidIndex(name.clone(), i.clone()))
1302 .into());
1303 }
1304
1305 (Var { var, loc: var_loc }, [Index(i), rest @ ..]) => {
1307 let i = mem::take(i);
1308 (
1309 construct_expr_attr(
1310 construct_expr_var(var, var_loc.clone()),
1311 i,
1312 self.loc.clone(),
1313 ),
1314 rest,
1315 )
1316 }
1317 }
1318 };
1319
1320 while let [next, rest @ ..] = tail {
1325 (head, tail) = self.build_expr_accessor(head, next, rest)?;
1326 }
1327 Ok(ExprOrSpecial::Expr {
1328 expr: head,
1329 loc: self.loc.clone(),
1330 })
1331 }
1332}
1333
1334impl Node<Option<cst::MemAccess>> {
1335 fn to_access(&self) -> Result<AstAccessor> {
1336 let acc = self.try_as_inner()?;
1337
1338 match acc {
1339 cst::MemAccess::Field(i) => {
1340 let maybe_ident = i.to_unreserved_ident();
1341 maybe_ident.map(AstAccessor::Field)
1342 }
1343 cst::MemAccess::Call(args) => {
1344 let maybe_args = ParseErrors::transpose(args.iter().map(|e| e.to_expr()));
1345 maybe_args.map(AstAccessor::Call)
1346 }
1347 cst::MemAccess::Index(index) => {
1348 let maybe_index = index.to_expr_or_special()?.into_string_literal();
1349 maybe_index.map(AstAccessor::Index)
1350 }
1351 }
1352 }
1353}
1354
1355impl Node<Option<cst::Primary>> {
1356 pub(crate) fn to_expr(&self) -> Result<ast::Expr> {
1357 self.to_expr_or_special()?.into_expr()
1358 }
1359 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
1360 let prim = self.try_as_inner()?;
1361
1362 match prim {
1363 cst::Primary::Literal(lit) => lit.to_expr_or_special(),
1364 cst::Primary::Ref(r) => r.to_expr().map(|expr| ExprOrSpecial::Expr {
1365 expr,
1366 loc: r.loc.clone(),
1367 }),
1368 cst::Primary::Slot(s) => s.clone().into_expr().map(|expr| ExprOrSpecial::Expr {
1369 expr,
1370 loc: s.loc.clone(),
1371 }),
1372 #[allow(clippy::manual_map)]
1373 cst::Primary::Name(n) => {
1374 if let Some(var) = n.maybe_to_var() {
1376 Ok(ExprOrSpecial::Var {
1377 var,
1378 loc: self.loc.clone(),
1379 })
1380 } else {
1381 n.to_internal_name().and_then(|name| match name.try_into() {
1382 Ok(name) => Ok(ExprOrSpecial::Name {
1383 name,
1384 loc: self.loc.clone(),
1385 }),
1386 Err(err) => Err(ParseErrors::singleton(err)),
1387 })
1388 }
1389 }
1390 cst::Primary::Expr(e) => e.to_expr().map(|expr| ExprOrSpecial::Expr {
1391 expr,
1392 loc: e.loc.clone(),
1393 }),
1394 cst::Primary::EList(es) => {
1395 let maybe_list = ParseErrors::transpose(es.iter().map(|e| e.to_expr()));
1396 maybe_list.map(|list| ExprOrSpecial::Expr {
1397 expr: construct_expr_set(list, self.loc.clone()),
1398 loc: self.loc.clone(),
1399 })
1400 }
1401 cst::Primary::RInits(is) => {
1402 let rec = ParseErrors::transpose(is.iter().map(|i| i.to_init()))?;
1403 let expr = construct_expr_record(rec, self.loc.clone())?;
1404 Ok(ExprOrSpecial::Expr {
1405 expr,
1406 loc: self.loc.clone(),
1407 })
1408 }
1409 }
1410 }
1411
1412 pub fn to_string_literal(&self) -> Result<SmolStr> {
1414 let prim = self.try_as_inner()?;
1415
1416 match prim {
1417 cst::Primary::Literal(lit) => lit.to_expr_or_special()?.into_string_literal(),
1418 _ => Err(self
1419 .to_ast_err(ToASTErrorKind::InvalidString(prim.to_string()))
1420 .into()),
1421 }
1422 }
1423}
1424
1425impl Node<Option<cst::Slot>> {
1426 fn into_expr(self) -> Result<ast::Expr> {
1427 match self.try_as_inner()?.try_into() {
1428 Ok(slot_id) => Ok(ast::ExprBuilder::new()
1429 .with_source_loc(self.loc)
1430 .slot(slot_id)),
1431 Err(e) => Err(self.to_ast_err(e).into()),
1432 }
1433 }
1434}
1435
1436impl TryFrom<&cst::Slot> for ast::SlotId {
1437 type Error = ToASTErrorKind;
1438
1439 fn try_from(slot: &cst::Slot) -> std::result::Result<Self, Self::Error> {
1440 match slot {
1441 cst::Slot::Principal => Ok(ast::SlotId::principal()),
1442 cst::Slot::Resource => Ok(ast::SlotId::resource()),
1443 cst::Slot::Other(slot) => Err(ToASTErrorKind::InvalidSlot(slot.clone())),
1444 }
1445 }
1446}
1447
1448impl From<ast::SlotId> for cst::Slot {
1449 fn from(slot: ast::SlotId) -> cst::Slot {
1450 match slot {
1451 ast::SlotId(ast::ValidSlotId::Principal) => cst::Slot::Principal,
1452 ast::SlotId(ast::ValidSlotId::Resource) => cst::Slot::Resource,
1453 }
1454 }
1455}
1456
1457impl Node<Option<cst::Name>> {
1458 fn to_type_constraint(&self) -> Result<ast::Expr> {
1460 match self.as_inner() {
1461 Some(_) => Err(self.to_ast_err(ToASTErrorKind::TypeConstraints).into()),
1462 None => Ok(construct_expr_bool(true, self.loc.clone())),
1463 }
1464 }
1465
1466 pub(crate) fn to_name(&self) -> Result<ast::Name> {
1467 self.to_internal_name()
1468 .and_then(|n| n.try_into().map_err(ParseErrors::singleton))
1469 }
1470
1471 pub(crate) fn to_internal_name(&self) -> Result<ast::InternalName> {
1472 let name = self.try_as_inner()?;
1473
1474 let maybe_path = ParseErrors::transpose(name.path.iter().map(|i| i.to_valid_ident()));
1475 let maybe_name = name.name.to_valid_ident();
1476
1477 let (name, path) = flatten_tuple_2(maybe_name, maybe_path)?;
1479 Ok(construct_name(path, name, self.loc.clone()))
1480 }
1481
1482 fn maybe_to_var(&self) -> Option<ast::Var> {
1485 let name = self.as_inner()?;
1486
1487 let ident = match ParseErrors::transpose(name.path.iter().map(|id| id.to_valid_ident())) {
1488 Ok(path) => {
1489 if !path.is_empty() {
1490 None
1492 } else {
1493 name.name.as_inner()
1494 }
1495 }
1496 Err(_) => None,
1497 }?;
1498
1499 match ident {
1500 cst::Ident::Principal => Some(ast::Var::Principal),
1501 cst::Ident::Action => Some(ast::Var::Action),
1502 cst::Ident::Resource => Some(ast::Var::Resource),
1503 cst::Ident::Context => Some(ast::Var::Context),
1504 _ => None,
1505 }
1506 }
1507}
1508
1509pub(crate) fn is_known_extension_func_name(name: &ast::Name) -> bool {
1511 EXTENSION_STYLES.functions.contains(name)
1512 || (name.0.path.is_empty() && EXTENSION_STYLES.methods.contains(&name.basename()))
1513}
1514
1515pub(crate) fn is_known_extension_func_str(s: &SmolStr) -> bool {
1520 EXTENSION_STYLES.functions_and_methods_as_str.contains(s)
1521}
1522
1523impl ast::Name {
1524 fn into_valid_attr(self, loc: Loc) -> Result<SmolStr> {
1526 if !self.0.path.is_empty() {
1527 Err(ToASTError::new(ToASTErrorKind::PathAsAttribute(self.to_string()), loc).into())
1528 } else {
1529 Ok(self.0.id.into_smolstr())
1530 }
1531 }
1532
1533 fn into_func(self, args: Vec<ast::Expr>, loc: Loc) -> Result<ast::Expr> {
1534 if self.0.path.is_empty() {
1536 let id = self.basename();
1537 if EXTENSION_STYLES.methods.contains(&id)
1538 || matches!(
1539 id.as_ref(),
1540 "contains" | "containsAll" | "containsAny" | "getTag" | "hasTag"
1541 )
1542 {
1543 return Err(ToASTError::new(
1544 ToASTErrorKind::FunctionCallOnMethod(self.basename()),
1545 loc,
1546 )
1547 .into());
1548 }
1549 }
1550 if EXTENSION_STYLES.functions.contains(&self) {
1551 Ok(construct_ext_func(self, args, loc))
1552 } else {
1553 fn suggest_function(name: &ast::Name, funs: &HashSet<&ast::Name>) -> Option<String> {
1554 const SUGGEST_FUNCTION_MAX_DISTANCE: usize = 3;
1555 let fnames = funs.iter().map(ToString::to_string).collect::<Vec<_>>();
1556 let suggested_function = fuzzy_search_limited(
1557 &name.to_string(),
1558 fnames.as_slice(),
1559 Some(SUGGEST_FUNCTION_MAX_DISTANCE),
1560 );
1561 suggested_function.map(|f| format!("did you mean `{f}`?"))
1562 }
1563 let hint = suggest_function(&self, &EXTENSION_STYLES.functions);
1564 Err(ToASTError::new(ToASTErrorKind::UnknownFunction { id: self, hint }, loc).into())
1565 }
1566 }
1567}
1568
1569impl Node<Option<cst::Ref>> {
1570 pub fn to_ref(&self) -> Result<ast::EntityUID> {
1572 let refr = self.try_as_inner()?;
1573
1574 match refr {
1575 cst::Ref::Uid { path, eid } => {
1576 let maybe_path = path.to_name().map(ast::EntityType::from);
1577 let maybe_eid = eid.as_valid_string().and_then(|s| {
1578 to_unescaped_string(s).map_err(|escape_errs| {
1579 ParseErrors::new_from_nonempty(
1580 escape_errs
1581 .map(|e| self.to_ast_err(ToASTErrorKind::Unescape(e)).into()),
1582 )
1583 })
1584 });
1585
1586 let (p, e) = flatten_tuple_2(maybe_path, maybe_eid)?;
1587 Ok(construct_refr(p, e, self.loc.clone()))
1588 }
1589 r @ cst::Ref::Ref { .. } => Err(self
1590 .to_ast_err(ToASTErrorKind::InvalidEntityLiteral(r.to_string()))
1591 .into()),
1592 }
1593 }
1594 fn to_expr(&self) -> Result<ast::Expr> {
1595 self.to_ref()
1596 .map(|euid| construct_expr_ref(euid, self.loc.clone()))
1597 }
1598}
1599
1600impl Node<Option<cst::Literal>> {
1601 fn to_expr_or_special(&self) -> Result<ExprOrSpecial<'_>> {
1602 let lit = self.try_as_inner()?;
1603
1604 match lit {
1605 cst::Literal::True => Ok(ExprOrSpecial::Expr {
1606 expr: construct_expr_bool(true, self.loc.clone()),
1607 loc: self.loc.clone(),
1608 }),
1609 cst::Literal::False => Ok(ExprOrSpecial::Expr {
1610 expr: construct_expr_bool(false, self.loc.clone()),
1611 loc: self.loc.clone(),
1612 }),
1613 cst::Literal::Num(n) => match Integer::try_from(*n) {
1614 Ok(i) => Ok(ExprOrSpecial::Expr {
1615 expr: construct_expr_num(i, self.loc.clone()),
1616 loc: self.loc.clone(),
1617 }),
1618 Err(_) => Err(self
1619 .to_ast_err(ToASTErrorKind::IntegerLiteralTooLarge(*n))
1620 .into()),
1621 },
1622 cst::Literal::Str(s) => {
1623 let maybe_str = s.as_valid_string();
1624 maybe_str.map(|lit| ExprOrSpecial::StrLit {
1625 lit,
1626 loc: self.loc.clone(),
1627 })
1628 }
1629 }
1630 }
1631}
1632
1633impl Node<Option<cst::RecInit>> {
1634 fn to_init(&self) -> Result<(SmolStr, ast::Expr)> {
1635 let lit = self.try_as_inner()?;
1636
1637 let maybe_attr = lit.0.to_expr_or_special()?.into_valid_attr();
1638 let maybe_value = lit.1.to_expr();
1639
1640 flatten_tuple_2(maybe_attr, maybe_value)
1641 }
1642}
1643
1644#[allow(clippy::too_many_arguments)]
1647fn construct_template_policy(
1648 id: ast::PolicyID,
1649 annotations: ast::Annotations,
1650 effect: ast::Effect,
1651 principal: ast::PrincipalConstraint,
1652 action: ast::ActionConstraint,
1653 resource: ast::ResourceConstraint,
1654 conds: Vec<ast::Expr>,
1655 loc: &Loc,
1656) -> ast::Template {
1657 let construct_template = |non_scope_constraint| {
1658 ast::Template::new(
1659 id,
1660 Some(loc.clone()),
1661 annotations,
1662 effect,
1663 principal,
1664 action,
1665 resource,
1666 non_scope_constraint,
1667 )
1668 };
1669 let mut conds_iter = conds.into_iter();
1670 if let Some(first_expr) = conds_iter.next() {
1671 construct_template(match conds_iter.next() {
1674 Some(e) => construct_expr_and(first_expr, e, conds_iter, loc),
1675 None => first_expr,
1676 })
1677 } else {
1678 construct_template(construct_expr_bool(true, loc.clone()))
1680 }
1681}
1682fn construct_string_from_var(v: ast::Var) -> SmolStr {
1683 match v {
1684 ast::Var::Principal => "principal".into(),
1685 ast::Var::Action => "action".into(),
1686 ast::Var::Resource => "resource".into(),
1687 ast::Var::Context => "context".into(),
1688 }
1689}
1690fn construct_name(path: Vec<ast::Id>, id: ast::Id, loc: Loc) -> ast::InternalName {
1691 ast::InternalName {
1692 id,
1693 path: Arc::new(path),
1694 loc: Some(loc),
1695 }
1696}
1697fn construct_refr(p: ast::EntityType, n: SmolStr, loc: Loc) -> ast::EntityUID {
1698 ast::EntityUID::from_components(p, ast::Eid::new(n), Some(loc))
1699}
1700fn construct_expr_ref(r: ast::EntityUID, loc: Loc) -> ast::Expr {
1701 ast::ExprBuilder::new().with_source_loc(loc).val(r)
1702}
1703fn construct_expr_num(n: Integer, loc: Loc) -> ast::Expr {
1704 ast::ExprBuilder::new().with_source_loc(loc).val(n)
1705}
1706fn construct_expr_string(s: SmolStr, loc: Loc) -> ast::Expr {
1707 ast::ExprBuilder::new().with_source_loc(loc).val(s)
1708}
1709fn construct_expr_bool(b: bool, loc: Loc) -> ast::Expr {
1710 ast::ExprBuilder::new().with_source_loc(loc).val(b)
1711}
1712fn construct_expr_neg(e: ast::Expr, loc: Loc) -> ast::Expr {
1713 ast::ExprBuilder::new().with_source_loc(loc).neg(e)
1714}
1715fn construct_expr_not(e: ast::Expr, loc: Loc) -> ast::Expr {
1716 ast::ExprBuilder::new().with_source_loc(loc).not(e)
1717}
1718fn construct_expr_var(v: ast::Var, loc: Loc) -> ast::Expr {
1719 ast::ExprBuilder::new().with_source_loc(loc).var(v)
1720}
1721fn construct_expr_if(i: ast::Expr, t: ast::Expr, e: ast::Expr, loc: Loc) -> ast::Expr {
1722 ast::ExprBuilder::new().with_source_loc(loc).ite(i, t, e)
1723}
1724fn construct_expr_or(
1725 f: ast::Expr,
1726 s: ast::Expr,
1727 chained: impl IntoIterator<Item = ast::Expr>,
1728 loc: &Loc,
1729) -> ast::Expr {
1730 let first = ast::ExprBuilder::new()
1731 .with_source_loc(loc.clone())
1732 .or(f, s);
1733 chained.into_iter().fold(first, |a, n| {
1734 ast::ExprBuilder::new()
1735 .with_source_loc(loc.clone())
1736 .or(a, n)
1737 })
1738}
1739fn construct_expr_and(
1740 f: ast::Expr,
1741 s: ast::Expr,
1742 chained: impl IntoIterator<Item = ast::Expr>,
1743 loc: &Loc,
1744) -> ast::Expr {
1745 let first = ast::ExprBuilder::new()
1746 .with_source_loc(loc.clone())
1747 .and(f, s);
1748 chained.into_iter().fold(first, |a, n| {
1749 ast::ExprBuilder::new()
1750 .with_source_loc(loc.clone())
1751 .and(a, n)
1752 })
1753}
1754fn construct_expr_rel(f: ast::Expr, rel: cst::RelOp, s: ast::Expr, loc: Loc) -> Result<ast::Expr> {
1755 let builder = ast::ExprBuilder::new().with_source_loc(loc.clone());
1756 match rel {
1757 cst::RelOp::Less => Ok(builder.less(f, s)),
1758 cst::RelOp::LessEq => Ok(builder.lesseq(f, s)),
1759 cst::RelOp::GreaterEq => Ok(builder.greatereq(f, s)),
1760 cst::RelOp::Greater => Ok(builder.greater(f, s)),
1761 cst::RelOp::NotEq => Ok(builder.noteq(f, s)),
1762 cst::RelOp::Eq => Ok(builder.is_eq(f, s)),
1763 cst::RelOp::In => Ok(builder.is_in(f, s)),
1764 cst::RelOp::InvalidSingleEq => {
1765 Err(ToASTError::new(ToASTErrorKind::InvalidSingleEq, loc).into())
1766 }
1767 }
1768}
1769fn construct_expr_add(
1771 f: ast::Expr,
1772 chained: impl IntoIterator<Item = (cst::AddOp, ast::Expr)>,
1773 loc: &Loc,
1774) -> ast::Expr {
1775 let mut expr = f;
1776 for (op, next_expr) in chained {
1777 let builder = ast::ExprBuilder::new().with_source_loc(loc.clone());
1778 expr = match op {
1779 cst::AddOp::Plus => builder.add(expr, next_expr),
1780 cst::AddOp::Minus => builder.sub(expr, next_expr),
1781 };
1782 }
1783 expr
1784}
1785fn construct_expr_mul(
1787 f: ast::Expr,
1788 chained: impl IntoIterator<Item = ast::Expr>,
1789 loc: &Loc,
1790) -> ast::Expr {
1791 let mut expr = f;
1792 for next_expr in chained {
1793 expr = ast::ExprBuilder::new()
1794 .with_source_loc(loc.clone())
1795 .mul(expr, next_expr);
1796 }
1797 expr
1798}
1799fn construct_expr_has(t: ast::Expr, s: SmolStr, loc: Loc) -> ast::Expr {
1800 ast::ExprBuilder::new().with_source_loc(loc).has_attr(t, s)
1801}
1802fn construct_expr_attr(e: ast::Expr, s: SmolStr, loc: Loc) -> ast::Expr {
1803 ast::ExprBuilder::new().with_source_loc(loc).get_attr(e, s)
1804}
1805fn construct_expr_like(e: ast::Expr, s: Vec<PatternElem>, loc: Loc) -> ast::Expr {
1806 ast::ExprBuilder::new().with_source_loc(loc).like(e, s)
1807}
1808fn construct_expr_is(e: ast::Expr, n: ast::EntityType, loc: Loc) -> ast::Expr {
1809 ast::ExprBuilder::new()
1810 .with_source_loc(loc)
1811 .is_entity_type(e, n)
1812}
1813fn construct_ext_func(name: ast::Name, args: Vec<ast::Expr>, loc: Loc) -> ast::Expr {
1814 ast::ExprBuilder::new()
1816 .with_source_loc(loc)
1817 .call_extension_fn(name, args)
1818}
1819
1820fn construct_method_contains(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
1821 ast::ExprBuilder::new()
1822 .with_source_loc(loc)
1823 .contains(e0, e1)
1824}
1825fn construct_method_contains_all(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
1826 ast::ExprBuilder::new()
1827 .with_source_loc(loc)
1828 .contains_all(e0, e1)
1829}
1830fn construct_method_contains_any(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
1831 ast::ExprBuilder::new()
1832 .with_source_loc(loc)
1833 .contains_any(e0, e1)
1834}
1835fn construct_method_get_tag(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
1836 ast::ExprBuilder::new().with_source_loc(loc).get_tag(e0, e1)
1837}
1838fn construct_method_has_tag(e0: ast::Expr, e1: ast::Expr, loc: Loc) -> ast::Expr {
1839 ast::ExprBuilder::new().with_source_loc(loc).has_tag(e0, e1)
1840}
1841
1842fn construct_ext_meth(n: UnreservedId, args: NonEmpty<ast::Expr>, loc: Loc) -> ast::Expr {
1843 let name = ast::Name::unqualified_name(n);
1844 ast::ExprBuilder::new()
1846 .with_source_loc(loc)
1847 .call_extension_fn(name, args)
1848}
1849fn construct_expr_set(s: Vec<ast::Expr>, loc: Loc) -> ast::Expr {
1850 ast::ExprBuilder::new().with_source_loc(loc).set(s)
1851}
1852fn construct_expr_record(kvs: Vec<(SmolStr, ast::Expr)>, loc: Loc) -> Result<ast::Expr> {
1853 ast::ExprBuilder::new()
1854 .with_source_loc(loc.clone())
1855 .record(kvs)
1856 .map_err(|e| ToASTError::new(e.into(), loc).into())
1857}
1858
1859#[allow(clippy::panic)]
1861#[allow(clippy::indexing_slicing)]
1863#[cfg(test)]
1864mod tests {
1865 use super::*;
1866 use crate::{
1867 ast::{EntityUID, Expr},
1868 parser::{err::ParseErrors, test_utils::*, *},
1869 test_utils::*,
1870 };
1871 use ast::{InternalName, ReservedNameError};
1872 use cool_asserts::assert_matches;
1873
1874 #[track_caller]
1875 fn assert_parse_expr_succeeds(text: &str) -> Expr {
1876 text_to_cst::parse_expr(text)
1877 .expect("failed parser")
1878 .to_expr()
1879 .unwrap_or_else(|errs| {
1880 panic!("failed conversion to AST:\n{:?}", miette::Report::new(errs))
1881 })
1882 }
1883
1884 #[track_caller]
1885 fn assert_parse_expr_fails(text: &str) -> ParseErrors {
1886 let result = text_to_cst::parse_expr(text)
1887 .expect("failed parser")
1888 .to_expr();
1889 match result {
1890 Ok(expr) => {
1891 panic!("conversion to AST should have failed, but succeeded with:\n{expr}")
1892 }
1893 Err(errs) => errs,
1894 }
1895 }
1896
1897 #[track_caller]
1898 fn assert_parse_policy_succeeds(text: &str) -> ast::StaticPolicy {
1899 text_to_cst::parse_policy(text)
1900 .expect("failed parser")
1901 .to_policy(ast::PolicyID::from_string("id"))
1902 .unwrap_or_else(|errs| {
1903 panic!("failed conversion to AST:\n{:?}", miette::Report::new(errs))
1904 })
1905 }
1906
1907 #[track_caller]
1908 fn assert_parse_policy_fails(text: &str) -> ParseErrors {
1909 let result = text_to_cst::parse_policy(text)
1910 .expect("failed parser")
1911 .to_policy(ast::PolicyID::from_string("id"));
1912 match result {
1913 Ok(policy) => {
1914 panic!("conversion to AST should have failed, but succeeded with:\n{policy}")
1915 }
1916 Err(errs) => errs,
1917 }
1918 }
1919
1920 #[test]
1921 fn show_expr1() {
1922 assert_parse_expr_succeeds(
1923 r#"
1924 if 7 then 6 > 5 else !5 || "thursday" && ((8) >= "fish")
1925 "#,
1926 );
1927 }
1928
1929 #[test]
1930 fn show_expr2() {
1931 assert_parse_expr_succeeds(
1932 r#"
1933 [2,3,4].foo["hello"]
1934 "#,
1935 );
1936 }
1937
1938 #[test]
1939 fn show_expr3() {
1940 let expr = assert_parse_expr_succeeds(
1942 r#"
1943 "first".some_ident
1944 "#,
1945 );
1946 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
1947 assert_eq!(attr, "some_ident");
1948 });
1949 }
1950
1951 #[test]
1952 fn show_expr4() {
1953 let expr = assert_parse_expr_succeeds(
1954 r#"
1955 1.some_ident
1956 "#,
1957 );
1958 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
1959 assert_eq!(attr, "some_ident");
1960 });
1961 }
1962
1963 #[test]
1964 fn show_expr5() {
1965 let expr = assert_parse_expr_succeeds(
1966 r#"
1967 "first"["some string"]
1968 "#,
1969 );
1970 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
1971 assert_eq!(attr, "some string");
1972 });
1973 }
1974
1975 #[test]
1976 fn show_expr6() {
1977 let expr = assert_parse_expr_succeeds(
1978 r#"
1979 {"one":1,"two":2} has one
1980 "#,
1981 );
1982 assert_matches!(expr.expr_kind(), ast::ExprKind::HasAttr { attr, .. } => {
1983 assert_eq!(attr, "one");
1984 });
1985 }
1986
1987 #[test]
1988 fn show_expr7() {
1989 let expr = assert_parse_expr_succeeds(
1990 r#"
1991 {"one":1,"two":2}.one
1992 "#,
1993 );
1994 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
1995 assert_eq!(attr, "one");
1996 });
1997 }
1998
1999 #[test]
2000 fn show_expr8() {
2001 let expr = assert_parse_expr_succeeds(
2003 r#"
2004 {"one":1,"two":2}["one"]
2005 "#,
2006 );
2007 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
2008 assert_eq!(attr, "one");
2009 });
2010 }
2011
2012 #[test]
2013 fn show_expr9() {
2014 let expr = assert_parse_expr_succeeds(
2016 r#"
2017 {"this is a valid map key+.-_%()":1,"two":2}["this is a valid map key+.-_%()"]
2018 "#,
2019 );
2020 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
2021 assert_eq!(attr, "this is a valid map key+.-_%()");
2022 });
2023 }
2024
2025 #[test]
2026 fn show_expr10() {
2027 let src = r#"
2028 {if true then a else b:"b"} ||
2029 {if false then a else b:"b"}
2030 "#;
2031 let errs = assert_parse_expr_fails(src);
2032 expect_n_errors(src, &errs, 4);
2033 expect_some_error_matches(
2034 src,
2035 &errs,
2036 &ExpectedErrorMessageBuilder::error("invalid variable: a")
2037 .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `a` in quotes to make a string?")
2038 .exactly_one_underline("a")
2039 .build(),
2040 );
2041 expect_some_error_matches(
2042 src,
2043 &errs,
2044 &ExpectedErrorMessageBuilder::error("invalid variable: b")
2045 .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `b` in quotes to make a string?")
2046 .exactly_one_underline("b")
2047 .build(),
2048 );
2049 }
2050
2051 #[test]
2052 fn show_expr11() {
2053 let expr = assert_parse_expr_succeeds(
2054 r#"
2055 {principal:"principal"}
2056 "#,
2057 );
2058 assert_matches!(expr.expr_kind(), ast::ExprKind::Record { .. });
2059 }
2060
2061 #[test]
2062 fn show_expr12() {
2063 let expr = assert_parse_expr_succeeds(
2064 r#"
2065 {"principal":"principal"}
2066 "#,
2067 );
2068 assert_matches!(expr.expr_kind(), ast::ExprKind::Record { .. });
2069 }
2070
2071 #[test]
2072 fn reserved_idents1() {
2073 let src = r#"
2074 The::true::path::to::"enlightenment".false
2075 "#;
2076 let errs = assert_parse_expr_fails(src);
2077 expect_n_errors(src, &errs, 2);
2078 expect_some_error_matches(
2079 src,
2080 &errs,
2081 &ExpectedErrorMessageBuilder::error(
2082 "this identifier is reserved and cannot be used: true",
2083 )
2084 .exactly_one_underline("true")
2085 .build(),
2086 );
2087 expect_some_error_matches(
2088 src,
2089 &errs,
2090 &ExpectedErrorMessageBuilder::error(
2091 "this identifier is reserved and cannot be used: false",
2092 )
2093 .exactly_one_underline("false")
2094 .build(),
2095 );
2096 }
2097
2098 #[test]
2099 fn reserved_idents2() {
2100 let src = r#"
2101 if {if: true}.if then {"if":false}["if"] else {when:true}.permit
2102 "#;
2103 let errs = assert_parse_expr_fails(src);
2104 expect_n_errors(src, &errs, 2);
2105 expect_some_error_matches(
2106 src,
2107 &errs,
2108 &ExpectedErrorMessageBuilder::error(
2109 "this identifier is reserved and cannot be used: if",
2110 )
2111 .exactly_one_underline("if: true")
2112 .build(),
2113 );
2114 expect_some_error_matches(
2115 src,
2116 &errs,
2117 &ExpectedErrorMessageBuilder::error(
2118 "this identifier is reserved and cannot be used: if",
2119 )
2120 .exactly_one_underline("if")
2121 .build(),
2122 );
2123 }
2124
2125 #[test]
2126 fn reserved_idents3() {
2127 let src = r#"
2128 if {where: true}.like || {has:false}.in then {"like":false}["in"] else {then:true}.else
2129 "#;
2130 let errs = assert_parse_expr_fails(src);
2131 expect_n_errors(src, &errs, 5);
2132 expect_some_error_matches(
2133 src,
2134 &errs,
2135 &ExpectedErrorMessageBuilder::error(
2136 "this identifier is reserved and cannot be used: has",
2137 )
2138 .exactly_one_underline("has")
2139 .build(),
2140 );
2141 expect_some_error_matches(
2142 src,
2143 &errs,
2144 &ExpectedErrorMessageBuilder::error(
2145 "this identifier is reserved and cannot be used: like",
2146 )
2147 .exactly_one_underline("like")
2148 .build(),
2149 );
2150 expect_some_error_matches(
2151 src,
2152 &errs,
2153 &ExpectedErrorMessageBuilder::error(
2154 "this identifier is reserved and cannot be used: in",
2155 )
2156 .exactly_one_underline("in")
2157 .build(),
2158 );
2159 expect_some_error_matches(
2160 src,
2161 &errs,
2162 &ExpectedErrorMessageBuilder::error(
2163 "this identifier is reserved and cannot be used: then",
2164 )
2165 .exactly_one_underline("then")
2166 .build(),
2167 );
2168 expect_some_error_matches(
2169 src,
2170 &errs,
2171 &ExpectedErrorMessageBuilder::error(
2172 "this identifier is reserved and cannot be used: else",
2173 )
2174 .exactly_one_underline("else")
2175 .build(),
2176 );
2177 }
2178
2179 #[test]
2180 fn show_policy1() {
2181 let src = r#"
2182 permit(principal:p,action:a,resource:r)when{w}unless{u}advice{"doit"};
2183 "#;
2184 let errs = assert_parse_policy_fails(src);
2185 expect_n_errors(src, &errs, 6);
2186 expect_some_error_matches(
2187 src,
2188 &errs,
2189 &ExpectedErrorMessageBuilder::error("type constraints using `:` are not supported")
2190 .help("try using `is` instead")
2191 .exactly_one_underline("p")
2192 .build(),
2193 );
2194 expect_some_error_matches(
2195 src,
2196 &errs,
2197 &ExpectedErrorMessageBuilder::error("type constraints using `:` are not supported")
2198 .help("try using `is` instead")
2199 .exactly_one_underline("a")
2200 .build(),
2201 );
2202 expect_some_error_matches(
2203 src,
2204 &errs,
2205 &ExpectedErrorMessageBuilder::error("type constraints using `:` are not supported")
2206 .help("try using `is` instead")
2207 .exactly_one_underline("r")
2208 .build(),
2209 );
2210 expect_some_error_matches(
2211 src,
2212 &errs,
2213 &ExpectedErrorMessageBuilder::error("invalid variable: w")
2214 .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `w` in quotes to make a string?")
2215 .exactly_one_underline("w")
2216 .build(),
2217 );
2218 expect_some_error_matches(
2219 src,
2220 &errs,
2221 &ExpectedErrorMessageBuilder::error("invalid variable: u")
2222 .help("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `u` in quotes to make a string?")
2223 .exactly_one_underline("u")
2224 .build(),
2225 );
2226 expect_some_error_matches(
2227 src,
2228 &errs,
2229 &ExpectedErrorMessageBuilder::error("invalid policy condition: advice")
2230 .help("condition must be either `when` or `unless`")
2231 .exactly_one_underline("advice")
2232 .build(),
2233 );
2234 }
2235
2236 #[test]
2237 fn show_policy2() {
2238 let src = r#"
2239 permit(principal,action,resource)when{true};
2240 "#;
2241 assert_parse_policy_succeeds(src);
2242 }
2243
2244 #[test]
2245 fn show_policy3() {
2246 let src = r#"
2247 permit(principal in User::"jane",action,resource);
2248 "#;
2249 assert_parse_policy_succeeds(src);
2250 }
2251
2252 #[test]
2253 fn show_policy4() {
2254 let src = r#"
2255 forbid(principal in User::"jane",action,resource)unless{
2256 context.group != "friends"
2257 };
2258 "#;
2259 assert_parse_policy_succeeds(src);
2260 }
2261
2262 #[test]
2263 fn single_annotation() {
2264 let policy = assert_parse_policy_succeeds(
2266 r#"
2267 @anno("good annotation")permit(principal,action,resource);
2268 "#,
2269 );
2270 assert_matches!(
2271 policy.annotation(&ast::AnyId::new_unchecked("anno")),
2272 Some(annotation) => assert_eq!(annotation.as_ref(), "good annotation")
2273 );
2274 }
2275
2276 #[test]
2277 fn duplicate_annotations_error() {
2278 let src = r#"
2280 @anno("good annotation")
2281 @anno2("good annotation")
2282 @anno("oops, duplicate")
2283 permit(principal,action,resource);
2284 "#;
2285 let errs = assert_parse_policy_fails(src);
2286 expect_n_errors(src, &errs, 1);
2288 expect_some_error_matches(
2289 src,
2290 &errs,
2291 &ExpectedErrorMessageBuilder::error("duplicate annotation: @anno")
2292 .exactly_one_underline("@anno(\"oops, duplicate\")")
2293 .build(),
2294 );
2295 }
2296
2297 #[test]
2298 fn multiple_policys_and_annotations_ok() {
2299 let policyset = text_to_cst::parse_policies(
2301 r#"
2302 @anno1("first")
2303 permit(principal,action,resource);
2304
2305 @anno2("second")
2306 permit(principal,action,resource);
2307
2308 @anno3a("third-a")
2309 @anno3b("third-b")
2310 permit(principal,action,resource);
2311 "#,
2312 )
2313 .expect("should parse")
2314 .to_policyset()
2315 .unwrap_or_else(|errs| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2316 assert_matches!(
2317 policyset
2318 .get(&ast::PolicyID::from_string("policy0"))
2319 .expect("should be a policy")
2320 .annotation(&ast::AnyId::new_unchecked("anno0")),
2321 None
2322 );
2323 assert_matches!(
2324 policyset
2325 .get(&ast::PolicyID::from_string("policy0"))
2326 .expect("should be a policy")
2327 .annotation(&ast::AnyId::new_unchecked("anno1")),
2328 Some(annotation) => assert_eq!(annotation.as_ref(), "first")
2329 );
2330 assert_matches!(
2331 policyset
2332 .get(&ast::PolicyID::from_string("policy1"))
2333 .expect("should be a policy")
2334 .annotation(&ast::AnyId::new_unchecked("anno2")),
2335 Some(annotation) => assert_eq!(annotation.as_ref(), "second")
2336 );
2337 assert_matches!(
2338 policyset
2339 .get(&ast::PolicyID::from_string("policy2"))
2340 .expect("should be a policy")
2341 .annotation(&ast::AnyId::new_unchecked("anno3a")),
2342 Some(annotation) => assert_eq!(annotation.as_ref(), "third-a")
2343 );
2344 assert_matches!(
2345 policyset
2346 .get(&ast::PolicyID::from_string("policy2"))
2347 .expect("should be a policy")
2348 .annotation(&ast::AnyId::new_unchecked("anno3b")),
2349 Some(annotation) => assert_eq!(annotation.as_ref(), "third-b")
2350 );
2351 assert_matches!(
2352 policyset
2353 .get(&ast::PolicyID::from_string("policy2"))
2354 .expect("should be a policy")
2355 .annotation(&ast::AnyId::new_unchecked("anno3c")),
2356 None
2357 );
2358 assert_eq!(
2359 policyset
2360 .get(&ast::PolicyID::from_string("policy2"))
2361 .expect("should be a policy")
2362 .annotations()
2363 .count(),
2364 2
2365 );
2366 }
2367
2368 #[test]
2369 fn reserved_word_annotations_ok() {
2370 let policyset = text_to_cst::parse_policies(
2372 r#"
2373 @if("this is the annotation for `if`")
2374 @then("this is the annotation for `then`")
2375 @else("this is the annotation for `else`")
2376 @true("this is the annotation for `true`")
2377 @false("this is the annotation for `false`")
2378 @in("this is the annotation for `in`")
2379 @is("this is the annotation for `is`")
2380 @like("this is the annotation for `like`")
2381 @has("this is the annotation for `has`")
2382 @principal("this is the annotation for `principal`") // not reserved at time of this writing, but we test it anyway
2383 permit(principal, action, resource);
2384 "#,
2385 ).expect("should parse")
2386 .to_policyset()
2387 .unwrap_or_else(|errs| panic!("failed convert to AST:\n{:?}", miette::Report::new(errs)));
2388 let policy0 = policyset
2389 .get(&ast::PolicyID::from_string("policy0"))
2390 .expect("should be the right policy ID");
2391 assert_matches!(
2392 policy0.annotation(&ast::AnyId::new_unchecked("if")),
2393 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `if`")
2394 );
2395 assert_matches!(
2396 policy0.annotation(&ast::AnyId::new_unchecked("then")),
2397 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `then`")
2398 );
2399 assert_matches!(
2400 policy0.annotation(&ast::AnyId::new_unchecked("else")),
2401 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `else`")
2402 );
2403 assert_matches!(
2404 policy0.annotation(&ast::AnyId::new_unchecked("true")),
2405 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `true`")
2406 );
2407 assert_matches!(
2408 policy0.annotation(&ast::AnyId::new_unchecked("false")),
2409 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `false`")
2410 );
2411 assert_matches!(
2412 policy0.annotation(&ast::AnyId::new_unchecked("in")),
2413 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `in`")
2414 );
2415 assert_matches!(
2416 policy0.annotation(&ast::AnyId::new_unchecked("is")),
2417 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `is`")
2418 );
2419 assert_matches!(
2420 policy0.annotation(&ast::AnyId::new_unchecked("like")),
2421 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `like`")
2422 );
2423 assert_matches!(
2424 policy0.annotation(&ast::AnyId::new_unchecked("has")),
2425 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `has`")
2426 );
2427 assert_matches!(
2428 policy0.annotation(&ast::AnyId::new_unchecked("principal")),
2429 Some(annotation) => assert_eq!(annotation.as_ref(), "this is the annotation for `principal`")
2430 );
2431 }
2432
2433 #[test]
2434 fn single_annotation_without_value() {
2435 let policy = assert_parse_policy_succeeds(r#"@anno permit(principal,action,resource);"#);
2436 assert_matches!(
2437 policy.annotation(&ast::AnyId::new_unchecked("anno")),
2438 Some(annotation) => assert_eq!(annotation.as_ref(), ""),
2439 );
2440 }
2441
2442 #[test]
2443 fn duplicate_annotations_without_value() {
2444 let src = "@anno @anno permit(principal,action,resource);";
2445 let errs = assert_parse_policy_fails(src);
2446 expect_n_errors(src, &errs, 1);
2447 expect_some_error_matches(
2448 src,
2449 &errs,
2450 &ExpectedErrorMessageBuilder::error("duplicate annotation: @anno")
2451 .exactly_one_underline("@anno")
2452 .build(),
2453 );
2454 }
2455
2456 #[test]
2457 fn multiple_annotation_without_value() {
2458 let policy =
2459 assert_parse_policy_succeeds(r#"@foo @bar permit(principal,action,resource);"#);
2460 assert_matches!(
2461 policy.annotation(&ast::AnyId::new_unchecked("foo")),
2462 Some(annotation) => assert_eq!(annotation.as_ref(), ""),
2463 );
2464 assert_matches!(
2465 policy.annotation(&ast::AnyId::new_unchecked("bar")),
2466 Some(annotation) => assert_eq!(annotation.as_ref(), ""),
2467 );
2468 }
2469
2470 #[test]
2471 fn fail_scope1() {
2472 let src = r#"
2473 permit(
2474 principal in [User::"jane",Group::"friends"],
2475 action,
2476 resource
2477 );
2478 "#;
2479 let errs = assert_parse_policy_fails(src);
2480 expect_n_errors(src, &errs, 1);
2481 expect_some_error_matches(
2482 src,
2483 &errs,
2484 &ExpectedErrorMessageBuilder::error(
2485 "expected single entity uid or template slot, found set of entity uids",
2486 )
2487 .exactly_one_underline(r#"[User::"jane",Group::"friends"]"#)
2488 .build(),
2489 );
2490 }
2491
2492 #[test]
2493 fn fail_scope2() {
2494 let src = r#"
2495 permit(
2496 principal in User::"jane",
2497 action == if true then Photo::"view" else Photo::"edit",
2498 resource
2499 );
2500 "#;
2501 let errs = assert_parse_policy_fails(src);
2502 expect_n_errors(src, &errs, 1);
2503 expect_some_error_matches(
2504 src,
2505 &errs,
2506 &ExpectedErrorMessageBuilder::error("expected an entity uid, found an `if` expression")
2507 .exactly_one_underline(r#"if true then Photo::"view" else Photo::"edit""#)
2508 .build(),
2509 );
2510 }
2511
2512 #[test]
2513 fn fail_scope3() {
2514 let src = r#"
2515 permit(principal,action,resource,context);
2516 "#;
2517 let errs = assert_parse_policy_fails(src);
2518 expect_n_errors(src, &errs, 1);
2519 expect_some_error_matches(
2520 src,
2521 &errs,
2522 &ExpectedErrorMessageBuilder::error(
2523 "this policy has an extra element in the scope: context",
2524 )
2525 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
2526 .exactly_one_underline("context")
2527 .build(),
2528 );
2529 }
2530
2531 #[test]
2532 fn method_call2() {
2533 assert_parse_expr_succeeds(
2534 r#"
2535 principal.contains(resource)
2536 "#,
2537 );
2538
2539 let src = r#"
2540 contains(principal,resource)
2541 "#;
2542 let errs = assert_parse_expr_fails(src);
2543 expect_n_errors(src, &errs, 1);
2544 expect_some_error_matches(
2545 src,
2546 &errs,
2547 &ExpectedErrorMessageBuilder::error("`contains` is a method, not a function")
2548 .help("use a method-style call `e.contains(..)`")
2549 .exactly_one_underline("contains(principal,resource)")
2550 .build(),
2551 );
2552 }
2553
2554 #[test]
2555 fn construct_record_1() {
2556 let e = assert_parse_expr_succeeds(
2557 r#"
2558 {one:"one"}
2559 "#,
2560 );
2561 assert_matches!(e.expr_kind(), ast::ExprKind::Record { .. });
2563 println!("{e}");
2564 }
2565
2566 #[test]
2567 fn construct_record_2() {
2568 let e = assert_parse_expr_succeeds(
2569 r#"
2570 {"one":"one"}
2571 "#,
2572 );
2573 assert_matches!(e.expr_kind(), ast::ExprKind::Record { .. });
2575 println!("{e}");
2576 }
2577
2578 #[test]
2579 fn construct_record_3() {
2580 let e = assert_parse_expr_succeeds(
2581 r#"
2582 {"one":"one",two:"two"}
2583 "#,
2584 );
2585 assert_matches!(e.expr_kind(), ast::ExprKind::Record { .. });
2587 println!("{e}");
2588 }
2589
2590 #[test]
2591 fn construct_record_4() {
2592 let e = assert_parse_expr_succeeds(
2593 r#"
2594 {one:"one","two":"two"}
2595 "#,
2596 );
2597 assert_matches!(e.expr_kind(), ast::ExprKind::Record { .. });
2599 println!("{e}");
2600 }
2601
2602 #[test]
2603 fn construct_record_5() {
2604 let e = assert_parse_expr_succeeds(
2605 r#"
2606 {one:"b\"","b\"":2}
2607 "#,
2608 );
2609 assert_matches!(e.expr_kind(), ast::ExprKind::Record { .. });
2611 println!("{e}");
2612 }
2613
2614 #[test]
2615 fn construct_invalid_get_1() {
2616 let src = r#"
2617 {"one":1, "two":"two"}[0]
2618 "#;
2619 let errs = assert_parse_expr_fails(src);
2620 expect_n_errors(src, &errs, 1);
2621 expect_some_error_matches(
2622 src,
2623 &errs,
2624 &ExpectedErrorMessageBuilder::error("invalid string literal: 0")
2625 .exactly_one_underline("0")
2626 .build(),
2627 );
2628 }
2629
2630 #[test]
2631 fn construct_invalid_get_2() {
2632 let src = r#"
2633 {"one":1, "two":"two"}[-1]
2634 "#;
2635 let errs = assert_parse_expr_fails(src);
2636 expect_n_errors(src, &errs, 1);
2637 expect_some_error_matches(
2638 src,
2639 &errs,
2640 &ExpectedErrorMessageBuilder::error("invalid string literal: (-1)")
2641 .exactly_one_underline("-1")
2642 .build(),
2643 );
2644 }
2645
2646 #[test]
2647 fn construct_invalid_get_3() {
2648 let src = r#"
2649 {"one":1, "two":"two"}[true]
2650 "#;
2651 let errs = assert_parse_expr_fails(src);
2652 expect_n_errors(src, &errs, 1);
2653 expect_some_error_matches(
2654 src,
2655 &errs,
2656 &ExpectedErrorMessageBuilder::error("invalid string literal: true")
2657 .exactly_one_underline("true")
2658 .build(),
2659 );
2660 }
2661
2662 #[test]
2663 fn construct_invalid_get_4() {
2664 let src = r#"
2665 {"one":1, "two":"two"}[one]
2666 "#;
2667 let errs = assert_parse_expr_fails(src);
2668 expect_n_errors(src, &errs, 1);
2669 expect_some_error_matches(
2670 src,
2671 &errs,
2672 &ExpectedErrorMessageBuilder::error("invalid string literal: one")
2673 .exactly_one_underline("one")
2674 .build(),
2675 );
2676 }
2677
2678 #[test]
2679 fn construct_invalid_get_var() {
2680 let src = r#"
2681 {"principal":1, "two":"two"}[principal]
2682 "#;
2683 let errs = assert_parse_expr_fails(src);
2684 expect_n_errors(src, &errs, 1);
2685 expect_some_error_matches(
2686 src,
2687 &errs,
2688 &ExpectedErrorMessageBuilder::error("invalid string literal: principal")
2689 .exactly_one_underline("principal")
2690 .build(),
2691 );
2692 }
2693
2694 #[test]
2695 fn construct_has_1() {
2696 let expr = assert_parse_expr_succeeds(
2697 r#"
2698 {"one":1,"two":2} has "arbitrary+ _string"
2699 "#,
2700 );
2701 assert_matches!(expr.expr_kind(), ast::ExprKind::HasAttr { attr, .. } => {
2702 assert_eq!(attr, "arbitrary+ _string");
2703 });
2704 }
2705
2706 #[test]
2707 fn construct_has_2() {
2708 let src = r#"
2709 {"one":1,"two":2} has 1
2710 "#;
2711 let errs = assert_parse_expr_fails(src);
2712 expect_n_errors(src, &errs, 1);
2713 expect_some_error_matches(
2714 src,
2715 &errs,
2716 &ExpectedErrorMessageBuilder::error("invalid attribute name: 1")
2717 .help("attribute names can either be identifiers or string literals")
2718 .exactly_one_underline("1")
2719 .build(),
2720 );
2721 }
2722
2723 #[test]
2724 fn construct_like_1() {
2725 let expr = assert_parse_expr_succeeds(
2726 r#"
2727 "354 hams" like "*5*"
2728 "#,
2729 );
2730 assert_matches!(expr.expr_kind(), ast::ExprKind::Like { pattern, .. } => {
2731 assert_eq!(pattern.to_string(), "*5*");
2732 });
2733 }
2734
2735 #[test]
2736 fn construct_like_2() {
2737 let src = r#"
2738 "354 hams" like 354
2739 "#;
2740 let errs = assert_parse_expr_fails(src);
2741 expect_n_errors(src, &errs, 1);
2742 expect_some_error_matches(
2743 src,
2744 &errs,
2745 &ExpectedErrorMessageBuilder::error(
2746 "right hand side of a `like` expression must be a pattern literal, but got `354`",
2747 )
2748 .exactly_one_underline("354")
2749 .build(),
2750 );
2751 }
2752
2753 #[test]
2754 fn construct_like_3() {
2755 let expr = assert_parse_expr_succeeds(
2756 r#"
2757 "string\\with\\backslashes" like "string\\with\\backslashes"
2758 "#,
2759 );
2760 assert_matches!(expr.expr_kind(), ast::ExprKind::Like { pattern, .. } => {
2761 assert_eq!(pattern.to_string(), r"string\\with\\backslashes");
2762 });
2763 }
2764
2765 #[test]
2766 fn construct_like_4() {
2767 let expr = assert_parse_expr_succeeds(
2768 r#"
2769 "string\\with\\backslashes" like "string\*with\*backslashes"
2770 "#,
2771 );
2772 assert_matches!(expr.expr_kind(), ast::ExprKind::Like { pattern, .. } => {
2773 assert_eq!(pattern.to_string(), r"string\*with\*backslashes");
2774 });
2775 }
2776
2777 #[test]
2778 fn construct_like_5() {
2779 let src = r#"
2780 "string\*with\*escaped\*stars" like "string\*with\*escaped\*stars"
2781 "#;
2782 let errs = assert_parse_expr_fails(src);
2783 expect_n_errors(src, &errs, 3);
2784 expect_some_error_matches(
2786 src,
2787 &errs,
2788 &ExpectedErrorMessageBuilder::error("the input `\\*` is not a valid escape")
2789 .exactly_one_underline(r#""string\*with\*escaped\*stars""#)
2790 .build(),
2791 );
2792 }
2793
2794 #[test]
2795 fn construct_like_6() {
2796 let expr = assert_parse_expr_succeeds(
2797 r#"
2798 "string*with*stars" like "string\*with\*stars"
2799 "#,
2800 );
2801 assert_matches!(expr.expr_kind(), ast::ExprKind::Like { pattern, .. } => {
2802 assert_eq!(pattern.to_string(), "string\\*with\\*stars");
2803 });
2804 }
2805
2806 #[test]
2807 fn construct_like_7() {
2808 let expr = assert_parse_expr_succeeds(
2809 r#"
2810 "string\\*with\\*backslashes\\*and\\*stars" like "string\\\*with\\\*backslashes\\\*and\\\*stars"
2811 "#,
2812 );
2813 assert_matches!(expr.expr_kind(), ast::ExprKind::Like { pattern, .. } => {
2814 assert_eq!(
2815 pattern.to_string(),
2816 r"string\\\*with\\\*backslashes\\\*and\\\*stars"
2817 );
2818 });
2819 }
2820
2821 #[test]
2822 fn construct_like_var() {
2823 let src = r#"
2824 "principal" like principal
2825 "#;
2826 let errs = assert_parse_expr_fails(src);
2827 expect_n_errors(src, &errs, 1);
2828 expect_some_error_matches(
2829 src,
2830 &errs,
2831 &ExpectedErrorMessageBuilder::error(
2832 "right hand side of a `like` expression must be a pattern literal, but got `principal`",
2833 )
2834 .exactly_one_underline("principal")
2835 .build(),
2836 );
2837 }
2838
2839 #[test]
2840 fn construct_like_name() {
2841 let src = r#"
2842 "foo::bar::baz" like foo::bar
2843 "#;
2844 let errs = assert_parse_expr_fails(src);
2845 expect_n_errors(src, &errs, 1);
2846 expect_some_error_matches(
2847 src,
2848 &errs,
2849 &ExpectedErrorMessageBuilder::error(
2850 "right hand side of a `like` expression must be a pattern literal, but got `foo::bar`",
2851 )
2852 .exactly_one_underline("foo::bar")
2853 .build(),
2854 );
2855 }
2856
2857 #[test]
2858 fn pattern_roundtrip() {
2859 let test_pattern = &vec![
2860 PatternElem::Char('h'),
2861 PatternElem::Char('e'),
2862 PatternElem::Char('l'),
2863 PatternElem::Char('l'),
2864 PatternElem::Char('o'),
2865 PatternElem::Char('\\'),
2866 PatternElem::Char('0'),
2867 PatternElem::Char('*'),
2868 PatternElem::Char('\\'),
2869 PatternElem::Char('*'),
2870 ];
2871 let e1 = ast::Expr::like(ast::Expr::val("hello"), test_pattern.clone());
2872 let s1 = format!("{e1}");
2873 assert_eq!(s1, r#""hello" like "hello\\0\*\\\*""#);
2875 let e2 = assert_parse_expr_succeeds(&s1);
2876 assert_matches!(e2.expr_kind(), ast::ExprKind::Like { pattern, .. } => {
2877 assert_eq!(pattern.get_elems(), test_pattern);
2878 });
2879 let s2 = format!("{e2}");
2880 assert_eq!(s1, s2);
2881 }
2882
2883 #[test]
2884 fn issue_wf_5046() {
2885 let policy = parse_policy(
2886 Some(ast::PolicyID::from_string("WF-5046")),
2887 r#"permit(
2888 principal,
2889 action in [Action::"action"],
2890 resource in G::""
2891 ) when {
2892 true && ("" like "/gisterNatives\\*D")
2893 };"#,
2894 );
2895 assert!(policy.is_ok());
2896 }
2897
2898 #[test]
2899 fn entity_access() {
2900 let expr = assert_parse_expr_succeeds(
2904 r#"
2905 User::"jane" has age
2906 "#,
2907 );
2908 assert_matches!(expr.expr_kind(), ast::ExprKind::HasAttr { attr, .. } => {
2909 assert_eq!(attr, "age");
2910 });
2911
2912 let expr = assert_parse_expr_succeeds(
2914 r#"
2915 User::"jane" has "arbitrary+ _string"
2916 "#,
2917 );
2918 assert_matches!(expr.expr_kind(), ast::ExprKind::HasAttr { attr, .. } => {
2919 assert_eq!(attr, "arbitrary+ _string");
2920 });
2921
2922 let src = r#"
2924 User::"jane" has 1
2925 "#;
2926 let errs = assert_parse_expr_fails(src);
2927 expect_n_errors(src, &errs, 1);
2928 expect_some_error_matches(
2929 src,
2930 &errs,
2931 &ExpectedErrorMessageBuilder::error("invalid attribute name: 1")
2932 .help("attribute names can either be identifiers or string literals")
2933 .exactly_one_underline("1")
2934 .build(),
2935 );
2936
2937 let expr = assert_parse_expr_succeeds(
2939 r#"
2940 User::"jane".age
2941 "#,
2942 );
2943 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
2944 assert_eq!(attr, "age");
2945 });
2946
2947 let expr: ast::Expr = assert_parse_expr_succeeds(
2949 r#"
2950 User::"jane"["arbitrary+ _string"]
2951 "#,
2952 );
2953 assert_matches!(expr.expr_kind(), ast::ExprKind::GetAttr { attr, .. } => {
2954 assert_eq!(attr, "arbitrary+ _string");
2955 });
2956
2957 let src = r#"
2959 User::"jane"[age]
2960 "#;
2961 let errs = assert_parse_expr_fails(src);
2962 expect_n_errors(src, &errs, 1);
2963 expect_some_error_matches(
2964 src,
2965 &errs,
2966 &ExpectedErrorMessageBuilder::error("invalid string literal: age")
2967 .exactly_one_underline("age")
2968 .build(),
2969 );
2970 }
2971
2972 #[test]
2973 fn relational_ops1() {
2974 let src = r#"
2975 3 >= 2 >= 1
2976 "#;
2977 let errs = assert_parse_expr_fails(src);
2978 expect_n_errors(src, &errs, 1);
2979 expect_some_error_matches(
2980 src,
2981 &errs,
2982 &ExpectedErrorMessageBuilder::error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")
2983 .exactly_one_underline("3 >= 2 >= 1")
2984 .build(),
2985 );
2986 }
2987
2988 #[test]
2989 fn relational_ops2() {
2990 assert_parse_expr_succeeds(
2991 r#"
2992 3 >= ("dad" in "dad")
2993 "#,
2994 );
2995 }
2996
2997 #[test]
2998 fn relational_ops3() {
2999 assert_parse_expr_succeeds(
3000 r#"
3001 (3 >= 2) == true
3002 "#,
3003 );
3004 }
3005
3006 #[test]
3007 fn relational_ops4() {
3008 let src = r#"
3009 if 4 < 3 then 4 != 3 else 4 == 3 < 4
3010 "#;
3011 let errs = assert_parse_expr_fails(src);
3012 expect_n_errors(src, &errs, 1);
3013 expect_some_error_matches(
3014 src,
3015 &errs,
3016 &ExpectedErrorMessageBuilder::error("multiple relational operators (>, ==, in, etc.) must be used with parentheses to make ordering explicit")
3017 .exactly_one_underline("4 == 3 < 4")
3018 .build(),
3019 );
3020 }
3021
3022 #[test]
3023 fn arithmetic() {
3024 assert_parse_expr_succeeds(r#" 2 + 4 "#);
3025 assert_parse_expr_succeeds(r#" 2 + -5 "#);
3026 assert_parse_expr_succeeds(r#" 2 - 5 "#);
3027 assert_parse_expr_succeeds(r#" 2 * 5 "#);
3028 assert_parse_expr_succeeds(r#" 2 * -5 "#);
3029 assert_parse_expr_succeeds(r#" context.size * 4 "#);
3030 assert_parse_expr_succeeds(r#" 4 * context.size "#);
3031 assert_parse_expr_succeeds(r#" context.size * context.scale "#);
3032 assert_parse_expr_succeeds(r#" 5 + 10 + 90 "#);
3033 assert_parse_expr_succeeds(r#" 5 + 10 - 90 * -2 "#);
3034 assert_parse_expr_succeeds(r#" 5 + 10 * 90 - 2 "#);
3035 assert_parse_expr_succeeds(r#" 5 - 10 - 90 - 2 "#);
3036 assert_parse_expr_succeeds(r#" 5 * context.size * 10 "#);
3037 assert_parse_expr_succeeds(r#" context.size * 3 * context.scale "#);
3038 }
3039
3040 const CORRECT_TEMPLATES: [&str; 7] = [
3041 r#"permit(principal == ?principal, action == Action::"action", resource == ?resource);"#,
3042 r#"permit(principal in ?principal, action == Action::"action", resource in ?resource);"#,
3043 r#"permit(principal in ?principal, action == Action::"action", resource in ?resource);"#,
3044 r#"permit(principal in p::"principal", action == Action::"action", resource in ?resource);"#,
3045 r#"permit(principal == p::"principal", action == Action::"action", resource in ?resource);"#,
3046 r#"permit(principal in ?principal, action == Action::"action", resource in r::"resource");"#,
3047 r#"permit(principal in ?principal, action == Action::"action", resource == r::"resource");"#,
3048 ];
3049
3050 #[test]
3051 fn template_tests() {
3052 for src in CORRECT_TEMPLATES {
3053 text_to_cst::parse_policy(src)
3054 .expect("parse_error")
3055 .to_policy_template(ast::PolicyID::from_string("i0"))
3056 .unwrap_or_else(|errs| {
3057 panic!(
3058 "Failed to create a policy template: {:?}",
3059 miette::Report::new(errs)
3060 );
3061 });
3062 }
3063 }
3064
3065 #[test]
3066 fn var_type() {
3067 assert_parse_policy_succeeds(
3068 r#"
3069 permit(principal,action,resource);
3070 "#,
3071 );
3072
3073 let src = r#"
3074 permit(principal:User,action,resource);
3075 "#;
3076 let errs = assert_parse_policy_fails(src);
3077 expect_n_errors(src, &errs, 1);
3078 expect_some_error_matches(
3079 src,
3080 &errs,
3081 &ExpectedErrorMessageBuilder::error("type constraints using `:` are not supported")
3082 .help("try using `is` instead")
3083 .exactly_one_underline("User")
3084 .build(),
3085 );
3086 }
3087
3088 #[test]
3089 fn unescape_err_positions() {
3090 let assert_invalid_escape = |p_src, underline| {
3091 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
3092 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("the input `\\q` is not a valid escape").exactly_one_underline(underline).build());
3093 });
3094 };
3095 assert_invalid_escape(
3096 r#"@foo("\q")permit(principal, action, resource);"#,
3097 r#"@foo("\q")"#,
3098 );
3099 assert_invalid_escape(
3100 r#"permit(principal, action, resource) when { "\q" };"#,
3101 r#""\q""#,
3102 );
3103 assert_invalid_escape(
3104 r#"permit(principal, action, resource) when { "\q".contains(0) };"#,
3105 r#""\q".contains(0)"#,
3106 );
3107 assert_invalid_escape(
3108 r#"permit(principal, action, resource) when { "\q".bar };"#,
3109 r#""\q".bar"#,
3110 );
3111 assert_invalid_escape(
3112 r#"permit(principal, action, resource) when { "\q"["a"] };"#,
3113 r#""\q"["a"]"#,
3114 );
3115 assert_invalid_escape(
3116 r#"permit(principal, action, resource) when { "" like "\q" };"#,
3117 r#""\q""#,
3118 );
3119 assert_invalid_escape(
3120 r#"permit(principal, action, resource) when { {}["\q"] };"#,
3121 r#""\q""#,
3122 );
3123 assert_invalid_escape(
3124 r#"permit(principal, action, resource) when { {"\q": 0} };"#,
3125 r#""\q""#,
3126 );
3127 assert_invalid_escape(
3128 r#"permit(principal, action, resource) when { User::"\q" };"#,
3129 r#"User::"\q""#,
3130 );
3131 }
3132
3133 #[track_caller] fn expect_action_error(test: &str, msg: &str, underline: &str) {
3135 assert_matches!(parse_policyset(test), Err(es) => {
3136 expect_some_error_matches(
3137 test,
3138 &es,
3139 &ExpectedErrorMessageBuilder::error(msg)
3140 .help("action entities must have type `Action`, optionally in a namespace")
3141 .exactly_one_underline(underline)
3142 .build(),
3143 );
3144 });
3145 }
3146
3147 #[test]
3148 fn action_must_be_action() {
3149 parse_policyset(r#"permit(principal, action == Action::"view", resource);"#)
3150 .expect("Valid policy failed to parse");
3151 parse_policyset(r#"permit(principal, action == Foo::Action::"view", resource);"#)
3152 .expect("Valid policy failed to parse");
3153 parse_policyset(r#"permit(principal, action in Action::"view", resource);"#)
3154 .expect("Valid policy failed to parse");
3155 parse_policyset(r#"permit(principal, action in Foo::Action::"view", resource);"#)
3156 .expect("Valid policy failed to parse");
3157 parse_policyset(r#"permit(principal, action in [Foo::Action::"view"], resource);"#)
3158 .expect("Valid policy failed to parse");
3159 parse_policyset(
3160 r#"permit(principal, action in [Foo::Action::"view", Action::"view"], resource);"#,
3161 )
3162 .expect("Valid policy failed to parse");
3163 expect_action_error(
3164 r#"permit(principal, action == Foo::"view", resource);"#,
3165 "expected an entity uid with type `Action` but got `Foo::\"view\"`",
3166 "Foo::\"view\"",
3167 );
3168 expect_action_error(
3169 r#"permit(principal, action == Action::Foo::"view", resource);"#,
3170 "expected an entity uid with type `Action` but got `Action::Foo::\"view\"`",
3171 "Action::Foo::\"view\"",
3172 );
3173 expect_action_error(
3174 r#"permit(principal, action == Bar::Action::Foo::"view", resource);"#,
3175 "expected an entity uid with type `Action` but got `Bar::Action::Foo::\"view\"`",
3176 "Bar::Action::Foo::\"view\"",
3177 );
3178 expect_action_error(
3179 r#"permit(principal, action in Bar::Action::Foo::"view", resource);"#,
3180 "expected an entity uid with type `Action` but got `Bar::Action::Foo::\"view\"`",
3181 "Bar::Action::Foo::\"view\"",
3182 );
3183 expect_action_error(
3184 r#"permit(principal, action in [Bar::Action::Foo::"view"], resource);"#,
3185 "expected an entity uid with type `Action` but got `Bar::Action::Foo::\"view\"`",
3186 "[Bar::Action::Foo::\"view\"]",
3187 );
3188 expect_action_error(
3189 r#"permit(principal, action in [Bar::Action::Foo::"view", Action::"check"], resource);"#,
3190 "expected an entity uid with type `Action` but got `Bar::Action::Foo::\"view\"`",
3191 "[Bar::Action::Foo::\"view\", Action::\"check\"]",
3192 );
3193 expect_action_error(
3194 r#"permit(principal, action in [Bar::Action::Foo::"view", Foo::"delete", Action::"check"], resource);"#,
3195 "expected entity uids with type `Action` but got `Bar::Action::Foo::\"view\"` and `Foo::\"delete\"`",
3196 "[Bar::Action::Foo::\"view\", Foo::\"delete\", Action::\"check\"]",
3197 );
3198 }
3199
3200 #[test]
3201 fn method_style() {
3202 let src = r#"permit(principal, action, resource)
3203 when { contains(true) < 1 };"#;
3204 assert_matches!(parse_policyset(src), Err(e) => {
3205 expect_n_errors(src, &e, 1);
3206 expect_some_error_matches(src, &e, &ExpectedErrorMessageBuilder::error(
3207 "`contains` is a method, not a function",
3208 ).help(
3209 "use a method-style call `e.contains(..)`",
3210 ).exactly_one_underline("contains(true)").build());
3211 });
3212 }
3213
3214 #[test]
3215 fn test_mul() {
3216 for (str, expected) in [
3217 ("--2*3", Expr::mul(Expr::neg(Expr::val(-2)), Expr::val(3))),
3218 (
3219 "1 * 2 * false",
3220 Expr::mul(Expr::mul(Expr::val(1), Expr::val(2)), Expr::val(false)),
3221 ),
3222 (
3223 "0 * 1 * principal",
3224 Expr::mul(
3225 Expr::mul(Expr::val(0), Expr::val(1)),
3226 Expr::var(ast::Var::Principal),
3227 ),
3228 ),
3229 (
3230 "0 * (-1) * principal",
3231 Expr::mul(
3232 Expr::mul(Expr::val(0), Expr::val(-1)),
3233 Expr::var(ast::Var::Principal),
3234 ),
3235 ),
3236 (
3237 "0 * 6 * context.foo",
3238 Expr::mul(
3239 Expr::mul(Expr::val(0), Expr::val(6)),
3240 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
3241 ),
3242 ),
3243 (
3244 "(0 * 6) * context.foo",
3245 Expr::mul(
3246 Expr::mul(Expr::val(0), Expr::val(6)),
3247 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
3248 ),
3249 ),
3250 (
3251 "0 * (6 * context.foo)",
3252 Expr::mul(
3253 Expr::val(0),
3254 Expr::mul(
3255 Expr::val(6),
3256 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
3257 ),
3258 ),
3259 ),
3260 (
3261 "0 * (context.foo * 6)",
3262 Expr::mul(
3263 Expr::val(0),
3264 Expr::mul(
3265 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
3266 Expr::val(6),
3267 ),
3268 ),
3269 ),
3270 (
3271 "1 * 2 * 3 * context.foo * 4 * 5 * 6",
3272 Expr::mul(
3273 Expr::mul(
3274 Expr::mul(
3275 Expr::mul(
3276 Expr::mul(Expr::mul(Expr::val(1), Expr::val(2)), Expr::val(3)),
3277 Expr::get_attr(Expr::var(ast::Var::Context), "foo".into()),
3278 ),
3279 Expr::val(4),
3280 ),
3281 Expr::val(5),
3282 ),
3283 Expr::val(6),
3284 ),
3285 ),
3286 (
3287 "principal * (1 + 2)",
3288 Expr::mul(
3289 Expr::var(ast::Var::Principal),
3290 Expr::add(Expr::val(1), Expr::val(2)),
3291 ),
3292 ),
3293 (
3294 "principal * -(-1)",
3295 Expr::mul(Expr::var(ast::Var::Principal), Expr::neg(Expr::val(-1))),
3296 ),
3297 (
3298 "principal * --1",
3299 Expr::mul(Expr::var(ast::Var::Principal), Expr::neg(Expr::val(-1))),
3300 ),
3301 (
3302 r#"false * "bob""#,
3303 Expr::mul(Expr::val(false), Expr::val("bob")),
3304 ),
3305 ] {
3306 let e = assert_parse_expr_succeeds(str);
3307 assert!(
3308 e.eq_shape(&expected),
3309 "{e:?} and {expected:?} should have the same shape",
3310 );
3311 }
3312 }
3313
3314 #[test]
3315 fn test_not() {
3316 for (es, expr) in [
3317 (
3318 "!1 + 2 == 3",
3319 Expr::is_eq(
3320 Expr::add(Expr::not(Expr::val(1)), Expr::val(2)),
3321 Expr::val(3),
3322 ),
3323 ),
3324 (
3325 "!!1 + 2 == 3",
3326 Expr::is_eq(
3327 Expr::add(Expr::not(Expr::not(Expr::val(1))), Expr::val(2)),
3328 Expr::val(3),
3329 ),
3330 ),
3331 (
3332 "!!!1 + 2 == 3",
3333 Expr::is_eq(
3334 Expr::add(Expr::not(Expr::not(Expr::not(Expr::val(1)))), Expr::val(2)),
3335 Expr::val(3),
3336 ),
3337 ),
3338 (
3339 "!!!!1 + 2 == 3",
3340 Expr::is_eq(
3341 Expr::add(
3342 Expr::not(Expr::not(Expr::not(Expr::not(Expr::val(1))))),
3343 Expr::val(2),
3344 ),
3345 Expr::val(3),
3346 ),
3347 ),
3348 (
3349 "!!(-1) + 2 == 3",
3350 Expr::is_eq(
3351 Expr::add(Expr::not(Expr::not(Expr::val(-1))), Expr::val(2)),
3352 Expr::val(3),
3353 ),
3354 ),
3355 ] {
3356 let e = assert_parse_expr_succeeds(es);
3357 assert!(
3358 e.eq_shape(&expr),
3359 "{:?} and {:?} should have the same shape.",
3360 e,
3361 expr
3362 );
3363 }
3364 }
3365
3366 #[test]
3367 fn test_neg() {
3368 for (es, expr) in [
3369 ("-(1 + 2)", Expr::neg(Expr::add(Expr::val(1), Expr::val(2)))),
3370 ("1-(2)", Expr::sub(Expr::val(1), Expr::val(2))),
3371 ("1-2", Expr::sub(Expr::val(1), Expr::val(2))),
3372 ("(-1)", Expr::val(-1)),
3373 ("-(-1)", Expr::neg(Expr::val(-1))),
3374 ("--1", Expr::neg(Expr::val(-1))),
3375 ("--(--1)", Expr::neg(Expr::neg(Expr::neg(Expr::val(-1))))),
3376 ("2--1", Expr::sub(Expr::val(2), Expr::val(-1))),
3377 ("-9223372036854775808", Expr::val(-(9223372036854775808))),
3378 (
3381 "--9223372036854775808",
3382 Expr::neg(Expr::val(-9223372036854775808)),
3383 ),
3384 (
3385 "-(9223372036854775807)",
3386 Expr::neg(Expr::val(9223372036854775807)),
3387 ),
3388 ] {
3389 let e = assert_parse_expr_succeeds(es);
3390 assert!(
3391 e.eq_shape(&expr),
3392 "{:?} and {:?} should have the same shape.",
3393 e,
3394 expr
3395 );
3396 }
3397
3398 for (es, em) in [
3399 (
3400 "-9223372036854775809",
3401 ExpectedErrorMessageBuilder::error(
3402 "integer literal `9223372036854775809` is too large",
3403 )
3404 .help("maximum allowed integer literal is `9223372036854775807`")
3405 .exactly_one_underline("-9223372036854775809")
3406 .build(),
3407 ),
3408 (
3413 "-(9223372036854775808)",
3414 ExpectedErrorMessageBuilder::error(
3415 "integer literal `9223372036854775808` is too large",
3416 )
3417 .help("maximum allowed integer literal is `9223372036854775807`")
3418 .exactly_one_underline("9223372036854775808")
3419 .build(),
3420 ),
3421 ] {
3422 let errs = assert_parse_expr_fails(es);
3423 expect_err(es, &miette::Report::new(errs), &em);
3424 }
3425 }
3426
3427 #[test]
3428 fn test_is_condition_ok() {
3429 for (es, expr) in [
3430 (
3431 r#"User::"alice" is User"#,
3432 Expr::is_entity_type(
3433 Expr::val(r#"User::"alice""#.parse::<EntityUID>().unwrap()),
3434 "User".parse().unwrap(),
3435 ),
3436 ),
3437 (
3438 r#"principal is User"#,
3439 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
3440 ),
3441 (
3442 r#"principal.foo is User"#,
3443 Expr::is_entity_type(
3444 Expr::get_attr(Expr::var(ast::Var::Principal), "foo".into()),
3445 "User".parse().unwrap(),
3446 ),
3447 ),
3448 (
3449 r#"1 is User"#,
3450 Expr::is_entity_type(Expr::val(1), "User".parse().unwrap()),
3451 ),
3452 (
3453 r#"principal is User in Group::"friends""#,
3454 Expr::and(
3455 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
3456 Expr::is_in(
3457 Expr::var(ast::Var::Principal),
3458 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
3459 ),
3460 ),
3461 ),
3462 (
3463 r#"principal is User && principal in Group::"friends""#,
3464 Expr::and(
3465 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
3466 Expr::is_in(
3467 Expr::var(ast::Var::Principal),
3468 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
3469 ),
3470 ),
3471 ),
3472 (
3473 r#"principal is User || principal in Group::"friends""#,
3474 Expr::or(
3475 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
3476 Expr::is_in(
3477 Expr::var(ast::Var::Principal),
3478 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
3479 ),
3480 ),
3481 ),
3482 (
3483 r#"true && principal is User in principal"#,
3484 Expr::and(
3485 Expr::val(true),
3486 Expr::and(
3487 Expr::is_entity_type(
3488 Expr::var(ast::Var::Principal),
3489 "User".parse().unwrap(),
3490 ),
3491 Expr::is_in(
3492 Expr::var(ast::Var::Principal),
3493 Expr::var(ast::Var::Principal),
3494 ),
3495 ),
3496 ),
3497 ),
3498 (
3499 r#"principal is User in principal && true"#,
3500 Expr::and(
3501 Expr::and(
3502 Expr::is_entity_type(
3503 Expr::var(ast::Var::Principal),
3504 "User".parse().unwrap(),
3505 ),
3506 Expr::is_in(
3507 Expr::var(ast::Var::Principal),
3508 Expr::var(ast::Var::Principal),
3509 ),
3510 ),
3511 Expr::val(true),
3512 ),
3513 ),
3514 (
3515 r#"principal is A::B::C::User"#,
3516 Expr::is_entity_type(
3517 Expr::var(ast::Var::Principal),
3518 "A::B::C::User".parse().unwrap(),
3519 ),
3520 ),
3521 (
3522 r#"principal is A::B::C::User in Group::"friends""#,
3523 Expr::and(
3524 Expr::is_entity_type(
3525 Expr::var(ast::Var::Principal),
3526 "A::B::C::User".parse().unwrap(),
3527 ),
3528 Expr::is_in(
3529 Expr::var(ast::Var::Principal),
3530 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
3531 ),
3532 ),
3533 ),
3534 (
3535 r#"if principal is User then 1 else 2"#,
3536 Expr::ite(
3537 Expr::is_entity_type(Expr::var(ast::Var::Principal), "User".parse().unwrap()),
3538 Expr::val(1),
3539 Expr::val(2),
3540 ),
3541 ),
3542 (
3543 r#"if principal is User in Group::"friends" then 1 else 2"#,
3544 Expr::ite(
3545 Expr::and(
3546 Expr::is_entity_type(
3547 Expr::var(ast::Var::Principal),
3548 "User".parse().unwrap(),
3549 ),
3550 Expr::is_in(
3551 Expr::var(ast::Var::Principal),
3552 Expr::val(r#"Group::"friends""#.parse::<EntityUID>().unwrap()),
3553 ),
3554 ),
3555 Expr::val(1),
3556 Expr::val(2),
3557 ),
3558 ),
3559 (
3560 r#"principal::"alice" is principal"#,
3561 Expr::is_entity_type(
3562 Expr::val(r#"principal::"alice""#.parse::<EntityUID>().unwrap()),
3563 "principal".parse().unwrap(),
3564 ),
3565 ),
3566 (
3567 r#"foo::principal::"alice" is foo::principal"#,
3568 Expr::is_entity_type(
3569 Expr::val(r#"foo::principal::"alice""#.parse::<EntityUID>().unwrap()),
3570 "foo::principal".parse().unwrap(),
3571 ),
3572 ),
3573 (
3574 r#"principal::foo::"alice" is principal::foo"#,
3575 Expr::is_entity_type(
3576 Expr::val(r#"principal::foo::"alice""#.parse::<EntityUID>().unwrap()),
3577 "principal::foo".parse().unwrap(),
3578 ),
3579 ),
3580 (
3581 r#"resource::"thing" is resource"#,
3582 Expr::is_entity_type(
3583 Expr::val(r#"resource::"thing""#.parse::<EntityUID>().unwrap()),
3584 "resource".parse().unwrap(),
3585 ),
3586 ),
3587 (
3588 r#"action::"do" is action"#,
3589 Expr::is_entity_type(
3590 Expr::val(r#"action::"do""#.parse::<EntityUID>().unwrap()),
3591 "action".parse().unwrap(),
3592 ),
3593 ),
3594 (
3595 r#"context::"stuff" is context"#,
3596 Expr::is_entity_type(
3597 Expr::val(r#"context::"stuff""#.parse::<EntityUID>().unwrap()),
3598 "context".parse().unwrap(),
3599 ),
3600 ),
3601 ] {
3602 let e = parse_expr(es).unwrap();
3603 assert!(
3604 e.eq_shape(&expr),
3605 "{:?} and {:?} should have the same shape.",
3606 e,
3607 expr
3608 );
3609 }
3610 }
3611
3612 #[test]
3613 fn is_scope() {
3614 for (src, p, a, r) in [
3615 (
3616 r#"permit(principal is User, action, resource);"#,
3617 PrincipalConstraint::is_entity_type(Arc::new("User".parse().unwrap())),
3618 ActionConstraint::any(),
3619 ResourceConstraint::any(),
3620 ),
3621 (
3622 r#"permit(principal is principal, action, resource);"#,
3623 PrincipalConstraint::is_entity_type(Arc::new("principal".parse().unwrap())),
3624 ActionConstraint::any(),
3625 ResourceConstraint::any(),
3626 ),
3627 (
3628 r#"permit(principal is A::User, action, resource);"#,
3629 PrincipalConstraint::is_entity_type(Arc::new("A::User".parse().unwrap())),
3630 ActionConstraint::any(),
3631 ResourceConstraint::any(),
3632 ),
3633 (
3634 r#"permit(principal is User in Group::"thing", action, resource);"#,
3635 PrincipalConstraint::is_entity_type_in(
3636 Arc::new("User".parse().unwrap()),
3637 Arc::new(r#"Group::"thing""#.parse().unwrap()),
3638 ),
3639 ActionConstraint::any(),
3640 ResourceConstraint::any(),
3641 ),
3642 (
3643 r#"permit(principal is principal in Group::"thing", action, resource);"#,
3644 PrincipalConstraint::is_entity_type_in(
3645 Arc::new("principal".parse().unwrap()),
3646 Arc::new(r#"Group::"thing""#.parse().unwrap()),
3647 ),
3648 ActionConstraint::any(),
3649 ResourceConstraint::any(),
3650 ),
3651 (
3652 r#"permit(principal is A::User in Group::"thing", action, resource);"#,
3653 PrincipalConstraint::is_entity_type_in(
3654 Arc::new("A::User".parse().unwrap()),
3655 Arc::new(r#"Group::"thing""#.parse().unwrap()),
3656 ),
3657 ActionConstraint::any(),
3658 ResourceConstraint::any(),
3659 ),
3660 (
3661 r#"permit(principal is User in ?principal, action, resource);"#,
3662 PrincipalConstraint::is_entity_type_in_slot(Arc::new("User".parse().unwrap())),
3663 ActionConstraint::any(),
3664 ResourceConstraint::any(),
3665 ),
3666 (
3667 r#"permit(principal, action, resource is Folder);"#,
3668 PrincipalConstraint::any(),
3669 ActionConstraint::any(),
3670 ResourceConstraint::is_entity_type(Arc::new("Folder".parse().unwrap())),
3671 ),
3672 (
3673 r#"permit(principal, action, resource is Folder in Folder::"inner");"#,
3674 PrincipalConstraint::any(),
3675 ActionConstraint::any(),
3676 ResourceConstraint::is_entity_type_in(
3677 Arc::new("Folder".parse().unwrap()),
3678 Arc::new(r#"Folder::"inner""#.parse().unwrap()),
3679 ),
3680 ),
3681 (
3682 r#"permit(principal, action, resource is Folder in ?resource);"#,
3683 PrincipalConstraint::any(),
3684 ActionConstraint::any(),
3685 ResourceConstraint::is_entity_type_in_slot(Arc::new("Folder".parse().unwrap())),
3686 ),
3687 ] {
3688 let policy = parse_policy_or_template(None, src).unwrap();
3689 assert_eq!(policy.principal_constraint(), &p);
3690 assert_eq!(policy.action_constraint(), &a);
3691 assert_eq!(policy.resource_constraint(), &r);
3692 }
3693 }
3694
3695 #[test]
3696 fn is_err() {
3697 let invalid_is_policies = [
3698 (
3699 r#"permit(principal in Group::"friends" is User, action, resource);"#,
3700 ExpectedErrorMessageBuilder::error("when `is` and `in` are used together, `is` must come first")
3701 .help("try `_ is _ in _`")
3702 .exactly_one_underline(r#"principal in Group::"friends" is User"#)
3703 .build(),
3704 ),
3705 (
3706 r#"permit(principal, action in Group::"action_group" is Action, resource);"#,
3707 ExpectedErrorMessageBuilder::error("`is` cannot appear in the action scope")
3708 .help("try moving `action is ..` into a `when` condition")
3709 .exactly_one_underline(r#"action in Group::"action_group" is Action"#)
3710 .build(),
3711 ),
3712 (
3713 r#"permit(principal, action, resource in Folder::"folder" is File);"#,
3714 ExpectedErrorMessageBuilder::error("when `is` and `in` are used together, `is` must come first")
3715 .help("try `_ is _ in _`")
3716 .exactly_one_underline(r#"resource in Folder::"folder" is File"#)
3717 .build(),
3718 ),
3719 (
3720 r#"permit(principal is User == User::"Alice", action, resource);"#,
3721 ExpectedErrorMessageBuilder::error(
3722 "`is` cannot be used together with `==`",
3723 ).help(
3724 "try using `_ is _ in _`"
3725 ).exactly_one_underline("principal is User == User::\"Alice\"").build(),
3726 ),
3727 (
3728 r#"permit(principal, action, resource is Doc == Doc::"a");"#,
3729 ExpectedErrorMessageBuilder::error(
3730 "`is` cannot be used together with `==`",
3731 ).help(
3732 "try using `_ is _ in _`"
3733 ).exactly_one_underline("resource is Doc == Doc::\"a\"").build(),
3734 ),
3735 (
3736 r#"permit(principal is User::"alice", action, resource);"#,
3737 ExpectedErrorMessageBuilder::error(
3738 r#"right hand side of an `is` expression must be an entity type name, but got `User::"alice"`"#,
3739 ).help(
3740 "try using `==` to test for equality"
3741 ).exactly_one_underline("User::\"alice\"").build(),
3742 ),
3743 (
3744 r#"permit(principal, action, resource is File::"f");"#,
3745 ExpectedErrorMessageBuilder::error(
3746 r#"right hand side of an `is` expression must be an entity type name, but got `File::"f"`"#,
3747 ).help(
3748 "try using `==` to test for equality"
3749 ).exactly_one_underline("File::\"f\"").build(),
3750 ),
3751 (
3752 r#"permit(principal is User in 1, action, resource);"#,
3753 ExpectedErrorMessageBuilder::error(
3754 "expected an entity uid or matching template slot, found literal `1`",
3755 ).exactly_one_underline("1").build(),
3756 ),
3757 (
3758 r#"permit(principal, action, resource is File in 1);"#,
3759 ExpectedErrorMessageBuilder::error(
3760 "expected an entity uid or matching template slot, found literal `1`",
3761 ).exactly_one_underline("1").build(),
3762 ),
3763 (
3764 r#"permit(principal is User in User, action, resource);"#,
3765 ExpectedErrorMessageBuilder::error(
3766 "expected an entity uid or matching template slot, found name `User`",
3767 )
3768 .help(
3769 "try using `is` to test for an entity type or including an identifier string if you intended this name to be an entity uid"
3770 )
3771 .exactly_one_underline("User").build(),
3772 ),
3773 (
3774 r#"permit(principal is User::"Alice" in Group::"f", action, resource);"#,
3775 ExpectedErrorMessageBuilder::error(
3776 r#"right hand side of an `is` expression must be an entity type name, but got `User::"Alice"`"#,
3777 ).help(
3778 "try using `==` to test for equality"
3779 ).exactly_one_underline("User::\"Alice\"").build(),
3780 ),
3781 (
3782 r#"permit(principal, action, resource is File in File);"#,
3783 ExpectedErrorMessageBuilder::error(
3784 "expected an entity uid or matching template slot, found name `File`",
3785 )
3786 .help(
3787 "try using `is` to test for an entity type or including an identifier string if you intended this name to be an entity uid"
3788 )
3789 .exactly_one_underline("File").build(),
3790 ),
3791 (
3792 r#"permit(principal, action, resource is File::"file" in Folder::"folder");"#,
3793 ExpectedErrorMessageBuilder::error(
3794 r#"right hand side of an `is` expression must be an entity type name, but got `File::"file"`"#,
3795 ).help(
3796 "try using `==` to test for equality"
3797 ).exactly_one_underline("File::\"file\"").build(),
3798 ),
3799 (
3800 r#"permit(principal is 1, action, resource);"#,
3801 ExpectedErrorMessageBuilder::error(
3802 r#"right hand side of an `is` expression must be an entity type name, but got `1`"#,
3803 ).help(
3804 "try using `==` to test for equality"
3805 ).exactly_one_underline("1").build(),
3806 ),
3807 (
3808 r#"permit(principal, action, resource is 1);"#,
3809 ExpectedErrorMessageBuilder::error(
3810 r#"right hand side of an `is` expression must be an entity type name, but got `1`"#,
3811 ).help(
3812 "try using `==` to test for equality"
3813 ).exactly_one_underline("1").build(),
3814 ),
3815 (
3816 r#"permit(principal, action is Action, resource);"#,
3817 ExpectedErrorMessageBuilder::error(
3818 "`is` cannot appear in the action scope",
3819 ).help(
3820 "try moving `action is ..` into a `when` condition"
3821 ).exactly_one_underline("action is Action").build(),
3822 ),
3823 (
3824 r#"permit(principal, action is Action::"a", resource);"#,
3825 ExpectedErrorMessageBuilder::error(
3826 "`is` cannot appear in the action scope",
3827 ).help(
3828 "try moving `action is ..` into a `when` condition"
3829 ).exactly_one_underline("action is Action::\"a\"").build(),
3830 ),
3831 (
3832 r#"permit(principal, action is Action in Action::"A", resource);"#,
3833 ExpectedErrorMessageBuilder::error(
3834 "`is` cannot appear in the action scope",
3835 ).help(
3836 "try moving `action is ..` into a `when` condition"
3837 ).exactly_one_underline("action is Action in Action::\"A\"").build(),
3838 ),
3839 (
3840 r#"permit(principal, action is Action in Action, resource);"#,
3841 ExpectedErrorMessageBuilder::error(
3842 "`is` cannot appear in the action scope",
3843 ).help(
3844 "try moving `action is ..` into a `when` condition"
3845 ).exactly_one_underline("action is Action in Action").build(),
3846 ),
3847 (
3848 r#"permit(principal, action is Action::"a" in Action::"b", resource);"#,
3849 ExpectedErrorMessageBuilder::error(
3850 "`is` cannot appear in the action scope",
3851 ).help(
3852 "try moving `action is ..` into a `when` condition"
3853 ).exactly_one_underline("action is Action::\"a\" in Action::\"b\"").build(),
3854 ),
3855 (
3856 r#"permit(principal, action is Action in ?action, resource);"#,
3857 ExpectedErrorMessageBuilder::error(
3858 "`is` cannot appear in the action scope",
3859 ).help(
3860 "try moving `action is ..` into a `when` condition"
3861 ).exactly_one_underline("action is Action in ?action").build(),
3862 ),
3863 (
3864 r#"permit(principal, action is ?action, resource);"#,
3865 ExpectedErrorMessageBuilder::error(
3866 "`is` cannot appear in the action scope",
3867 ).help(
3868 "try moving `action is ..` into a `when` condition"
3869 ).exactly_one_underline("action is ?action").build(),
3870 ),
3871 (
3872 r#"permit(principal is User in ?resource, action, resource);"#,
3873 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?resource instead of ?principal").exactly_one_underline("?resource").build(),
3874 ),
3875 (
3876 r#"permit(principal, action, resource is Folder in ?principal);"#,
3877 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?principal instead of ?resource").exactly_one_underline("?principal").build(),
3878 ),
3879 (
3880 r#"permit(principal is ?principal, action, resource);"#,
3881 ExpectedErrorMessageBuilder::error(
3882 "right hand side of an `is` expression must be an entity type name, but got `?principal`",
3883 ).help(
3884 "try using `==` to test for equality"
3885 ).exactly_one_underline("?principal").build(),
3886 ),
3887 (
3888 r#"permit(principal, action, resource is ?resource);"#,
3889 ExpectedErrorMessageBuilder::error(
3890 "right hand side of an `is` expression must be an entity type name, but got `?resource`",
3891 ).help(
3892 "try using `==` to test for equality"
3893 ).exactly_one_underline("?resource").build(),
3894 ),
3895 (
3896 r#"permit(principal, action, resource) when { principal is 1 };"#,
3897 ExpectedErrorMessageBuilder::error(
3898 r#"right hand side of an `is` expression must be an entity type name, but got `1`"#,
3899 ).help(
3900 "try using `==` to test for equality"
3901 ).exactly_one_underline("1").build(),
3902 ),
3903 (
3904 r#"permit(principal, action, resource) when { principal is User::"alice" in Group::"friends" };"#,
3905 ExpectedErrorMessageBuilder::error(
3906 r#"right hand side of an `is` expression must be an entity type name, but got `User::"alice"`"#,
3907 ).help(
3908 "try using `==` to test for equality"
3909 ).exactly_one_underline("User::\"alice\"").build(),
3910 ),
3911 (
3912 r#"permit(principal, action, resource) when { principal is ! User::"alice" in Group::"friends" };"#,
3913 ExpectedErrorMessageBuilder::error(
3914 r#"right hand side of an `is` expression must be an entity type name, but got `!User::"alice"`"#,
3915 ).help(
3916 "try using `==` to test for equality"
3917 ).exactly_one_underline("! User::\"alice\"").build(),
3918 ),
3919 (
3920 r#"permit(principal, action, resource) when { principal is User::"alice" + User::"alice" in Group::"friends" };"#,
3921 ExpectedErrorMessageBuilder::error(
3922 r#"right hand side of an `is` expression must be an entity type name, but got `User::"alice" + User::"alice"`"#,
3923 ).help(
3924 "try using `==` to test for equality"
3925 ).exactly_one_underline("User::\"alice\" + User::\"alice\"").build(),
3926 ),
3927 (
3928 r#"permit(principal, action, resource) when { principal is User in User::"alice" in Group::"friends" };"#,
3929 ExpectedErrorMessageBuilder::error("unexpected token `in`")
3930 .exactly_one_underline_with_label("in", "expected `&&`, `||`, or `}`")
3931 .build(),
3932 ),
3933 (
3934 r#"permit(principal, action, resource) when { principal is User == User::"alice" in Group::"friends" };"#,
3935 ExpectedErrorMessageBuilder::error("unexpected token `==`")
3936 .exactly_one_underline_with_label("==", "expected `&&`, `||`, `}`, or `in`")
3937 .build(),
3938 ),
3939 (
3940 r#"permit(principal, action, resource) when { principal in Group::"friends" is User };"#,
3942 ExpectedErrorMessageBuilder::error("unexpected token `is`")
3943 .exactly_one_underline_with_label(r#"is"#, "expected `!=`, `&&`, `<`, `<=`, `==`, `>`, `>=`, `||`, `}`, or `in`")
3944 .build(),
3945 ),
3946 (
3947 r#"permit(principal, action, resource) when { principal is "User" };"#,
3949 ExpectedErrorMessageBuilder::error(
3950 r#"right hand side of an `is` expression must be an entity type name, but got `User`"#,
3951 ).help(
3952 "try using `==` to test for equality"
3953 ).exactly_one_underline("\"User\"").build(),
3954 ),
3955 ];
3956 for (p_src, expected) in invalid_is_policies {
3957 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
3958 expect_err(p_src, &miette::Report::new(e), &expected);
3959 });
3960 }
3961 }
3962
3963 #[test]
3964 fn issue_255() {
3965 let policy = r#"
3966 permit (
3967 principal == name-with-dashes::"Alice",
3968 action,
3969 resource
3970 );
3971 "#;
3972 assert_matches!(
3973 parse_policy(None, policy),
3974 Err(e) => {
3975 expect_n_errors(policy, &e, 1);
3976 expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error(
3977 "expected an entity uid or matching template slot, found a `+/-` expression",
3978 ).help(
3979 "entity types and namespaces cannot use `+` or `-` characters -- perhaps try `_` or `::` instead?",
3980 ).exactly_one_underline("name-with-dashes::\"Alice\"").build());
3981 }
3982 );
3983 }
3984
3985 #[test]
3986 fn invalid_methods_function_calls() {
3987 let invalid_exprs = [
3988 (
3989 r#"contains([], 1)"#,
3990 ExpectedErrorMessageBuilder::error("`contains` is a method, not a function")
3991 .help("use a method-style call `e.contains(..)`")
3992 .exactly_one_underline("contains([], 1)")
3993 .build(),
3994 ),
3995 (
3996 r#"[].contains()"#,
3997 ExpectedErrorMessageBuilder::error(
3998 "call to `contains` requires exactly 1 argument, but got 0 arguments",
3999 )
4000 .exactly_one_underline("[].contains()")
4001 .build(),
4002 ),
4003 (
4004 r#"[].contains(1, 2)"#,
4005 ExpectedErrorMessageBuilder::error(
4006 "call to `contains` requires exactly 1 argument, but got 2 arguments",
4007 )
4008 .exactly_one_underline("[].contains(1, 2)")
4009 .build(),
4010 ),
4011 (
4012 r#"[].containsAll()"#,
4013 ExpectedErrorMessageBuilder::error(
4014 "call to `containsAll` requires exactly 1 argument, but got 0 arguments",
4015 )
4016 .exactly_one_underline("[].containsAll()")
4017 .build(),
4018 ),
4019 (
4020 r#"[].containsAll(1, 2)"#,
4021 ExpectedErrorMessageBuilder::error(
4022 "call to `containsAll` requires exactly 1 argument, but got 2 arguments",
4023 )
4024 .exactly_one_underline("[].containsAll(1, 2)")
4025 .build(),
4026 ),
4027 (
4028 r#"[].containsAny()"#,
4029 ExpectedErrorMessageBuilder::error(
4030 "call to `containsAny` requires exactly 1 argument, but got 0 arguments",
4031 )
4032 .exactly_one_underline("[].containsAny()")
4033 .build(),
4034 ),
4035 (
4036 r#"[].containsAny(1, 2)"#,
4037 ExpectedErrorMessageBuilder::error(
4038 "call to `containsAny` requires exactly 1 argument, but got 2 arguments",
4039 )
4040 .exactly_one_underline("[].containsAny(1, 2)")
4041 .build(),
4042 ),
4043 (
4044 r#""1.1.1.1".ip()"#,
4045 ExpectedErrorMessageBuilder::error("`ip` is a function, not a method")
4046 .help("use a function-style call `ip(..)`")
4047 .exactly_one_underline(r#""1.1.1.1".ip()"#)
4048 .build(),
4049 ),
4050 (
4051 r#"greaterThan(1, 2)"#,
4052 ExpectedErrorMessageBuilder::error("`greaterThan` is a method, not a function")
4053 .help("use a method-style call `e.greaterThan(..)`")
4054 .exactly_one_underline("greaterThan(1, 2)")
4055 .build(),
4056 ),
4057 (
4058 "[].bar()",
4059 ExpectedErrorMessageBuilder::error("`bar` is not a valid method")
4060 .exactly_one_underline("[].bar()")
4061 .build(),
4062 ),
4063 (
4064 "principal.addr.isipv4()",
4065 ExpectedErrorMessageBuilder::error("`isipv4` is not a valid method")
4066 .exactly_one_underline("principal.addr.isipv4()")
4067 .help("did you mean `isIpv4`?")
4068 .build(),
4069 ),
4070 (
4071 "bar([])",
4072 ExpectedErrorMessageBuilder::error("`bar` is not a valid function")
4073 .exactly_one_underline("bar([])")
4074 .help("did you mean `ip`?")
4075 .build(),
4076 ),
4077 (
4078 r#"Ip("1.1.1.1/24")"#,
4079 ExpectedErrorMessageBuilder::error("`Ip` is not a valid function")
4080 .exactly_one_underline(r#"Ip("1.1.1.1/24")"#)
4081 .help("did you mean `ip`?")
4082 .build(),
4083 ),
4084 (
4085 "principal()",
4086 ExpectedErrorMessageBuilder::error("`principal(...)` is not a valid function call")
4087 .help("variables cannot be called as functions")
4088 .exactly_one_underline("principal()")
4089 .build(),
4090 ),
4091 (
4092 "(1+1)()",
4093 ExpectedErrorMessageBuilder::error(
4094 "function calls must be of the form `<name>(arg1, arg2, ...)`",
4095 )
4096 .exactly_one_underline("(1+1)()")
4097 .build(),
4098 ),
4099 (
4100 "foo.bar()",
4101 ExpectedErrorMessageBuilder::error(
4102 "attempted to call `foo.bar(...)`, but `foo` does not have any methods",
4103 )
4104 .exactly_one_underline("foo.bar()")
4105 .build(),
4106 ),
4107 ];
4108 for (src, expected) in invalid_exprs {
4109 assert_matches!(parse_expr(src), Err(e) => {
4110 expect_err(src, &miette::Report::new(e), &expected);
4111 });
4112 }
4113 }
4114
4115 #[test]
4116 fn invalid_slot() {
4117 let invalid_policies = [
4118 (
4119 r#"permit(principal == ?resource, action, resource);"#,
4120 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?resource instead of ?principal").exactly_one_underline("?resource").build(),
4121 ),
4122 (
4123 r#"permit(principal in ?resource, action, resource);"#,
4124 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?resource instead of ?principal").exactly_one_underline("?resource").build(),
4125 ),
4126 (
4127 r#"permit(principal == ?foo, action, resource);"#,
4128 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?foo instead of ?principal").exactly_one_underline("?foo").build(),
4129 ),
4130 (
4131 r#"permit(principal in ?foo, action, resource);"#,
4132 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?foo instead of ?principal").exactly_one_underline("?foo").build(),
4133 ),
4134
4135 (
4136 r#"permit(principal, action, resource == ?principal);"#,
4137 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?principal instead of ?resource").exactly_one_underline("?principal").build(),
4138 ),
4139 (
4140 r#"permit(principal, action, resource in ?principal);"#,
4141 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?principal instead of ?resource").exactly_one_underline("?principal").build(),
4142 ),
4143 (
4144 r#"permit(principal, action, resource == ?baz);"#,
4145 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?baz instead of ?resource").exactly_one_underline("?baz").build(),
4146 ),
4147 (
4148 r#"permit(principal, action, resource in ?baz);"#,
4149 ExpectedErrorMessageBuilder::error("expected an entity uid or matching template slot, found ?baz instead of ?resource").exactly_one_underline("?baz").build(),
4150 ),
4151 (
4152 r#"permit(principal, action, resource) when { principal == ?foo};"#,
4153 ExpectedErrorMessageBuilder::error(
4154 "`?foo` is not a valid template slot",
4155 ).help(
4156 "a template slot may only be `?principal` or `?resource`",
4157 ).exactly_one_underline("?foo").build(),
4158 ),
4159
4160 (
4161 r#"permit(principal, action == ?action, resource);"#,
4162 ExpectedErrorMessageBuilder::error("expected single entity uid, found template slot").exactly_one_underline("?action").build(),
4163 ),
4164 (
4165 r#"permit(principal, action in ?action, resource);"#,
4166 ExpectedErrorMessageBuilder::error("expected single entity uid or set of entity uids, found template slot").exactly_one_underline("?action").build(),
4167 ),
4168 (
4169 r#"permit(principal, action == ?principal, resource);"#,
4170 ExpectedErrorMessageBuilder::error("expected single entity uid, found template slot").exactly_one_underline("?principal").build(),
4171 ),
4172 (
4173 r#"permit(principal, action in ?principal, resource);"#,
4174 ExpectedErrorMessageBuilder::error("expected single entity uid or set of entity uids, found template slot").exactly_one_underline("?principal").build(),
4175 ),
4176 (
4177 r#"permit(principal, action == ?resource, resource);"#,
4178 ExpectedErrorMessageBuilder::error("expected single entity uid, found template slot").exactly_one_underline("?resource").build(),
4179 ),
4180 (
4181 r#"permit(principal, action in ?resource, resource);"#,
4182 ExpectedErrorMessageBuilder::error("expected single entity uid or set of entity uids, found template slot").exactly_one_underline("?resource").build(),
4183 ),
4184 (
4185 r#"permit(principal, action in [?bar], resource);"#,
4186 ExpectedErrorMessageBuilder::error("expected single entity uid, found template slot").exactly_one_underline("?bar").build(),
4187 ),
4188 ];
4189
4190 for (p_src, expected) in invalid_policies {
4191 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4192 expect_err(p_src, &miette::Report::new(e), &expected);
4193 });
4194 let forbid_src = format!("forbid{}", &p_src[6..]);
4195 assert_matches!(parse_policy_or_template(None, &forbid_src), Err(e) => {
4196 expect_err(forbid_src.as_str(), &miette::Report::new(e), &expected);
4197 });
4198 }
4199 }
4200
4201 #[test]
4202 fn missing_scope_constraint() {
4203 let p_src = "permit();";
4204 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4205 expect_err(
4206 p_src,
4207 &miette::Report::new(e),
4208 &ExpectedErrorMessageBuilder::error("this policy is missing the `principal` variable in the scope")
4209 .exactly_one_underline("")
4210 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
4211 .build()
4212 );
4213 });
4214 let p_src = "permit(principal);";
4215 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4216 expect_err(
4217 p_src,
4218 &miette::Report::new(e),
4219 &ExpectedErrorMessageBuilder::error("this policy is missing the `action` variable in the scope")
4220 .exactly_one_underline("")
4221 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
4222 .build()
4223 );
4224 });
4225 let p_src = "permit(principal, action);";
4226 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4227 expect_err(
4228 p_src,
4229 &miette::Report::new(e),
4230 &ExpectedErrorMessageBuilder::error("this policy is missing the `resource` variable in the scope")
4231 .exactly_one_underline("")
4232 .help("policy scopes must contain a `principal`, `action`, and `resource` element in that order")
4233 .build()
4234 );
4235 });
4236 }
4237
4238 #[test]
4239 fn invalid_scope_constraint() {
4240 let p_src = "permit(foo, action, resource);";
4241 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4242 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4243 "found an invalid variable in the policy scope: foo",
4244 ).help(
4245 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
4246 ).exactly_one_underline("foo").build());
4247 });
4248 let p_src = "permit(foo::principal, action, resource);";
4249 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4250 expect_err(
4251 p_src,
4252 &miette::Report::new(e),
4253 &ExpectedErrorMessageBuilder::error("unexpected token `::`")
4254 .exactly_one_underline_with_label("::", "expected `!=`, `)`, `,`, `:`, `<`, `<=`, `==`, `>`, `>=`, `in`, or `is`")
4255 .build()
4256 );
4257 });
4258 let p_src = "permit(resource, action, resource);";
4259 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4260 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4261 "found the variable `resource` where the variable `principal` must be used",
4262 ).help(
4263 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
4264 ).exactly_one_underline("resource").build());
4265 });
4266
4267 let p_src = "permit(principal, principal, resource);";
4268 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4269 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4270 "found the variable `principal` where the variable `action` must be used",
4271 ).help(
4272 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
4273 ).exactly_one_underline("principal").build());
4274 });
4275 let p_src = "permit(principal, if, resource);";
4276 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4277 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4278 "found an invalid variable in the policy scope: if",
4279 ).help(
4280 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
4281 ).exactly_one_underline("if").build());
4282 });
4283
4284 let p_src = "permit(principal, action, like);";
4285 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4286 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4287 "found an invalid variable in the policy scope: like",
4288 ).help(
4289 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
4290 ).exactly_one_underline("like").build());
4291 });
4292 let p_src = "permit(principal, action, principal);";
4293 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4294 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4295 "found the variable `principal` where the variable `resource` must be used",
4296 ).help(
4297 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
4298 ).exactly_one_underline("principal").build());
4299 });
4300 let p_src = "permit(principal, action, action);";
4301 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4302 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4303 "found the variable `action` where the variable `resource` must be used",
4304 ).help(
4305 "policy scopes must contain a `principal`, `action`, and `resource` element in that order",
4306 ).exactly_one_underline("action").build());
4307 });
4308 }
4309
4310 #[test]
4311 fn invalid_scope_operator() {
4312 let p_src = r#"permit(principal > User::"alice", action, resource);"#;
4313 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4314 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4315 "invalid operator in the policy scope: >",
4316 ).help(
4317 "policy scope clauses can only use `==`, `in`, `is`, or `_ is _ in _`"
4318 ).exactly_one_underline("principal > User::\"alice\"").build());
4319 });
4320 let p_src = r#"permit(principal, action != Action::"view", resource);"#;
4321 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4322 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4323 "invalid operator in the action scope: !=",
4324 ).help(
4325 "action scope clauses can only use `==` or `in`"
4326 ).exactly_one_underline("action != Action::\"view\"").build());
4327 });
4328 let p_src = r#"permit(principal, action, resource <= Folder::"things");"#;
4329 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4330 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4331 "invalid operator in the policy scope: <=",
4332 ).help(
4333 "policy scope clauses can only use `==`, `in`, `is`, or `_ is _ in _`"
4334 ).exactly_one_underline("resource <= Folder::\"things\"").build());
4335 });
4336 let p_src = r#"permit(principal = User::"alice", action, resource);"#;
4337 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4338 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4339 "'=' is not a valid operator in Cedar",
4340 ).help(
4341 "try using '==' instead",
4342 ).exactly_one_underline("principal = User::\"alice\"").build());
4343 });
4344 let p_src = r#"permit(principal, action = Action::"act", resource);"#;
4345 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4346 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4347 "'=' is not a valid operator in Cedar",
4348 ).help(
4349 "try using '==' instead",
4350 ).exactly_one_underline("action = Action::\"act\"").build());
4351 });
4352 let p_src = r#"permit(principal, action, resource = Photo::"photo");"#;
4353 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4354 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4355 "'=' is not a valid operator in Cedar",
4356 ).help(
4357 "try using '==' instead",
4358 ).exactly_one_underline("resource = Photo::\"photo\"").build());
4359 });
4360 }
4361
4362 #[test]
4363 fn scope_action_eq_set() {
4364 let p_src = r#"permit(principal, action == [Action::"view", Action::"edit"], resource);"#;
4365 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4366 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("expected single entity uid, found set of entity uids").exactly_one_underline(r#"[Action::"view", Action::"edit"]"#).build());
4367 });
4368 }
4369
4370 #[test]
4371 fn scope_compare_to_string() {
4372 let p_src = r#"permit(principal == "alice", action, resource);"#;
4373 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4374 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4375 r#"expected an entity uid or matching template slot, found literal `"alice"`"#
4376 ).help(
4377 "try including the entity type if you intended this string to be an entity uid"
4378 ).exactly_one_underline(r#""alice""#).build());
4379 });
4380 let p_src = r#"permit(principal in "bob_friends", action, resource);"#;
4381 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4382 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4383 r#"expected an entity uid or matching template slot, found literal `"bob_friends"`"#
4384 ).help(
4385 "try including the entity type if you intended this string to be an entity uid"
4386 ).exactly_one_underline(r#""bob_friends""#).build());
4387 });
4388 let p_src = r#"permit(principal, action, resource in "jane_photos");"#;
4389 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4390 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4391 r#"expected an entity uid or matching template slot, found literal `"jane_photos"`"#
4392 ).help(
4393 "try including the entity type if you intended this string to be an entity uid"
4394 ).exactly_one_underline(r#""jane_photos""#).build());
4395 });
4396 let p_src = r#"permit(principal, action in ["view_actions"], resource);"#;
4397 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4398 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4399 r#"expected an entity uid, found literal `"view_actions"`"#
4400 ).help(
4401 "try including the entity type if you intended this string to be an entity uid"
4402 ).exactly_one_underline(r#""view_actions""#).build());
4403 });
4404 }
4405
4406 #[test]
4407 fn scope_compare_to_name() {
4408 let p_src = r#"permit(principal == User, action, resource);"#;
4409 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4410 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4411 "expected an entity uid or matching template slot, found name `User`"
4412 ).help(
4413 "try using `is` to test for an entity type or including an identifier string if you intended this name to be an entity uid"
4414 ).exactly_one_underline("User").build());
4415 });
4416 let p_src = r#"permit(principal in Group, action, resource);"#;
4417 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4418 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4419 "expected an entity uid or matching template slot, found name `Group`"
4420 ).help(
4421 "try using `is` to test for an entity type or including an identifier string if you intended this name to be an entity uid"
4422 ).exactly_one_underline("Group").build());
4423 });
4424 let p_src = r#"permit(principal, action, resource in Album);"#;
4425 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4426 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4427 "expected an entity uid or matching template slot, found name `Album`"
4428 ).help(
4429 "try using `is` to test for an entity type or including an identifier string if you intended this name to be an entity uid"
4430 ).exactly_one_underline("Album").build());
4431 });
4432 let p_src = r#"permit(principal, action == Action, resource);"#;
4433 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4434 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4435 "expected an entity uid, found name `Action`"
4436 ).help(
4437 "try including an identifier string if you intended this name to be an entity uid"
4438 ).exactly_one_underline("Action").build());
4439 });
4440 }
4441
4442 #[test]
4443 fn scope_and() {
4444 let p_src = r#"permit(principal == User::"alice" && principal in Group::"jane_friends", action, resource);"#;
4445 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4446 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4447 "expected an entity uid or matching template slot, found a `&&` expression"
4448 ).help(
4449 "the policy scope can only contain one constraint per variable. Consider moving the second operand of this `&&` into a `when` condition",
4450 ).exactly_one_underline(r#"User::"alice" && principal in Group::"jane_friends""#).build());
4451 });
4452 }
4453
4454 #[test]
4455 fn scope_or() {
4456 let p_src =
4457 r#"permit(principal == User::"alice" || principal == User::"bob", action, resource);"#;
4458 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4459 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4460 "expected an entity uid or matching template slot, found a `||` expression"
4461 ).help(
4462 "the policy scope can only contain one constraint per variable. Consider moving the second operand of this `||` into a new policy",
4463 ).exactly_one_underline(r#"User::"alice" || principal == User::"bob""#).build());
4464 });
4465 }
4466
4467 #[test]
4468 fn scope_action_in_set_set() {
4469 let p_src = r#"permit(principal, action in [[Action::"view"]], resource);"#;
4470 assert_matches!(parse_policy_or_template(None, p_src), Err(e) => {
4471 expect_err(p_src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("expected single entity uid, found set of entity uids").exactly_one_underline(r#"[Action::"view"]"#).build());
4472 });
4473 }
4474
4475 #[test]
4476 fn scope_unexpected_nested_sets() {
4477 let policy = r#"
4478 permit (
4479 principal == [[User::"alice"]],
4480 action,
4481 resource
4482 );
4483 "#;
4484 assert_matches!(
4485 parse_policy(None, policy),
4486 Err(e) => {
4487 expect_n_errors(policy, &e, 1);
4488 expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error(
4489 "expected single entity uid or template slot, found set of entity uids",
4490 ).exactly_one_underline(r#"[[User::"alice"]]"#).build());
4491 }
4492 );
4493
4494 let policy = r#"
4495 permit (
4496 principal,
4497 action,
4498 resource == [[?resource]]
4499 );
4500 "#;
4501 assert_matches!(
4502 parse_policy(None, policy),
4503 Err(e) => {
4504 expect_n_errors(policy, &e, 1);
4505 expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error(
4506 "expected single entity uid or template slot, found set of entity uids",
4507 ).exactly_one_underline("[[?resource]]").build());
4508 }
4509 );
4510
4511 let policy = r#"
4512 permit (
4513 principal,
4514 action in [[[Action::"act"]]],
4515 resource
4516 );
4517 "#;
4518 assert_matches!(
4519 parse_policy(None, policy),
4520 Err(e) => {
4521 expect_n_errors(policy, &e, 1);
4522 expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error(
4523 "expected single entity uid, found set of entity uids",
4524 ).exactly_one_underline(r#"[[Action::"act"]]"#).build());
4525 }
4526 );
4527 }
4528
4529 #[test]
4530 fn unsupported_ops() {
4531 let src = "1/2";
4532 assert_matches!(parse_expr(src), Err(e) => {
4533 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("division is not supported").exactly_one_underline("1/2").build());
4534 });
4535 let src = "7 % 3";
4536 assert_matches!(parse_expr(src), Err(e) => {
4537 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("remainder/modulo is not supported").exactly_one_underline("7 % 3").build());
4538 });
4539 let src = "7 = 3";
4540 assert_matches!(parse_expr(src), Err(e) => {
4541 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error("'=' is not a valid operator in Cedar").exactly_one_underline("7 = 3").help("try using '==' instead").build());
4542 });
4543 }
4544
4545 #[test]
4546 fn over_unary() {
4547 let src = "!!!!!!false";
4548 assert_matches!(parse_expr(src), Err(e) => {
4549 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4550 "too many occurrences of `!_`",
4551 ).help(
4552 "cannot chain more the 4 applications of a unary operator"
4553 ).exactly_one_underline("!!!!!!false").build());
4554 });
4555 let src = "-------0";
4556 assert_matches!(parse_expr(src), Err(e) => {
4557 expect_err(src, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4558 "too many occurrences of `-_`",
4559 ).help(
4560 "cannot chain more the 4 applications of a unary operator"
4561 ).exactly_one_underline("-------0").build());
4562 });
4563 }
4564
4565 #[test]
4566 fn arbitrary_variables() {
4567 #[track_caller]
4568 fn expect_arbitrary_var(name: &str) {
4569 assert_matches!(parse_expr(name), Err(e) => {
4570 expect_err(name, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4571 &format!("invalid variable: {name}"),
4572 ).help(
4573 &format!("the valid Cedar variables are `principal`, `action`, `resource`, and `context`; did you mean to enclose `{name}` in quotes to make a string?"),
4574 ).exactly_one_underline(name).build());
4575 })
4576 }
4577 expect_arbitrary_var("foo::principal");
4578 expect_arbitrary_var("bar::action");
4579 expect_arbitrary_var("baz::resource");
4580 expect_arbitrary_var("buz::context");
4581 expect_arbitrary_var("foo::principal");
4582 expect_arbitrary_var("foo::bar::principal");
4583 expect_arbitrary_var("principal::foo");
4584 expect_arbitrary_var("principal::foo::bar");
4585 expect_arbitrary_var("foo::principal::bar");
4586 expect_arbitrary_var("foo");
4587 expect_arbitrary_var("foo::bar");
4588 expect_arbitrary_var("foo::bar::baz");
4589 }
4590
4591 #[test]
4592 fn empty_clause() {
4593 #[track_caller]
4594 fn expect_empty_clause(policy: &str, clause: &str) {
4595 assert_matches!(parse_policy_or_template(None, policy), Err(e) => {
4596 expect_err(policy, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4597 &format!("`{clause}` condition clause cannot be empty")
4598 ).exactly_one_underline(&format!("{clause} {{}}")).build());
4599 })
4600 }
4601
4602 expect_empty_clause("permit(principal, action, resource) when {};", "when");
4603 expect_empty_clause("permit(principal, action, resource) unless {};", "unless");
4604 expect_empty_clause(
4605 "permit(principal, action, resource) when { principal has foo } when {};",
4606 "when",
4607 );
4608 expect_empty_clause(
4609 "permit(principal, action, resource) when { principal has foo } unless {};",
4610 "unless",
4611 );
4612 expect_empty_clause(
4613 "permit(principal, action, resource) when {} unless { resource.bar };",
4614 "when",
4615 );
4616 expect_empty_clause(
4617 "permit(principal, action, resource) unless {} unless { resource.bar };",
4618 "unless",
4619 );
4620 }
4621
4622 #[test]
4623 fn namespaced_attr() {
4624 #[track_caller]
4625 fn expect_namespaced_attr(expr: &str, name: &str) {
4626 assert_matches!(parse_expr(expr), Err(e) => {
4627 expect_err(expr, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4628 &format!("`{name}` cannot be used as an attribute as it contains a namespace")
4629 ).exactly_one_underline(name).build());
4630 })
4631 }
4632
4633 expect_namespaced_attr("principal has foo::bar", "foo::bar");
4634 expect_namespaced_attr("principal has foo::bar::baz", "foo::bar::baz");
4635 expect_namespaced_attr("principal has foo::principal", "foo::principal");
4636 expect_namespaced_attr("{foo::bar: 1}", "foo::bar");
4637
4638 let expr = "principal has if::foo";
4639 assert_matches!(parse_expr(expr), Err(e) => {
4640 expect_err(expr, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4641 "this identifier is reserved and cannot be used: if"
4642 ).exactly_one_underline("if").build());
4643 })
4644 }
4645
4646 #[test]
4647 fn reserved_ident_var() {
4648 #[track_caller]
4649 fn expect_reserved_ident(name: &str, reserved: &str) {
4650 assert_matches!(parse_expr(name), Err(e) => {
4651 expect_err(name, &miette::Report::new(e), &ExpectedErrorMessageBuilder::error(
4652 &format!("this identifier is reserved and cannot be used: {reserved}"),
4653 ).exactly_one_underline(reserved).build());
4654 })
4655 }
4656 expect_reserved_ident("if::principal", "if");
4657 expect_reserved_ident("then::action", "then");
4658 expect_reserved_ident("else::resource", "else");
4659 expect_reserved_ident("true::context", "true");
4660 expect_reserved_ident("false::bar::principal", "false");
4661 expect_reserved_ident("foo::in::principal", "in");
4662 expect_reserved_ident("foo::is::bar::principal", "is");
4663 }
4664
4665 #[test]
4666 fn reserved_namespace() {
4667 assert_matches!(parse_expr(r#"__cedar::"""#),
4668 Err(errs) if matches!(errs.as_ref().first(),
4669 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4670 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar".parse::<InternalName>().unwrap())));
4671 assert_matches!(parse_expr(r#"__cedar::A::"""#),
4672 Err(errs) if matches!(errs.as_ref().first(),
4673 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4674 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar::A".parse::<InternalName>().unwrap())));
4675 assert_matches!(parse_expr(r#"A::__cedar::B::"""#),
4676 Err(errs) if matches!(errs.as_ref().first(),
4677 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4678 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "A::__cedar::B".parse::<InternalName>().unwrap())));
4679 assert_matches!(parse_expr(r#"[A::"", __cedar::Action::"action"]"#),
4680 Err(errs) if matches!(errs.as_ref().first(),
4681 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4682 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar::Action".parse::<InternalName>().unwrap())));
4683 assert_matches!(parse_expr(r#"principal is __cedar::A"#),
4684 Err(errs) if matches!(errs.as_ref().first(),
4685 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4686 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar::A".parse::<InternalName>().unwrap())));
4687 assert_matches!(parse_expr(r#"__cedar::decimal("0.0")"#),
4688 Err(errs) if matches!(errs.as_ref().first(),
4689 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4690 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar::decimal".parse::<InternalName>().unwrap())));
4691 assert_matches!(parse_expr(r#"ip("").__cedar()"#),
4692 Err(errs) if matches!(errs.as_ref().first(),
4693 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4694 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar".parse::<InternalName>().unwrap())));
4695 assert_matches!(parse_expr(r#"{__cedar: 0}"#),
4696 Err(errs) if matches!(errs.as_ref().first(),
4697 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4698 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar".parse::<InternalName>().unwrap())));
4699 assert_matches!(parse_expr(r#"{a: 0}.__cedar"#),
4700 Err(errs) if matches!(errs.as_ref().first(),
4701 ParseError::ToAST(to_ast_err) if matches!(to_ast_err.kind(),
4702 ToASTErrorKind::ReservedNamespace(ReservedNameError(n)) if *n == "__cedar".parse::<InternalName>().unwrap())));
4703 assert_matches!(
4705 parse_policy(
4706 None,
4707 r#"@__cedar("foo") permit(principal, action, resource);"#
4708 ),
4709 Ok(_)
4710 );
4711 }
4712
4713 #[test]
4714 fn arbitrary_name_attr_access() {
4715 let src = "foo.attr";
4716 assert_matches!(parse_expr(src), Err(e) => {
4717 expect_err(src, &miette::Report::new(e),
4718 &ExpectedErrorMessageBuilder::error("invalid member access `foo.attr`, `foo` has no fields or methods")
4719 .exactly_one_underline("foo.attr")
4720 .build()
4721 );
4722 });
4723
4724 let src = r#"foo["attr"]"#;
4725 assert_matches!(parse_expr(src), Err(e) => {
4726 expect_err(src, &miette::Report::new(e),
4727 &ExpectedErrorMessageBuilder::error(r#"invalid indexing expression `foo["attr"]`, `foo` has no fields"#)
4728 .exactly_one_underline(r#"foo["attr"]"#)
4729 .build()
4730 );
4731 });
4732
4733 let src = r#"foo["\n"]"#;
4734 assert_matches!(parse_expr(src), Err(e) => {
4735 expect_err(src, &miette::Report::new(e),
4736 &ExpectedErrorMessageBuilder::error(r#"invalid indexing expression `foo["\n"]`, `foo` has no fields"#)
4737 .exactly_one_underline(r#"foo["\n"]"#)
4738 .build()
4739 );
4740 });
4741 }
4742}