cedar_policy_core/parser/
cst_to_ast.rs

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