Skip to main content

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