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