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