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