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