Skip to main content

cedar_policy_core/parser/
cst_to_ast.rs

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