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