cedar_policy_core/parser/
cst_to_ast.rs

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