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