Skip to main content

cedar_policy_core/est/
expr.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
17use super::FromJsonError;
18#[cfg(feature = "tolerant-ast")]
19use crate::ast::expr_allows_errors::AstExprErrorKind;
20use crate::ast::Infallible;
21use crate::ast::{self, is_normalized_ident, BoundedDisplay, EntityUID, Name};
22use crate::entities::json::{
23    err::EscapeKind, err::JsonDeserializationError, err::JsonDeserializationErrorContext,
24    CedarValueJson, FnAndArgs,
25};
26use crate::expr_builder::{ExprBuilder, ExprBuilderInfallibleBuild};
27use crate::extensions::{ExtStyles, Extensions};
28use crate::jsonvalue::JsonValueWithNoDuplicateKeys;
29use crate::parser::{cst, err::ParseErrors, Loc, Node};
30use crate::FromNormalizedStr;
31use itertools::Itertools;
32use nonempty::NonEmpty;
33use serde::{de::Visitor, Deserialize, Serialize};
34use serde_with::serde_as;
35use smol_str::{SmolStr, ToSmolStr};
36use std::collections::{btree_map, BTreeMap, HashMap};
37use std::sync::Arc;
38
39/// Serde JSON structure for a Cedar expression in the EST format
40#[derive(Debug, Clone, PartialEq, Serialize)]
41#[serde(untagged)]
42#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
43#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
44pub enum Expr {
45    /// Any Cedar expression other than an extension function call.
46    ExprNoExt(ExprNoExt),
47    /// Extension function call, where the key is the name of an extension
48    /// function or method.
49    ExtFuncCall(ExtFuncCall),
50}
51
52// Manual implementation of `Deserialize` is more efficient than the derived
53// implementation with `serde(untagged)`. In particular, if the key is valid for
54// `ExprNoExt` but there is a deserialization problem within the corresponding
55// value, the derived implementation would backtrack and try to deserialize as
56// `ExtFuncCall` with that key as the extension function name, but this manual
57// implementation instead eagerly errors out, taking advantage of the fact that
58// none of the keys for `ExprNoExt` are valid extension function names.
59//
60// See #1284.
61impl<'de> Deserialize<'de> for Expr {
62    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
63    where
64        D: serde::Deserializer<'de>,
65    {
66        struct ExprVisitor;
67        impl<'de> Visitor<'de> for ExprVisitor {
68            type Value = Expr;
69            fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
70                formatter.write_str("JSON object representing an expression")
71            }
72
73            fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
74            where
75                A: serde::de::MapAccess<'de>,
76            {
77                let (k, v): (SmolStr, JsonValueWithNoDuplicateKeys) = match map.next_entry()? {
78                    None => {
79                        return Err(serde::de::Error::custom(
80                            "empty map is not a valid expression",
81                        ))
82                    }
83                    Some((k, v)) => (k, v),
84                };
85                match map.next_key()? {
86                    None => (),
87                    Some(k2) => {
88                        let k2: SmolStr = k2;
89                        return Err(serde::de::Error::custom(format!("JSON object representing an `Expr` should have only one key, but found two keys: `{k}` and `{k2}`")));
90                    }
91                };
92                if ExtStyles::is_known_extension_func_str(&k) {
93                    // `k` is the name of an extension function or method. We assume that
94                    // no such keys are valid keys for `ExprNoExt`, so we must parse as an
95                    // `ExtFuncCall`.
96                    let obj = serde_json::json!({ k: v });
97                    let extfunccall =
98                        serde_json::from_value(obj).map_err(serde::de::Error::custom)?;
99                    Ok(Expr::ExtFuncCall(extfunccall))
100                } else {
101                    // not a valid extension function or method, so we expect it
102                    // to work for `ExprNoExt`.
103                    let obj = serde_json::json!({ k: v });
104                    let exprnoext =
105                        serde_json::from_value(obj).map_err(serde::de::Error::custom)?;
106                    Ok(Expr::ExprNoExt(exprnoext))
107                }
108            }
109        }
110
111        deserializer.deserialize_map(ExprVisitor)
112    }
113}
114
115/// Represent an element of a pattern literal
116#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
117#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
118#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
119pub enum PatternElem {
120    /// The wildcard asterisk
121    Wildcard,
122    /// A string without any wildcards
123    Literal(SmolStr),
124}
125
126impl From<&[PatternElem]> for crate::ast::Pattern {
127    fn from(value: &[PatternElem]) -> Self {
128        let mut elems = Vec::new();
129        for elem in value {
130            match elem {
131                PatternElem::Wildcard => {
132                    elems.push(crate::ast::PatternElem::Wildcard);
133                }
134                PatternElem::Literal(s) => {
135                    elems.extend(s.chars().map(crate::ast::PatternElem::Char));
136                }
137            }
138        }
139        Self::from(elems)
140    }
141}
142
143impl From<crate::ast::PatternElem> for PatternElem {
144    fn from(value: crate::ast::PatternElem) -> Self {
145        match value {
146            crate::ast::PatternElem::Wildcard => Self::Wildcard,
147            crate::ast::PatternElem::Char(c) => Self::Literal(c.to_smolstr()),
148        }
149    }
150}
151
152impl From<crate::ast::Pattern> for Vec<PatternElem> {
153    fn from(value: crate::ast::Pattern) -> Self {
154        value.iter().map(|elem| (*elem).into()).collect()
155    }
156}
157
158/// Represents the variants of the `has` operation, either a simple operation or
159/// the extended has operation
160#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
161#[serde(untagged)]
162#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
163#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi,))]
164pub enum HasAttrRepr {
165    /// Simple has test with a single attribute :
166    /// { "has": { "left": ..., "attr": <string>}}
167    Simple {
168        /// Left-hand argument
169        left: Arc<Expr>,
170        /// Attribute tested
171        attr: SmolStr,
172    },
173    /// Extended has test with a sequence of attributes :
174    /// { "has": { "left": ..., "attr": [<string>, ..]}}
175    Extended {
176        /// Left-hand argument
177        left: Arc<Expr>,
178        /// Sequence of attributes tested
179        attr: NonEmpty<SmolStr>,
180    },
181}
182
183/// Serde JSON structure for [any Cedar expression other than an extension
184/// function call] in the EST format
185#[serde_as]
186#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
187#[serde(deny_unknown_fields)]
188#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
189#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
190pub enum ExprNoExt {
191    /// Literal value (including anything that's legal to express in the
192    /// attribute-value JSON format)
193    Value(CedarValueJson),
194    /// Var
195    Var(ast::Var),
196    /// Template slot
197    Slot(#[cfg_attr(feature = "wasm", tsify(type = "string"))] ast::SlotId),
198    /// `!`
199    #[serde(rename = "!")]
200    Not {
201        /// Argument
202        arg: Arc<Expr>,
203    },
204    /// `-`
205    #[serde(rename = "neg")]
206    Neg {
207        /// Argument
208        arg: Arc<Expr>,
209    },
210    /// `==`
211    #[serde(rename = "==")]
212    Eq {
213        /// Left-hand argument
214        left: Arc<Expr>,
215        /// Right-hand argument
216        right: Arc<Expr>,
217    },
218    /// `!=`
219    #[serde(rename = "!=")]
220    NotEq {
221        /// Left-hand argument
222        left: Arc<Expr>,
223        /// Right-hand argument
224        right: Arc<Expr>,
225    },
226    /// `in`
227    #[serde(rename = "in")]
228    In {
229        /// Left-hand argument
230        left: Arc<Expr>,
231        /// Right-hand argument
232        right: Arc<Expr>,
233    },
234    /// `<`
235    #[serde(rename = "<")]
236    Less {
237        /// Left-hand argument
238        left: Arc<Expr>,
239        /// Right-hand argument
240        right: Arc<Expr>,
241    },
242    /// `<=`
243    #[serde(rename = "<=")]
244    LessEq {
245        /// Left-hand argument
246        left: Arc<Expr>,
247        /// Right-hand argument
248        right: Arc<Expr>,
249    },
250    /// `>`
251    #[serde(rename = ">")]
252    Greater {
253        /// Left-hand argument
254        left: Arc<Expr>,
255        /// Right-hand argument
256        right: Arc<Expr>,
257    },
258    /// `>=`
259    #[serde(rename = ">=")]
260    GreaterEq {
261        /// Left-hand argument
262        left: Arc<Expr>,
263        /// Right-hand argument
264        right: Arc<Expr>,
265    },
266    /// `&&`
267    #[serde(rename = "&&")]
268    And {
269        /// Left-hand argument
270        left: Arc<Expr>,
271        /// Right-hand argument
272        right: Arc<Expr>,
273    },
274    /// `||`
275    #[serde(rename = "||")]
276    Or {
277        /// Left-hand argument
278        left: Arc<Expr>,
279        /// Right-hand argument
280        right: Arc<Expr>,
281    },
282    /// `+`
283    #[serde(rename = "+")]
284    Add {
285        /// Left-hand argument
286        left: Arc<Expr>,
287        /// Right-hand argument
288        right: Arc<Expr>,
289    },
290    /// `-`
291    #[serde(rename = "-")]
292    Sub {
293        /// Left-hand argument
294        left: Arc<Expr>,
295        /// Right-hand argument
296        right: Arc<Expr>,
297    },
298    /// `*`
299    #[serde(rename = "*")]
300    Mul {
301        /// Left-hand argument
302        left: Arc<Expr>,
303        /// Right-hand argument
304        right: Arc<Expr>,
305    },
306    /// `contains()`
307    #[serde(rename = "contains")]
308    Contains {
309        /// Left-hand argument (receiver)
310        left: Arc<Expr>,
311        /// Right-hand argument (inside the `()`)
312        right: Arc<Expr>,
313    },
314    /// `containsAll()`
315    #[serde(rename = "containsAll")]
316    ContainsAll {
317        /// Left-hand argument (receiver)
318        left: Arc<Expr>,
319        /// Right-hand argument (inside the `()`)
320        right: Arc<Expr>,
321    },
322    /// `containsAny()`
323    #[serde(rename = "containsAny")]
324    ContainsAny {
325        /// Left-hand argument (receiver)
326        left: Arc<Expr>,
327        /// Right-hand argument (inside the `()`)
328        right: Arc<Expr>,
329    },
330    /// `isEmpty()`
331    #[serde(rename = "isEmpty")]
332    IsEmpty {
333        /// Argument
334        arg: Arc<Expr>,
335    },
336    /// `getTag()`
337    #[serde(rename = "getTag")]
338    GetTag {
339        /// Left-hand argument (receiver)
340        left: Arc<Expr>,
341        /// Right-hand argument (inside the `()`)
342        right: Arc<Expr>,
343    },
344    /// `hasTag()`
345    #[serde(rename = "hasTag")]
346    HasTag {
347        /// Left-hand argument (receiver)
348        left: Arc<Expr>,
349        /// Right-hand argument (inside the `()`)
350        right: Arc<Expr>,
351    },
352    /// Get-attribute
353    #[serde(rename = ".")]
354    GetAttr {
355        /// Left-hand argument
356        left: Arc<Expr>,
357        /// Attribute name
358        attr: SmolStr,
359    },
360    /// `has`
361    #[serde(rename = "has")]
362    HasAttr(HasAttrRepr),
363    /// `like`
364    #[serde(rename = "like")]
365    Like {
366        /// Left-hand argument
367        left: Arc<Expr>,
368        /// Pattern
369        pattern: Vec<PatternElem>,
370    },
371    /// `<entity> is <entity_type> in <entity_or_entity_set> `
372    #[serde(rename = "is")]
373    Is {
374        /// Left-hand entity argument
375        left: Arc<Expr>,
376        /// Entity type
377        entity_type: SmolStr,
378        /// Entity or entity set
379        #[serde(skip_serializing_if = "Option::is_none")]
380        #[serde(rename = "in")]
381        in_expr: Option<Arc<Expr>>,
382    },
383    /// Ternary
384    #[serde(rename = "if-then-else")]
385    If {
386        /// Condition
387        #[serde(rename = "if")]
388        cond_expr: Arc<Expr>,
389        /// `then` expression
390        #[serde(rename = "then")]
391        then_expr: Arc<Expr>,
392        /// `else` expression
393        #[serde(rename = "else")]
394        else_expr: Arc<Expr>,
395    },
396    /// Set literal, whose elements may be arbitrary expressions
397    /// (which is why we need this case specifically and can't just
398    /// use Expr::Value)
399    Set(Vec<Expr>),
400    /// Record literal, whose elements may be arbitrary expressions
401    /// (which is why we need this case specifically and can't just
402    /// use Expr::Value)
403    Record(
404        #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
405        #[cfg_attr(feature = "wasm", tsify(type = "Record<string, Expr>"))]
406        BTreeMap<SmolStr, Expr>,
407    ),
408    /// AST Error node - this represents a parsing error in a partially generated AST
409    #[cfg(feature = "tolerant-ast")]
410    Error(AstExprErrorKind),
411}
412
413/// Serde JSON structure for an extension function call in the EST format
414#[serde_as]
415#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
416#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
417#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
418pub struct ExtFuncCall {
419    /// maps the name of the function to a JSON list/array of the arguments.
420    /// Note that for method calls, the method receiver is the first argument.
421    /// For example, for `a.isInRange(b)`, the first argument is `a` and the
422    /// second argument is `b`.
423    ///
424    /// INVARIANT: This map should always have exactly one k-v pair (not more or
425    /// less), but we make it a map in order to get the correct JSON structure
426    /// we want.
427    #[serde(flatten)]
428    #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
429    #[cfg_attr(feature = "wasm", tsify(type = "Record<string, Array<Expr>>"))]
430    call: HashMap<SmolStr, Vec<Expr>>,
431}
432
433impl ExtFuncCall {
434    /// Check the invariant.
435    ///
436    /// Returns `FromJsonError::MissingOperator` or `FromJsonError::MultipleOperators` as appropriate.
437    fn invariant(&self) -> Result<(), FromJsonError> {
438        match self.call.len() {
439            0 => Err(FromJsonError::MissingOperator),
440            1 => Ok(()),
441            _ => Err(FromJsonError::MultipleOperators {
442                ops: self.call.clone().into_keys().collect(),
443            }),
444        }
445    }
446
447    /// Attempt to extract the function name and arguments, returns the error of `self.invariant()`
448    /// if invariant doesn't hold.
449    pub(crate) fn try_into_components(self) -> Result<(SmolStr, Vec<Expr>), FromJsonError> {
450        self.invariant()?;
451        match self.call.into_iter().next() {
452            Some((fn_name, args)) => Ok((fn_name, args)),
453            None => Err(FromJsonError::MissingOperator),
454        }
455    }
456
457    /// Attempt to extract references to the function name and arguments,
458    /// Returns the error of `self.invariant()` if invariant doesn't hold.
459    pub(crate) fn try_components(&self) -> Result<(&SmolStr, &[Expr]), FromJsonError> {
460        self.invariant()?;
461        match self.call.iter().next() {
462            Some((fn_name, args)) => Ok((fn_name, args)),
463            None => Err(FromJsonError::MissingOperator),
464        }
465    }
466}
467
468/// Construct an [`Expr`].
469#[derive(Clone, Debug)]
470pub struct Builder;
471
472impl ExprBuilderInfallibleBuild for Builder {}
473
474impl ExprBuilder for Builder {
475    type Expr = Expr;
476    type BuildError = Infallible;
477    type Data = ();
478    #[cfg(feature = "tolerant-ast")]
479    type ErrorType = Infallible;
480
481    fn with_data(_data: Self::Data) -> Self {
482        Self
483    }
484
485    fn with_maybe_source_loc(self, _: Option<&Loc>) -> Self {
486        self
487    }
488
489    fn loc(&self) -> Option<&Loc> {
490        None
491    }
492
493    fn data(&self) -> &Self::Data {
494        &()
495    }
496
497    /// literal
498    fn val(self, lit: impl Into<ast::Literal>) -> Expr {
499        Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::from_lit(lit.into())))
500    }
501
502    /// principal, action, resource, context
503    fn var(self, var: ast::Var) -> Expr {
504        Expr::ExprNoExt(ExprNoExt::Var(var))
505    }
506
507    /// Template slots
508    fn slot(self, slot: ast::SlotId) -> Expr {
509        Expr::ExprNoExt(ExprNoExt::Slot(slot))
510    }
511
512    /// An extension call with one arg, which is the name of the unknown
513    fn unknown(self, u: ast::Unknown) -> Expr {
514        Expr::ExtFuncCall(ExtFuncCall {
515            call: HashMap::from([("unknown".to_smolstr(), vec![Builder::new().val(u.name)])]),
516        })
517    }
518
519    /// `!`
520    fn not(self, e: Expr) -> Expr {
521        Expr::ExprNoExt(ExprNoExt::Not { arg: Arc::new(e) })
522    }
523
524    /// `-`
525    fn neg(self, e: Expr) -> Expr {
526        Expr::ExprNoExt(ExprNoExt::Neg { arg: Arc::new(e) })
527    }
528
529    /// `==`
530    fn is_eq(self, left: Expr, right: Expr) -> Expr {
531        Expr::ExprNoExt(ExprNoExt::Eq {
532            left: Arc::new(left),
533            right: Arc::new(right),
534        })
535    }
536
537    /// `!=`
538    fn noteq(self, left: Expr, right: Expr) -> Expr {
539        Expr::ExprNoExt(ExprNoExt::NotEq {
540            left: Arc::new(left),
541            right: Arc::new(right),
542        })
543    }
544
545    /// `in`
546    fn is_in_arc(self, left: Arc<Expr>, right: Arc<Expr>) -> Expr {
547        Expr::ExprNoExt(ExprNoExt::In { left, right })
548    }
549
550    /// `<`
551    fn less(self, left: Expr, right: Expr) -> Expr {
552        Expr::ExprNoExt(ExprNoExt::Less {
553            left: Arc::new(left),
554            right: Arc::new(right),
555        })
556    }
557
558    /// `<=`
559    fn lesseq(self, left: Expr, right: Expr) -> Expr {
560        Expr::ExprNoExt(ExprNoExt::LessEq {
561            left: Arc::new(left),
562            right: Arc::new(right),
563        })
564    }
565
566    /// `>`
567    fn greater(self, left: Expr, right: Expr) -> Expr {
568        Expr::ExprNoExt(ExprNoExt::Greater {
569            left: Arc::new(left),
570            right: Arc::new(right),
571        })
572    }
573
574    /// `>=`
575    fn greatereq(self, left: Expr, right: Expr) -> Expr {
576        Expr::ExprNoExt(ExprNoExt::GreaterEq {
577            left: Arc::new(left),
578            right: Arc::new(right),
579        })
580    }
581
582    /// `&&`
583    fn and(self, left: Expr, right: Expr) -> Expr {
584        Expr::ExprNoExt(ExprNoExt::And {
585            left: Arc::new(left),
586            right: Arc::new(right),
587        })
588    }
589
590    /// `||`
591    fn or(self, left: Expr, right: Expr) -> Expr {
592        Expr::ExprNoExt(ExprNoExt::Or {
593            left: Arc::new(left),
594            right: Arc::new(right),
595        })
596    }
597
598    /// `+`
599    fn add(self, left: Expr, right: Expr) -> Expr {
600        Expr::ExprNoExt(ExprNoExt::Add {
601            left: Arc::new(left),
602            right: Arc::new(right),
603        })
604    }
605
606    /// `-`
607    fn sub(self, left: Expr, right: Expr) -> Expr {
608        Expr::ExprNoExt(ExprNoExt::Sub {
609            left: Arc::new(left),
610            right: Arc::new(right),
611        })
612    }
613
614    /// `*`
615    fn mul(self, left: Expr, right: Expr) -> Expr {
616        Expr::ExprNoExt(ExprNoExt::Mul {
617            left: Arc::new(left),
618            right: Arc::new(right),
619        })
620    }
621
622    /// `left.contains(right)`
623    fn contains(self, left: Expr, right: Expr) -> Expr {
624        Expr::ExprNoExt(ExprNoExt::Contains {
625            left: Arc::new(left),
626            right: Arc::new(right),
627        })
628    }
629
630    /// `left.containsAll(right)`
631    fn contains_all(self, left: Expr, right: Expr) -> Expr {
632        Expr::ExprNoExt(ExprNoExt::ContainsAll {
633            left: Arc::new(left),
634            right: Arc::new(right),
635        })
636    }
637
638    /// `left.containsAny(right)`
639    fn contains_any(self, left: Expr, right: Expr) -> Expr {
640        Expr::ExprNoExt(ExprNoExt::ContainsAny {
641            left: Arc::new(left),
642            right: Arc::new(right),
643        })
644    }
645
646    /// `arg.isEmpty()`
647    fn is_empty(self, expr: Expr) -> Expr {
648        Expr::ExprNoExt(ExprNoExt::IsEmpty {
649            arg: Arc::new(expr),
650        })
651    }
652
653    /// `left.getTag(right)`
654    fn get_tag(self, expr: Expr, tag: Expr) -> Expr {
655        Expr::ExprNoExt(ExprNoExt::GetTag {
656            left: Arc::new(expr),
657            right: Arc::new(tag),
658        })
659    }
660
661    /// `left.hasTag(right)`
662    fn has_tag(self, expr: Expr, tag: Expr) -> Expr {
663        Expr::ExprNoExt(ExprNoExt::HasTag {
664            left: Arc::new(expr),
665            right: Arc::new(tag),
666        })
667    }
668
669    /// `left.attr`
670    fn get_attr(self, expr: Expr, attr: SmolStr) -> Expr {
671        Expr::ExprNoExt(ExprNoExt::GetAttr {
672            left: Arc::new(expr),
673            attr,
674        })
675    }
676
677    /// `left.attr` (with an Arc)
678    fn get_attr_arc(self, expr: Arc<Expr>, attr: SmolStr) -> Expr {
679        Expr::ExprNoExt(ExprNoExt::GetAttr { left: expr, attr })
680    }
681
682    /// `left has attr`
683    fn has_attr(self, expr: Expr, attr: SmolStr) -> Expr {
684        Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple {
685            left: Arc::new(expr),
686            attr,
687        }))
688    }
689
690    /// `left has attr` (with an Arc)
691    fn has_attr_arc(self, expr: Arc<Self::Expr>, attr: SmolStr) -> Self::Expr {
692        Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple { left: expr, attr }))
693    }
694
695    /// `left like pattern`
696    fn like(self, expr: Expr, pattern: ast::Pattern) -> Expr {
697        Expr::ExprNoExt(ExprNoExt::Like {
698            left: Arc::new(expr),
699            pattern: pattern.into(),
700        })
701    }
702
703    /// `left is entity_type`
704    fn is_entity_type_arc(self, left: Arc<Expr>, entity_type: ast::EntityType) -> Expr {
705        Expr::ExprNoExt(ExprNoExt::Is {
706            left,
707            entity_type: entity_type.to_smolstr(),
708            in_expr: None,
709        })
710    }
711
712    /// `left is entity_type in entity`
713    fn is_in_entity_type(self, left: Expr, entity_type: ast::EntityType, entity: Expr) -> Expr {
714        Expr::ExprNoExt(ExprNoExt::Is {
715            left: Arc::new(left),
716            entity_type: entity_type.to_smolstr(),
717            in_expr: Some(Arc::new(entity)),
718        })
719    }
720
721    /// `if cond_expr then then_expr else else_expr`
722    fn ite_arc(
723        self,
724        cond_expr: Arc<Self::Expr>,
725        then_expr: Arc<Self::Expr>,
726        else_expr: Arc<Self::Expr>,
727    ) -> Self::Expr {
728        Expr::ExprNoExt(ExprNoExt::If {
729            cond_expr,
730            then_expr,
731            else_expr,
732        })
733    }
734
735    /// e.g. [1+2, !(context has department)]
736    fn set(self, elements: impl IntoIterator<Item = Expr>) -> Expr {
737        Expr::ExprNoExt(ExprNoExt::Set(elements.into_iter().collect()))
738    }
739
740    /// e.g. {foo: 1+2, bar: !(context has department)}
741    fn record(
742        self,
743        map: impl IntoIterator<Item = (SmolStr, Expr)>,
744    ) -> Result<Expr, ast::ExpressionConstructionError> {
745        let mut dedup_map = BTreeMap::new();
746        for (k, v) in map {
747            match dedup_map.entry(k) {
748                btree_map::Entry::Occupied(oentry) => {
749                    return Err(ast::expression_construction_errors::DuplicateKeyError {
750                        key: oentry.key().clone(),
751                        context: "in record literal",
752                    }
753                    .into());
754                }
755                btree_map::Entry::Vacant(ventry) => {
756                    ventry.insert(v);
757                }
758            }
759        }
760        Ok(Expr::ExprNoExt(ExprNoExt::Record(dedup_map)))
761    }
762
763    /// extension function call, including method calls
764    fn call_extension_fn(
765        self,
766        fn_name: ast::Name,
767        args: impl IntoIterator<Item = Expr>,
768    ) -> Result<Expr, Infallible> {
769        Ok(Expr::ExtFuncCall(ExtFuncCall {
770            call: HashMap::from([(fn_name.to_smolstr(), args.into_iter().collect())]),
771        }))
772    }
773
774    #[cfg(feature = "tolerant-ast")]
775    fn error(self, parse_errors: ParseErrors) -> Result<Self::Expr, Self::ErrorType> {
776        Ok(Expr::ExprNoExt(ExprNoExt::Error(
777            AstExprErrorKind::InvalidExpr(parse_errors.to_string()),
778        )))
779    }
780}
781
782impl Expr {
783    /// Substitute entity literals
784    pub fn sub_entity_literals(
785        self,
786        mapping: &BTreeMap<EntityUID, EntityUID>,
787    ) -> Result<Self, JsonDeserializationError> {
788        match self {
789            Expr::ExprNoExt(e) => match e {
790                ExprNoExt::Value(v) => Ok(Expr::ExprNoExt(ExprNoExt::Value(
791                    v.sub_entity_literals(mapping)?,
792                ))),
793                v @ ExprNoExt::Var(_) => Ok(Expr::ExprNoExt(v)),
794                s @ ExprNoExt::Slot(_) => Ok(Expr::ExprNoExt(s)),
795                ExprNoExt::Not { arg } => Ok(Expr::ExprNoExt(ExprNoExt::Not {
796                    arg: Arc::new(Arc::unwrap_or_clone(arg).sub_entity_literals(mapping)?),
797                })),
798                ExprNoExt::Neg { arg } => Ok(Expr::ExprNoExt(ExprNoExt::Neg {
799                    arg: Arc::new(Arc::unwrap_or_clone(arg).sub_entity_literals(mapping)?),
800                })),
801                ExprNoExt::Eq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Eq {
802                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
803                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
804                })),
805                ExprNoExt::NotEq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::NotEq {
806                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
807                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
808                })),
809                ExprNoExt::In { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::In {
810                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
811                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
812                })),
813                ExprNoExt::Less { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Less {
814                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
815                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
816                })),
817                ExprNoExt::LessEq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::LessEq {
818                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
819                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
820                })),
821                ExprNoExt::Greater { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Greater {
822                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
823                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
824                })),
825                ExprNoExt::GreaterEq { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::GreaterEq {
826                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
827                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
828                })),
829                ExprNoExt::And { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::And {
830                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
831                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
832                })),
833                ExprNoExt::Or { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Or {
834                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
835                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
836                })),
837                ExprNoExt::Add { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Add {
838                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
839                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
840                })),
841                ExprNoExt::Sub { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Sub {
842                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
843                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
844                })),
845                ExprNoExt::Mul { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Mul {
846                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
847                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
848                })),
849                ExprNoExt::Contains { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::Contains {
850                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
851                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
852                })),
853                ExprNoExt::ContainsAll { left, right } => {
854                    Ok(Expr::ExprNoExt(ExprNoExt::ContainsAll {
855                        left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
856                        right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
857                    }))
858                }
859                ExprNoExt::ContainsAny { left, right } => {
860                    Ok(Expr::ExprNoExt(ExprNoExt::ContainsAny {
861                        left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
862                        right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
863                    }))
864                }
865                ExprNoExt::IsEmpty { arg } => Ok(Expr::ExprNoExt(ExprNoExt::IsEmpty {
866                    arg: Arc::new(Arc::unwrap_or_clone(arg).sub_entity_literals(mapping)?),
867                })),
868                ExprNoExt::GetTag { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::GetTag {
869                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
870                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
871                })),
872                ExprNoExt::HasTag { left, right } => Ok(Expr::ExprNoExt(ExprNoExt::HasTag {
873                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
874                    right: Arc::new(Arc::unwrap_or_clone(right).sub_entity_literals(mapping)?),
875                })),
876                ExprNoExt::GetAttr { left, attr } => Ok(Expr::ExprNoExt(ExprNoExt::GetAttr {
877                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
878                    attr,
879                })),
880                ExprNoExt::HasAttr(repr) => match repr {
881                    HasAttrRepr::Simple { left, attr } => {
882                        Ok(Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple {
883                            left: Arc::new(
884                                Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?,
885                            ),
886                            attr,
887                        })))
888                    }
889                    HasAttrRepr::Extended { left, attr } => {
890                        Ok(Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
891                            left: Arc::new(
892                                Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?,
893                            ),
894                            attr,
895                        })))
896                    }
897                },
898                ExprNoExt::Like { left, pattern } => Ok(Expr::ExprNoExt(ExprNoExt::Like {
899                    left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
900                    pattern,
901                })),
902                ExprNoExt::Is {
903                    left,
904                    entity_type,
905                    in_expr,
906                } => match in_expr {
907                    Some(in_expr) => Ok(Expr::ExprNoExt(ExprNoExt::Is {
908                        left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
909                        entity_type,
910                        in_expr: Some(Arc::new(
911                            Arc::unwrap_or_clone(in_expr).sub_entity_literals(mapping)?,
912                        )),
913                    })),
914                    None => Ok(Expr::ExprNoExt(ExprNoExt::Is {
915                        left: Arc::new(Arc::unwrap_or_clone(left).sub_entity_literals(mapping)?),
916                        entity_type,
917                        in_expr: None,
918                    })),
919                },
920                ExprNoExt::If {
921                    cond_expr,
922                    then_expr,
923                    else_expr,
924                } => Ok(Expr::ExprNoExt(ExprNoExt::If {
925                    cond_expr: Arc::new(
926                        Arc::unwrap_or_clone(cond_expr).sub_entity_literals(mapping)?,
927                    ),
928                    then_expr: Arc::new(
929                        Arc::unwrap_or_clone(then_expr).sub_entity_literals(mapping)?,
930                    ),
931                    else_expr: Arc::new(
932                        Arc::unwrap_or_clone(else_expr).sub_entity_literals(mapping)?,
933                    ),
934                })),
935                ExprNoExt::Set(v) => {
936                    let mut new_v = vec![];
937                    for e in v {
938                        new_v.push(e.sub_entity_literals(mapping)?);
939                    }
940                    Ok(Expr::ExprNoExt(ExprNoExt::Set(new_v)))
941                }
942                ExprNoExt::Record(m) => {
943                    let mut new_m = BTreeMap::new();
944                    for (k, v) in m {
945                        new_m.insert(k, v.sub_entity_literals(mapping)?);
946                    }
947                    Ok(Expr::ExprNoExt(ExprNoExt::Record(new_m)))
948                }
949                #[cfg(feature = "tolerant-ast")]
950                ExprNoExt::Error(_) => Err(JsonDeserializationError::ASTErrorNode),
951            },
952            Expr::ExtFuncCall(e_fn_call) => {
953                let mut new_m = HashMap::new();
954                for (k, v) in e_fn_call.call {
955                    let mut new_v = vec![];
956                    for e in v {
957                        new_v.push(e.sub_entity_literals(mapping)?);
958                    }
959                    new_m.insert(k, new_v);
960                }
961                Ok(Expr::ExtFuncCall(ExtFuncCall { call: new_m }))
962            }
963        }
964    }
965
966    /// Compute the height of this expression tree, defined as the number of
967    /// edges on the longest root-to-leaf path through nested `Expr` and
968    /// `CedarValueJson` nodes.
969    ///
970    /// Leaf nodes (`Var`, `Slot`, etc.) have height 0.
971    /// An empty `Set` or empty `Record` expression also has height 0.
972    ///
973    /// For internal nodes (`Not`, `Eq`, `If`, `Set`, `Record`, etc.)
974    /// with at least one child, the height is 1 + the maximum height among
975    /// all children.
976    ///
977    /// Uses an iterative depth-first traversal rather than recursion.
978    pub fn height(&self) -> usize {
979        // Stack of (node, depth) pairs.
980        // Start with this expression at depth 0.
981        let mut stack: Vec<(&Expr, usize)> = vec![(self, 0)];
982        let mut max_depth: usize = 0;
983
984        while let Some((expr, depth)) = stack.pop() {
985            max_depth = max_depth.max(depth);
986            let child_depth = depth + 1;
987
988            match expr {
989                Expr::ExprNoExt(e) => match e {
990                    ExprNoExt::Value(v) => {
991                        // A literal value may itself be a nested structure
992                        // (e.g., a set of records). Use CedarValueJson::height()
993                        // to account for its internal depth.
994                        let value_height = depth + v.height();
995                        max_depth = max_depth.max(value_height);
996                    }
997                    ExprNoExt::Var(_) | ExprNoExt::Slot(_) => {}
998                    #[cfg(feature = "tolerant-ast")]
999                    ExprNoExt::Error(_) => {}
1000                    ExprNoExt::Not { arg }
1001                    | ExprNoExt::Neg { arg }
1002                    | ExprNoExt::IsEmpty { arg } => {
1003                        stack.push((arg, child_depth));
1004                    }
1005                    ExprNoExt::Eq { left, right }
1006                    | ExprNoExt::NotEq { left, right }
1007                    | ExprNoExt::In { left, right }
1008                    | ExprNoExt::Less { left, right }
1009                    | ExprNoExt::LessEq { left, right }
1010                    | ExprNoExt::Greater { left, right }
1011                    | ExprNoExt::GreaterEq { left, right }
1012                    | ExprNoExt::And { left, right }
1013                    | ExprNoExt::Or { left, right }
1014                    | ExprNoExt::Add { left, right }
1015                    | ExprNoExt::Sub { left, right }
1016                    | ExprNoExt::Mul { left, right }
1017                    | ExprNoExt::Contains { left, right }
1018                    | ExprNoExt::ContainsAll { left, right }
1019                    | ExprNoExt::ContainsAny { left, right }
1020                    | ExprNoExt::GetTag { left, right }
1021                    | ExprNoExt::HasTag { left, right } => {
1022                        stack.push((left, child_depth));
1023                        stack.push((right, child_depth));
1024                    }
1025                    ExprNoExt::GetAttr { left, .. } => {
1026                        stack.push((left, child_depth));
1027                    }
1028                    ExprNoExt::HasAttr(repr) => match repr {
1029                        HasAttrRepr::Simple { left, .. } | HasAttrRepr::Extended { left, .. } => {
1030                            // For the `Extended` variant, the attr list is
1031                            // flat (a list of strings), not nested sub-expressions,
1032                            // so only `left` contributes to EST height.
1033                            // Note that the AST desugars this into a
1034                            // nested tree of `and`/`hasAttr`/`getAttr`
1035                            // nodes, which will result in a deeper tree.
1036                            // However, the conversion to the AST uses an iterator + fold.
1037                            stack.push((left, child_depth));
1038                        }
1039                    },
1040                    ExprNoExt::Like { left, .. } => {
1041                        stack.push((left, child_depth));
1042                    }
1043                    ExprNoExt::Is { left, in_expr, .. } => {
1044                        stack.push((left, child_depth));
1045                        if let Some(in_e) = in_expr {
1046                            stack.push((in_e, child_depth));
1047                        }
1048                    }
1049                    ExprNoExt::If {
1050                        cond_expr,
1051                        then_expr,
1052                        else_expr,
1053                    } => {
1054                        stack.push((cond_expr, child_depth));
1055                        stack.push((then_expr, child_depth));
1056                        stack.push((else_expr, child_depth));
1057                    }
1058                    ExprNoExt::Set(elements) => {
1059                        for el in elements {
1060                            stack.push((el, child_depth));
1061                        }
1062                    }
1063                    ExprNoExt::Record(map) => {
1064                        for v in map.values() {
1065                            stack.push((v, child_depth));
1066                        }
1067                    }
1068                },
1069                Expr::ExtFuncCall(ExtFuncCall { call }) => {
1070                    for args in call.values() {
1071                        for arg in args {
1072                            stack.push((arg, child_depth));
1073                        }
1074                    }
1075                }
1076            }
1077        }
1078
1079        max_depth
1080    }
1081}
1082
1083impl Expr {
1084    /// Convert this `est::Expr` into an expression of type `B::Expr` using the builder `B`,
1085    /// In the case of an error, returns the builder's own `BuildError`.
1086    pub fn try_into_expr<B>(self) -> Result<B::Expr, B::BuildError>
1087    where
1088        B: ExprBuilder,
1089        B::BuildError: From<FromJsonError>,
1090    {
1091        // This implementation looks a lot like try_into_ast but:
1092        // - it doesn't have the policy ID information (could be injected after though)
1093        // - it uses the fallible extension call constructor; validation is handled by builder
1094        // - it doesn't directly desugars is_in, rather defers to the builder
1095        // This implementation is used by PST. Using this for AST would result in a different
1096        // behavior than try_into_ast, particularly for the second point which would fail
1097        // conversions that are currently non-failing (method call with no arguments).
1098        let builder = B::new();
1099        match self {
1100            Expr::ExprNoExt(ExprNoExt::Value(jsonvalue)) => {
1101                // Through AST expressions to convert to B::Expr
1102                let ast_expr: ast::Expr = jsonvalue
1103                    .into_expr(&|| JsonDeserializationErrorContext::Policy {
1104                        id: ast::PolicyID::from_string(""),
1105                    })
1106                    .map(ast::Expr::from)
1107                    .map_err(|j| B::BuildError::from(j.into()))?;
1108                ast_expr.try_into_expr::<B>()
1109            }
1110            Expr::ExprNoExt(ExprNoExt::Var(var)) => Ok(builder.var(var)),
1111            Expr::ExprNoExt(ExprNoExt::Slot(slot)) => Ok(builder.slot(slot)),
1112            Expr::ExprNoExt(ExprNoExt::Not { arg }) => {
1113                Ok(builder.not(Arc::unwrap_or_clone(arg).try_into_expr::<B>()?))
1114            }
1115            Expr::ExprNoExt(ExprNoExt::Neg { arg }) => {
1116                Ok(builder.neg(Arc::unwrap_or_clone(arg).try_into_expr::<B>()?))
1117            }
1118            Expr::ExprNoExt(ExprNoExt::Eq { left, right }) => Ok(builder.is_eq(
1119                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1120                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1121            )),
1122            Expr::ExprNoExt(ExprNoExt::NotEq { left, right }) => Ok(builder.noteq(
1123                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1124                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1125            )),
1126            Expr::ExprNoExt(ExprNoExt::In { left, right }) => Ok(builder.is_in(
1127                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1128                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1129            )),
1130            Expr::ExprNoExt(ExprNoExt::Less { left, right }) => Ok(builder.less(
1131                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1132                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1133            )),
1134            Expr::ExprNoExt(ExprNoExt::LessEq { left, right }) => Ok(builder.lesseq(
1135                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1136                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1137            )),
1138            Expr::ExprNoExt(ExprNoExt::Greater { left, right }) => Ok(builder.greater(
1139                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1140                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1141            )),
1142            Expr::ExprNoExt(ExprNoExt::GreaterEq { left, right }) => Ok(builder.greatereq(
1143                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1144                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1145            )),
1146            Expr::ExprNoExt(ExprNoExt::And { left, right }) => Ok(builder.and(
1147                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1148                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1149            )),
1150            Expr::ExprNoExt(ExprNoExt::Or { left, right }) => Ok(builder.or(
1151                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1152                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1153            )),
1154            Expr::ExprNoExt(ExprNoExt::Add { left, right }) => Ok(builder.add(
1155                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1156                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1157            )),
1158            Expr::ExprNoExt(ExprNoExt::Sub { left, right }) => Ok(builder.sub(
1159                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1160                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1161            )),
1162            Expr::ExprNoExt(ExprNoExt::Mul { left, right }) => Ok(builder.mul(
1163                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1164                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1165            )),
1166            Expr::ExprNoExt(ExprNoExt::Contains { left, right }) => Ok(builder.contains(
1167                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1168                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1169            )),
1170            Expr::ExprNoExt(ExprNoExt::ContainsAll { left, right }) => Ok(builder.contains_all(
1171                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1172                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1173            )),
1174            Expr::ExprNoExt(ExprNoExt::ContainsAny { left, right }) => Ok(builder.contains_any(
1175                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1176                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1177            )),
1178            Expr::ExprNoExt(ExprNoExt::IsEmpty { arg }) => {
1179                Ok(builder.is_empty(Arc::unwrap_or_clone(arg).try_into_expr::<B>()?))
1180            }
1181            Expr::ExprNoExt(ExprNoExt::GetTag { left, right }) => Ok(builder.get_tag(
1182                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1183                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1184            )),
1185            Expr::ExprNoExt(ExprNoExt::HasTag { left, right }) => Ok(builder.has_tag(
1186                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1187                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1188            )),
1189            Expr::ExprNoExt(ExprNoExt::GetAttr { left, attr }) => {
1190                Ok(builder.get_attr(Arc::unwrap_or_clone(left).try_into_expr::<B>()?, attr))
1191            }
1192            Expr::ExprNoExt(ExprNoExt::HasAttr(repr)) => match repr {
1193                HasAttrRepr::Simple { left, attr } => {
1194                    Ok(builder.has_attr(Arc::unwrap_or_clone(left).try_into_expr::<B>()?, attr))
1195                }
1196                HasAttrRepr::Extended { left, attr } => Ok(builder
1197                    .extended_has_attr(Arc::unwrap_or_clone(left).try_into_expr::<B>()?, attr)),
1198            },
1199            Expr::ExprNoExt(ExprNoExt::Like { left, pattern }) => Ok(builder.like(
1200                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1201                ast::Pattern::from(pattern.as_slice()),
1202            )),
1203            Expr::ExprNoExt(ExprNoExt::Is {
1204                left,
1205                entity_type,
1206                in_expr,
1207            }) => {
1208                let entity_type_name = ast::EntityType::from_normalized_str(&entity_type)
1209                    .map_err(FromJsonError::InvalidEntityType)?;
1210                let left_expr = Arc::unwrap_or_clone(left).try_into_expr::<B>()?;
1211                match in_expr {
1212                    Some(in_expr) => Ok(builder.is_in_entity_type(
1213                        left_expr,
1214                        entity_type_name,
1215                        Arc::unwrap_or_clone(in_expr).try_into_expr::<B>()?,
1216                    )),
1217                    None => Ok(builder.is_entity_type(left_expr, entity_type_name)),
1218                }
1219            }
1220            Expr::ExprNoExt(ExprNoExt::If {
1221                cond_expr,
1222                then_expr,
1223                else_expr,
1224            }) => Ok(builder.ite(
1225                Arc::unwrap_or_clone(cond_expr).try_into_expr::<B>()?,
1226                Arc::unwrap_or_clone(then_expr).try_into_expr::<B>()?,
1227                Arc::unwrap_or_clone(else_expr).try_into_expr::<B>()?,
1228            )),
1229            Expr::ExprNoExt(ExprNoExt::Set(elements)) => Ok(builder.set(
1230                elements
1231                    .into_iter()
1232                    .map(|el| el.try_into_expr::<B>())
1233                    .collect::<Result<Vec<_>, _>>()?,
1234            )),
1235            Expr::ExprNoExt(ExprNoExt::Record(map)) =>
1236            {
1237                #[expect(
1238                    clippy::expect_used,
1239                    reason = "can't have duplicate keys here because the input was already a HashMap"
1240                )]
1241                Ok(builder
1242                    .record(
1243                        map.into_iter()
1244                            .map(|(k, v)| Ok((k, v.try_into_expr::<B>()?)))
1245                            .collect::<Result<Vec<_>, B::BuildError>>()?,
1246                    )
1247                    .expect("map should not have duplicate keys"))
1248            }
1249            Expr::ExtFuncCall(e) => {
1250                let (fn_name, args) = e.try_into_components()?;
1251                let fn_name = Name::from_normalized_str(&fn_name).map_err(|errs| {
1252                    JsonDeserializationError::parse_escape(EscapeKind::Extension, fn_name, errs)
1253                        .into()
1254                })?;
1255                // unlike into_ast, some validation is handled by calling the fallible version
1256                // of the extension call builder
1257                builder.call_extension_fn(
1258                    fn_name,
1259                    args.into_iter()
1260                        .map(|arg| arg.try_into_expr::<B>())
1261                        .collect::<Result<Vec<_>, _>>()?,
1262                )
1263            }
1264            #[cfg(feature = "tolerant-ast")]
1265            Expr::ExprNoExt(ExprNoExt::Error(_)) => Err(FromJsonError::ASTErrorNode.into()),
1266        }
1267    }
1268
1269    /// Attempt to convert this `est::Expr` into an `ast::Expr`
1270    ///
1271    /// `id`: the ID of the policy this `Expr` belongs to, used only for reporting errors
1272    pub fn try_into_ast(self, id: &ast::PolicyID) -> Result<ast::Expr, FromJsonError> {
1273        match self {
1274            Expr::ExprNoExt(ExprNoExt::Value(jsonvalue)) => jsonvalue
1275                .into_expr(&|| JsonDeserializationErrorContext::Policy { id: id.clone() })
1276                .map(Into::into)
1277                .map_err(Into::into),
1278            Expr::ExprNoExt(ExprNoExt::Var(var)) => Ok(ast::Expr::var(var)),
1279            Expr::ExprNoExt(ExprNoExt::Slot(slot)) => Ok(ast::Expr::slot(slot)),
1280            Expr::ExprNoExt(ExprNoExt::Not { arg }) => {
1281                Ok(ast::Expr::not(Arc::unwrap_or_clone(arg).try_into_ast(id)?))
1282            }
1283            Expr::ExprNoExt(ExprNoExt::Neg { arg }) => {
1284                Ok(ast::Expr::neg(Arc::unwrap_or_clone(arg).try_into_ast(id)?))
1285            }
1286            Expr::ExprNoExt(ExprNoExt::Eq { left, right }) => Ok(ast::Expr::is_eq(
1287                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1288                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1289            )),
1290            Expr::ExprNoExt(ExprNoExt::NotEq { left, right }) => Ok(ast::Expr::noteq(
1291                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1292                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1293            )),
1294            Expr::ExprNoExt(ExprNoExt::In { left, right }) => Ok(ast::Expr::is_in(
1295                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1296                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1297            )),
1298            Expr::ExprNoExt(ExprNoExt::Less { left, right }) => Ok(ast::Expr::less(
1299                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1300                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1301            )),
1302            Expr::ExprNoExt(ExprNoExt::LessEq { left, right }) => Ok(ast::Expr::lesseq(
1303                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1304                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1305            )),
1306            Expr::ExprNoExt(ExprNoExt::Greater { left, right }) => Ok(ast::Expr::greater(
1307                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1308                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1309            )),
1310            Expr::ExprNoExt(ExprNoExt::GreaterEq { left, right }) => Ok(ast::Expr::greatereq(
1311                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1312                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1313            )),
1314            Expr::ExprNoExt(ExprNoExt::And { left, right }) => Ok(ast::Expr::and(
1315                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1316                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1317            )),
1318            Expr::ExprNoExt(ExprNoExt::Or { left, right }) => Ok(ast::Expr::or(
1319                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1320                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1321            )),
1322            Expr::ExprNoExt(ExprNoExt::Add { left, right }) => Ok(ast::Expr::add(
1323                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1324                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1325            )),
1326            Expr::ExprNoExt(ExprNoExt::Sub { left, right }) => Ok(ast::Expr::sub(
1327                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1328                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1329            )),
1330            Expr::ExprNoExt(ExprNoExt::Mul { left, right }) => Ok(ast::Expr::mul(
1331                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1332                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1333            )),
1334            Expr::ExprNoExt(ExprNoExt::Contains { left, right }) => Ok(ast::Expr::contains(
1335                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1336                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1337            )),
1338            Expr::ExprNoExt(ExprNoExt::ContainsAll { left, right }) => Ok(ast::Expr::contains_all(
1339                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1340                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1341            )),
1342            Expr::ExprNoExt(ExprNoExt::ContainsAny { left, right }) => Ok(ast::Expr::contains_any(
1343                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1344                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1345            )),
1346            Expr::ExprNoExt(ExprNoExt::IsEmpty { arg }) => Ok(ast::Expr::is_empty(
1347                Arc::unwrap_or_clone(arg).try_into_ast(id)?,
1348            )),
1349            Expr::ExprNoExt(ExprNoExt::GetTag { left, right }) => Ok(ast::Expr::get_tag(
1350                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1351                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1352            )),
1353            Expr::ExprNoExt(ExprNoExt::HasTag { left, right }) => Ok(ast::Expr::has_tag(
1354                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1355                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1356            )),
1357            Expr::ExprNoExt(ExprNoExt::GetAttr { left, attr }) => Ok(ast::Expr::get_attr(
1358                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1359                attr,
1360            )),
1361            Expr::ExprNoExt(ExprNoExt::HasAttr(repr)) => match repr {
1362                HasAttrRepr::Simple { left, attr } => Ok(ast::Expr::has_attr(
1363                    Arc::unwrap_or_clone(left).try_into_ast(id)?,
1364                    attr,
1365                )),
1366                HasAttrRepr::Extended { left, attr } => Ok(ast::ExprBuilder::new()
1367                    .extended_has_attr(Arc::unwrap_or_clone(left).try_into_ast(id)?, attr)),
1368            },
1369            Expr::ExprNoExt(ExprNoExt::Like { left, pattern }) => Ok(ast::Expr::like(
1370                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1371                crate::ast::Pattern::from(pattern.as_slice()),
1372            )),
1373            Expr::ExprNoExt(ExprNoExt::Is {
1374                left,
1375                entity_type,
1376                in_expr,
1377            }) => ast::EntityType::from_normalized_str(entity_type.as_str())
1378                .map_err(FromJsonError::InvalidEntityType)
1379                .and_then(|entity_type_name| {
1380                    let left: ast::Expr = Arc::unwrap_or_clone(left).try_into_ast(id)?;
1381                    match in_expr {
1382                        Some(in_expr) => Ok(ast::ExprBuilder::new().is_in_entity_type(
1383                            left,
1384                            entity_type_name,
1385                            Arc::unwrap_or_clone(in_expr).try_into_ast(id)?,
1386                        )),
1387                        None => Ok(ast::ExprBuilder::new().is_entity_type(left, entity_type_name)),
1388                    }
1389                }),
1390            Expr::ExprNoExt(ExprNoExt::If {
1391                cond_expr,
1392                then_expr,
1393                else_expr,
1394            }) => Ok(ast::Expr::ite(
1395                Arc::unwrap_or_clone(cond_expr).try_into_ast(id)?,
1396                Arc::unwrap_or_clone(then_expr).try_into_ast(id)?,
1397                Arc::unwrap_or_clone(else_expr).try_into_ast(id)?,
1398            )),
1399            Expr::ExprNoExt(ExprNoExt::Set(elements)) => Ok(ast::Expr::set(
1400                elements
1401                    .into_iter()
1402                    .map(|el| el.try_into_ast(id))
1403                    .collect::<Result<Vec<_>, FromJsonError>>()?,
1404            )),
1405            Expr::ExprNoExt(ExprNoExt::Record(map)) =>
1406            {
1407                #[expect(
1408                    clippy::expect_used,
1409                    reason = "can't have duplicate keys here because the input was already a HashMap"
1410                )]
1411                Ok(ast::Expr::record(
1412                    map.into_iter()
1413                        .map(|(k, v)| Ok((k, v.try_into_ast(id)?)))
1414                        .collect::<Result<HashMap<SmolStr, _>, FromJsonError>>()?,
1415                )
1416                .expect("can't have duplicate keys here because the input was already a HashMap"))
1417            }
1418            Expr::ExtFuncCall(e) => {
1419                let (fn_name, args) = e.try_into_components()?;
1420                let fn_name = Name::from_normalized_str(&fn_name).map_err(|errs| {
1421                    JsonDeserializationError::parse_escape(EscapeKind::Extension, fn_name, errs)
1422                })?;
1423                if ExtStyles::is_known_extension_func_name(&fn_name) {
1424                    Ok(ast::Expr::call_extension_fn(
1425                        fn_name,
1426                        args.into_iter()
1427                            .map(|arg| arg.try_into_ast(id))
1428                            .collect::<Result<_, _>>()?,
1429                    ))
1430                } else {
1431                    Err(FromJsonError::UnknownExtensionFunction(fn_name))
1432                }
1433            }
1434            #[cfg(feature = "tolerant-ast")]
1435            Expr::ExprNoExt(ExprNoExt::Error(_)) => Err(FromJsonError::ASTErrorNode),
1436        }
1437    }
1438}
1439
1440impl From<ast::Literal> for Expr {
1441    fn from(lit: ast::Literal) -> Expr {
1442        Builder::new().val(lit)
1443    }
1444}
1445
1446impl From<ast::Var> for Expr {
1447    fn from(var: ast::Var) -> Expr {
1448        Builder::new().var(var)
1449    }
1450}
1451
1452impl From<ast::SlotId> for Expr {
1453    fn from(slot: ast::SlotId) -> Expr {
1454        Builder::new().slot(slot)
1455    }
1456}
1457
1458impl TryFrom<&Node<Option<cst::Expr>>> for Expr {
1459    type Error = ParseErrors;
1460    fn try_from(e: &Node<Option<cst::Expr>>) -> Result<Expr, ParseErrors> {
1461        e.to_expr::<Builder>()
1462    }
1463}
1464
1465impl std::fmt::Display for Expr {
1466    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1467        match self {
1468            Self::ExprNoExt(e) => write!(f, "{e}"),
1469            Self::ExtFuncCall(e) => write!(f, "{e}"),
1470        }
1471    }
1472}
1473
1474impl BoundedDisplay for Expr {
1475    fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
1476        match self {
1477            Self::ExprNoExt(e) => BoundedDisplay::fmt(e, f, n),
1478            Self::ExtFuncCall(e) => BoundedDisplay::fmt(e, f, n),
1479        }
1480    }
1481}
1482
1483fn display_cedarvaluejson(
1484    f: &mut impl std::fmt::Write,
1485    v: &CedarValueJson,
1486    n: Option<usize>,
1487) -> std::fmt::Result {
1488    match v {
1489        // Add parentheses around negative numeric literals otherwise
1490        // round-tripping fuzzer fails for expressions like `(-1)["a"]`.
1491        CedarValueJson::Long(i) if *i < 0 => write!(f, "({i})"),
1492        CedarValueJson::Long(i) => write!(f, "{i}"),
1493        CedarValueJson::Bool(b) => write!(f, "{b}"),
1494        CedarValueJson::String(s) => write!(f, "\"{}\"", s.escape_debug()),
1495        CedarValueJson::EntityEscape { __entity } => {
1496            match ast::EntityUID::try_from(__entity.clone()) {
1497                Ok(euid) => write!(f, "{euid}"),
1498                Err(e) => write!(f, "(invalid entity uid: {e})"),
1499            }
1500        }
1501        CedarValueJson::ExprEscape { __expr } => write!(f, "({__expr})"),
1502        CedarValueJson::ExtnEscape {
1503            __extn: FnAndArgs::Single { ext_fn, arg },
1504        } => {
1505            // search for the name and callstyle
1506            let style = Extensions::all_available().all_funcs().find_map(|f| {
1507                if &f.name().to_smolstr() == ext_fn {
1508                    Some(f.style())
1509                } else {
1510                    None
1511                }
1512            });
1513            match style {
1514                Some(ast::CallStyle::MethodStyle) => {
1515                    display_cedarvaluejson(f, arg, n)?;
1516                    write!(f, ".{ext_fn}()")?;
1517                    Ok(())
1518                }
1519                Some(ast::CallStyle::FunctionStyle) | None => {
1520                    write!(f, "{ext_fn}(")?;
1521                    display_cedarvaluejson(f, arg, n)?;
1522                    write!(f, ")")?;
1523                    Ok(())
1524                }
1525            }
1526        }
1527        CedarValueJson::ExtnEscape {
1528            __extn: FnAndArgs::Multi { ext_fn, args },
1529        } => {
1530            // search for the name and callstyle
1531            let style = Extensions::all_available().all_funcs().find_map(|f| {
1532                if &f.name().to_smolstr() == ext_fn {
1533                    Some(f.style())
1534                } else {
1535                    None
1536                }
1537            });
1538            match style {
1539                Some(ast::CallStyle::MethodStyle) => {
1540                    #[expect(
1541                        clippy::indexing_slicing,
1542                        reason = "method-style calls must have more than one argument"
1543                    )]
1544                    display_cedarvaluejson(f, &args[0], n)?;
1545                    write!(f, ".{ext_fn}(")?;
1546                    #[expect(
1547                        clippy::indexing_slicing,
1548                        reason = "method-style calls must have more than one argument"
1549                    )]
1550                    match &args[1..] {
1551                        [] => {}
1552                        [args @ .., last] => {
1553                            for arg in args {
1554                                display_cedarvaluejson(f, arg, n)?;
1555                                write!(f, ", ")?;
1556                            }
1557                            display_cedarvaluejson(f, last, n)?;
1558                        }
1559                    }
1560                    write!(f, ")")?;
1561                    Ok(())
1562                }
1563                Some(ast::CallStyle::FunctionStyle) | None => {
1564                    write!(f, "{ext_fn}(")?;
1565                    match &args[..] {
1566                        [] => {}
1567                        [args @ .., last] => {
1568                            for arg in args {
1569                                display_cedarvaluejson(f, arg, n)?;
1570                                write!(f, ", ")?;
1571                            }
1572                            display_cedarvaluejson(f, last, n)?;
1573                        }
1574                    }
1575                    write!(f, ")")?;
1576                    Ok(())
1577                }
1578            }
1579        }
1580        CedarValueJson::Set(v) => {
1581            match n {
1582                Some(n) if v.len() > n => {
1583                    // truncate to n elements
1584                    write!(f, "[")?;
1585                    for val in v.iter().take(n) {
1586                        display_cedarvaluejson(f, val, Some(n))?;
1587                        write!(f, ", ")?;
1588                    }
1589                    write!(f, "..]")?;
1590                    Ok(())
1591                }
1592                _ => {
1593                    // no truncation
1594                    write!(f, "[")?;
1595                    for (i, val) in v.iter().enumerate() {
1596                        display_cedarvaluejson(f, val, n)?;
1597                        if i < v.len() - 1 {
1598                            write!(f, ", ")?;
1599                        }
1600                    }
1601                    write!(f, "]")?;
1602                    Ok(())
1603                }
1604            }
1605        }
1606        CedarValueJson::Record(r) => {
1607            match n {
1608                Some(n) if r.len() > n => {
1609                    // truncate to n key-value pairs
1610                    write!(f, "{{")?;
1611                    for (k, v) in r.iter().take(n) {
1612                        write!(f, "\"{}\": ", k.escape_debug())?;
1613                        display_cedarvaluejson(f, v, Some(n))?;
1614                        write!(f, ", ")?;
1615                    }
1616                    write!(f, "..}}")?;
1617                    Ok(())
1618                }
1619                _ => {
1620                    // no truncation
1621                    write!(f, "{{")?;
1622                    for (i, (k, v)) in r.iter().enumerate() {
1623                        write!(f, "\"{}\": ", k.escape_debug())?;
1624                        display_cedarvaluejson(f, v, n)?;
1625                        if i < r.len() - 1 {
1626                            write!(f, ", ")?;
1627                        }
1628                    }
1629                    write!(f, "}}")?;
1630                    Ok(())
1631                }
1632            }
1633        }
1634        CedarValueJson::Null => {
1635            write!(f, "null")?;
1636            Ok(())
1637        }
1638    }
1639}
1640
1641impl std::fmt::Display for ExprNoExt {
1642    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1643        BoundedDisplay::fmt_unbounded(self, f)
1644    }
1645}
1646
1647impl BoundedDisplay for ExprNoExt {
1648    fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
1649        match &self {
1650            ExprNoExt::Value(v) => display_cedarvaluejson(f, v, n),
1651            ExprNoExt::Var(v) => write!(f, "{v}"),
1652            ExprNoExt::Slot(id) => write!(f, "{id}"),
1653            ExprNoExt::Not { arg } => {
1654                write!(f, "!")?;
1655                maybe_with_parens(f, arg, n)
1656            }
1657            ExprNoExt::Neg { arg } => {
1658                // Always add parentheses instead of calling
1659                // `maybe_with_parens`.
1660                // This makes sure that we always get a negation operation back
1661                // (as opposed to e.g., a negative number) when parsing the
1662                // printed form, thus preserving the round-tripping property.
1663                write!(f, "-({arg})")
1664            }
1665            ExprNoExt::Eq { left, right } => {
1666                maybe_with_parens(f, left, n)?;
1667                write!(f, " == ")?;
1668                maybe_with_parens(f, right, n)
1669            }
1670            ExprNoExt::NotEq { left, right } => {
1671                maybe_with_parens(f, left, n)?;
1672                write!(f, " != ")?;
1673                maybe_with_parens(f, right, n)
1674            }
1675            ExprNoExt::In { left, right } => {
1676                maybe_with_parens(f, left, n)?;
1677                write!(f, " in ")?;
1678                maybe_with_parens(f, right, n)
1679            }
1680            ExprNoExt::Less { left, right } => {
1681                maybe_with_parens(f, left, n)?;
1682                write!(f, " < ")?;
1683                maybe_with_parens(f, right, n)
1684            }
1685            ExprNoExt::LessEq { left, right } => {
1686                maybe_with_parens(f, left, n)?;
1687                write!(f, " <= ")?;
1688                maybe_with_parens(f, right, n)
1689            }
1690            ExprNoExt::Greater { left, right } => {
1691                maybe_with_parens(f, left, n)?;
1692                write!(f, " > ")?;
1693                maybe_with_parens(f, right, n)
1694            }
1695            ExprNoExt::GreaterEq { left, right } => {
1696                maybe_with_parens(f, left, n)?;
1697                write!(f, " >= ")?;
1698                maybe_with_parens(f, right, n)
1699            }
1700            ExprNoExt::And { left, right } => {
1701                maybe_with_parens(f, left, n)?;
1702                write!(f, " && ")?;
1703                maybe_with_parens(f, right, n)
1704            }
1705            ExprNoExt::Or { left, right } => {
1706                maybe_with_parens(f, left, n)?;
1707                write!(f, " || ")?;
1708                maybe_with_parens(f, right, n)
1709            }
1710            ExprNoExt::Add { left, right } => {
1711                maybe_with_parens(f, left, n)?;
1712                write!(f, " + ")?;
1713                maybe_with_parens(f, right, n)
1714            }
1715            ExprNoExt::Sub { left, right } => {
1716                maybe_with_parens(f, left, n)?;
1717                write!(f, " - ")?;
1718                maybe_with_parens(f, right, n)
1719            }
1720            ExprNoExt::Mul { left, right } => {
1721                maybe_with_parens(f, left, n)?;
1722                write!(f, " * ")?;
1723                maybe_with_parens(f, right, n)
1724            }
1725            ExprNoExt::Contains { left, right } => {
1726                maybe_with_parens(f, left, n)?;
1727                write!(f, ".contains({right})")
1728            }
1729            ExprNoExt::ContainsAll { left, right } => {
1730                maybe_with_parens(f, left, n)?;
1731                write!(f, ".containsAll({right})")
1732            }
1733            ExprNoExt::ContainsAny { left, right } => {
1734                maybe_with_parens(f, left, n)?;
1735                write!(f, ".containsAny({right})")
1736            }
1737            ExprNoExt::IsEmpty { arg } => {
1738                maybe_with_parens(f, arg, n)?;
1739                write!(f, ".isEmpty()")
1740            }
1741            ExprNoExt::GetTag { left, right } => {
1742                maybe_with_parens(f, left, n)?;
1743                write!(f, ".getTag({right})")
1744            }
1745            ExprNoExt::HasTag { left, right } => {
1746                maybe_with_parens(f, left, n)?;
1747                write!(f, ".hasTag({right})")
1748            }
1749            ExprNoExt::GetAttr { left, attr } => {
1750                maybe_with_parens(f, left, n)?;
1751                if is_normalized_ident(attr) {
1752                    write!(f, ".{}", attr)
1753                } else {
1754                    write!(f, "[\"{}\"]", attr.escape_debug())
1755                }
1756            }
1757            ExprNoExt::HasAttr(repr) => match repr {
1758                HasAttrRepr::Simple { left, attr } => {
1759                    maybe_with_parens(f, left, n)?;
1760                    if is_normalized_ident(attr) {
1761                        write!(f, " has {}", attr)
1762                    } else {
1763                        write!(f, " has \"{}\"", attr.escape_debug())
1764                    }
1765                }
1766                HasAttrRepr::Extended { left, attr } => {
1767                    maybe_with_parens(f, left, n)?;
1768                    if is_normalized_ident(&attr.head) {
1769                        write!(f, " has {}", attr.head)?;
1770                    } else {
1771                        write!(f, " has \"{}\"", attr.head.escape_debug())?;
1772                    }
1773                    for attr in attr.tail.iter() {
1774                        // TODO: validation, we shouldn't have non-idents here because
1775                        // `principal has "foo".bar` doesn't parse
1776                        if is_normalized_ident(attr) {
1777                            write!(f, ".{}", attr)?;
1778                        } else {
1779                            write!(f, ".\"{}\"", attr.escape_debug())?;
1780                        }
1781                    }
1782                    Ok(())
1783                }
1784            },
1785            ExprNoExt::Like { left, pattern } => {
1786                maybe_with_parens(f, left, n)?;
1787                write!(
1788                    f,
1789                    " like \"{}\"",
1790                    crate::ast::Pattern::from(pattern.as_slice())
1791                )
1792            }
1793            ExprNoExt::Is {
1794                left,
1795                entity_type,
1796                in_expr,
1797            } => {
1798                maybe_with_parens(f, left, n)?;
1799                write!(f, " is {entity_type}")?;
1800                match in_expr {
1801                    Some(in_expr) => {
1802                        write!(f, " in ")?;
1803                        maybe_with_parens(f, in_expr, n)
1804                    }
1805                    None => Ok(()),
1806                }
1807            }
1808            ExprNoExt::If {
1809                cond_expr,
1810                then_expr,
1811                else_expr,
1812            } => {
1813                write!(f, "if ")?;
1814                maybe_with_parens(f, cond_expr, n)?;
1815                write!(f, " then ")?;
1816                maybe_with_parens(f, then_expr, n)?;
1817                write!(f, " else ")?;
1818                maybe_with_parens(f, else_expr, n)
1819            }
1820            ExprNoExt::Set(v) => {
1821                match n {
1822                    Some(n) if v.len() > n => {
1823                        // truncate to n elements
1824                        write!(f, "[")?;
1825                        for element in v.iter().take(n) {
1826                            BoundedDisplay::fmt(element, f, Some(n))?;
1827                            write!(f, ", ")?;
1828                        }
1829                        write!(f, "..]")?;
1830                        Ok(())
1831                    }
1832                    _ => {
1833                        // no truncation
1834                        write!(f, "[")?;
1835                        for (i, element) in v.iter().enumerate() {
1836                            BoundedDisplay::fmt(element, f, n)?;
1837                            if i < v.len() - 1 {
1838                                write!(f, ", ")?;
1839                            }
1840                        }
1841                        write!(f, "]")?;
1842                        Ok(())
1843                    }
1844                }
1845            }
1846            ExprNoExt::Record(m) => {
1847                match n {
1848                    Some(n) if m.len() > n => {
1849                        // truncate to n key-value pairs
1850                        write!(f, "{{")?;
1851                        for (k, v) in m.iter().take(n) {
1852                            if is_normalized_ident(k) {
1853                                write!(f, "{k}: ")?;
1854                            } else {
1855                                write!(f, "\"{}\": ", k.escape_debug())?;
1856                            }
1857                            BoundedDisplay::fmt(v, f, Some(n))?;
1858                            write!(f, ", ")?;
1859                        }
1860                        write!(f, "..}}")?;
1861                        Ok(())
1862                    }
1863                    _ => {
1864                        // no truncation
1865                        write!(f, "{{")?;
1866                        for (i, (k, v)) in m.iter().enumerate() {
1867                            if is_normalized_ident(k) {
1868                                write!(f, "{k}: ")?;
1869                            } else {
1870                                write!(f, "\"{}\": ", k.escape_debug())?;
1871                            }
1872                            BoundedDisplay::fmt(v, f, n)?;
1873                            if i < m.len() - 1 {
1874                                write!(f, ", ")?;
1875                            }
1876                        }
1877                        write!(f, "}}")?;
1878                        Ok(())
1879                    }
1880                }
1881            }
1882            #[cfg(feature = "tolerant-ast")]
1883            ExprNoExt::Error(e) => {
1884                write!(f, "{e}")?;
1885                Ok(())
1886            }
1887        }
1888    }
1889}
1890
1891impl std::fmt::Display for ExtFuncCall {
1892    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1893        BoundedDisplay::fmt_unbounded(self, f)
1894    }
1895}
1896
1897impl BoundedDisplay for ExtFuncCall {
1898    fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
1899        #[expect(clippy::unwrap_used, reason = "safe due to INVARIANT on `ExtFuncCall`")]
1900        let (fn_name, args) = self.try_components().unwrap();
1901        // search for the name and callstyle
1902        let style = Extensions::all_available().all_funcs().find_map(|ext_fn| {
1903            if &ext_fn.name().to_smolstr() == fn_name {
1904                Some(ext_fn.style())
1905            } else {
1906                None
1907            }
1908        });
1909        match (style, args) {
1910            (Some(ast::CallStyle::MethodStyle), [receiver, rest @ ..]) => {
1911                maybe_with_parens(f, receiver, n)?;
1912                write!(f, ".{}({})", fn_name, rest.iter().join(", "))
1913            }
1914            (_, _) => {
1915                write!(f, "{}({})", fn_name, args.iter().join(", "))
1916            }
1917        }
1918    }
1919}
1920
1921/// returns the `BoundedDisplay` representation of the Expr, adding parens around
1922/// the entire string if necessary.
1923/// E.g., won't add parens for constants or `principal` etc, but will for things
1924/// like `(2 < 5)`.
1925/// When in doubt, add the parens.
1926fn maybe_with_parens(
1927    f: &mut impl std::fmt::Write,
1928    expr: &Expr,
1929    n: Option<usize>,
1930) -> std::fmt::Result {
1931    match expr {
1932        Expr::ExprNoExt(ExprNoExt::Set(_)) |
1933        Expr::ExprNoExt(ExprNoExt::Record(_)) |
1934        Expr::ExprNoExt(ExprNoExt::Value(_)) |
1935        Expr::ExprNoExt(ExprNoExt::Var(_)) |
1936        Expr::ExprNoExt(ExprNoExt::Slot(_)) => BoundedDisplay::fmt(expr, f, n),
1937
1938        // we want parens here because things like parse((!x).y)
1939        // would be printed into !x.y which has a different meaning
1940        Expr::ExprNoExt(ExprNoExt::Not { .. }) |
1941        // we want parens here because things like parse((-x).y)
1942        // would be printed into -x.y which has a different meaning
1943        Expr::ExprNoExt(ExprNoExt::Neg { .. })  |
1944        Expr::ExprNoExt(ExprNoExt::Eq { .. }) |
1945        Expr::ExprNoExt(ExprNoExt::NotEq { .. }) |
1946        Expr::ExprNoExt(ExprNoExt::In { .. }) |
1947        Expr::ExprNoExt(ExprNoExt::Less { .. }) |
1948        Expr::ExprNoExt(ExprNoExt::LessEq { .. }) |
1949        Expr::ExprNoExt(ExprNoExt::Greater { .. }) |
1950        Expr::ExprNoExt(ExprNoExt::GreaterEq { .. }) |
1951        Expr::ExprNoExt(ExprNoExt::And { .. }) |
1952        Expr::ExprNoExt(ExprNoExt::Or { .. }) |
1953        Expr::ExprNoExt(ExprNoExt::Add { .. }) |
1954        Expr::ExprNoExt(ExprNoExt::Sub { .. }) |
1955        Expr::ExprNoExt(ExprNoExt::Mul { .. }) |
1956        Expr::ExprNoExt(ExprNoExt::Contains { .. }) |
1957        Expr::ExprNoExt(ExprNoExt::ContainsAll { .. }) |
1958        Expr::ExprNoExt(ExprNoExt::ContainsAny { .. }) |
1959        Expr::ExprNoExt(ExprNoExt::IsEmpty { .. }) |
1960        Expr::ExprNoExt(ExprNoExt::GetAttr { .. }) |
1961        Expr::ExprNoExt(ExprNoExt::HasAttr { .. }) |
1962        Expr::ExprNoExt(ExprNoExt::GetTag { .. }) |
1963        Expr::ExprNoExt(ExprNoExt::HasTag { .. }) |
1964        Expr::ExprNoExt(ExprNoExt::Like { .. }) |
1965        Expr::ExprNoExt(ExprNoExt::Is { .. }) |
1966        Expr::ExprNoExt(ExprNoExt::If { .. }) |
1967        Expr::ExtFuncCall { .. } => {
1968            write!(f, "(")?;
1969            BoundedDisplay::fmt(expr, f, n)?;
1970            write!(f, ")")?;
1971            Ok(())
1972        },
1973        #[cfg(feature = "tolerant-ast")]
1974        Expr::ExprNoExt(ExprNoExt::Error { .. }) => {
1975            write!(f, "(")?;
1976            BoundedDisplay::fmt(expr, f, n)?;
1977            write!(f, ")")?;
1978            Ok(())
1979        }
1980    }
1981}
1982
1983#[cfg(test)]
1984#[expect(clippy::indexing_slicing, reason = "this is unit test code")]
1985#[expect(clippy::panic, reason = "Unit Test Code")]
1986mod test {
1987    use crate::parser::{
1988        err::{ParseError, ToASTErrorKind},
1989        parse_expr,
1990    };
1991
1992    use super::*;
1993    use ast::BoundedToString;
1994    use cool_asserts::assert_matches;
1995
1996    #[test]
1997    fn test_invalid_expr_from_cst_name() {
1998        let e = crate::parser::text_to_cst::parse_expr("some_long_str::else").unwrap();
1999        assert_matches!(Expr::try_from(&e), Err(e) => {
2000            assert!(e.len() == 1);
2001            assert_matches!(&e[0],
2002                ParseError::ToAST(to_ast_error) => {
2003                    assert_matches!(to_ast_error.kind(), ToASTErrorKind::ReservedIdentifier(s) => {
2004                        assert_eq!(s.to_string(), "else");
2005                    });
2006                }
2007            );
2008        });
2009    }
2010
2011    #[test]
2012    fn display_and_bounded_display() {
2013        let expr = parse_expr(r#"[100, [3, 4, 5], -20, "foo"]"#)
2014            .unwrap()
2015            .into_expr::<Builder>();
2016        assert_eq!(format!("{expr}"), r#"[100, [3, 4, 5], (-20), "foo"]"#);
2017        assert_eq!(
2018            BoundedToString::to_string(&expr, None),
2019            r#"[100, [3, 4, 5], (-20), "foo"]"#
2020        );
2021        assert_eq!(
2022            BoundedToString::to_string(&expr, Some(4)),
2023            r#"[100, [3, 4, 5], (-20), "foo"]"#
2024        );
2025        assert_eq!(
2026            BoundedToString::to_string(&expr, Some(3)),
2027            r#"[100, [3, 4, 5], (-20), ..]"#
2028        );
2029        assert_eq!(
2030            BoundedToString::to_string(&expr, Some(2)),
2031            r#"[100, [3, 4, ..], ..]"#
2032        );
2033        assert_eq!(BoundedToString::to_string(&expr, Some(1)), r#"[100, ..]"#);
2034        assert_eq!(BoundedToString::to_string(&expr, Some(0)), r#"[..]"#);
2035
2036        let expr = parse_expr(
2037            r#"{
2038            a: 12,
2039            b: [3, 4, true],
2040            c: -20,
2041            "hello ∞ world": "∂µß≈¥"
2042        }"#,
2043        )
2044        .unwrap()
2045        .into_expr::<Builder>();
2046        assert_eq!(
2047            format!("{expr}"),
2048            r#"{a: 12, b: [3, 4, true], c: (-20), "hello ∞ world": "∂µß≈¥"}"#
2049        );
2050        assert_eq!(
2051            BoundedToString::to_string(&expr, None),
2052            r#"{a: 12, b: [3, 4, true], c: (-20), "hello ∞ world": "∂µß≈¥"}"#
2053        );
2054        assert_eq!(
2055            BoundedToString::to_string(&expr, Some(4)),
2056            r#"{a: 12, b: [3, 4, true], c: (-20), "hello ∞ world": "∂µß≈¥"}"#
2057        );
2058        assert_eq!(
2059            BoundedToString::to_string(&expr, Some(3)),
2060            r#"{a: 12, b: [3, 4, true], c: (-20), ..}"#
2061        );
2062        assert_eq!(
2063            BoundedToString::to_string(&expr, Some(2)),
2064            r#"{a: 12, b: [3, 4, ..], ..}"#
2065        );
2066        assert_eq!(BoundedToString::to_string(&expr, Some(1)), r#"{a: 12, ..}"#);
2067        assert_eq!(BoundedToString::to_string(&expr, Some(0)), r#"{..}"#);
2068    }
2069
2070    #[test]
2071    fn display_get_attr() {
2072        // Ensuring we prefer printing Expr::GetAttr in . notation if the attribute contains valid identifiers.
2073        let expr = parse_expr(r#"context.foo"#).unwrap().into_expr::<Builder>();
2074        assert_eq!(format!("{expr}"), r#"context.foo"#);
2075
2076        let expr = parse_expr(r#"context["foo"]"#)
2077            .unwrap()
2078            .into_expr::<Builder>();
2079        assert_eq!(format!("{expr}"), r#"context.foo"#);
2080
2081        // Ensure we escape the identifier if it contains whitespaces.
2082        let expr = parse_expr(r#"context["foo "]"#)
2083            .unwrap()
2084            .into_expr::<Builder>();
2085        assert_eq!(format!("{expr}"), r#"context["foo "]"#);
2086
2087        let expr = parse_expr(r#"context["foo-baz"]"#)
2088            .unwrap()
2089            .into_expr::<Builder>();
2090        assert_eq!(format!("{expr}"), r#"context["foo-baz"]"#);
2091
2092        let expr = parse_expr(r#"context["true"]"#)
2093            .unwrap()
2094            .into_expr::<Builder>();
2095        assert_eq!(format!("{expr}"), r#"context["true"]"#);
2096
2097        // Similarly for Expr::HasAttr
2098        let expr = parse_expr(r#"context has foo"#)
2099            .unwrap()
2100            .into_expr::<Builder>();
2101        assert_eq!(format!("{expr}"), r#"context has foo"#);
2102
2103        let expr = parse_expr(r#"context has "foo""#)
2104            .unwrap()
2105            .into_expr::<Builder>();
2106        assert_eq!(format!("{expr}"), r#"context has foo"#);
2107
2108        let expr = parse_expr(r#"context has "foo-baz""#)
2109            .unwrap()
2110            .into_expr::<Builder>();
2111        assert_eq!(format!("{expr}"), r#"context has "foo-baz""#);
2112
2113        let expr = parse_expr(r#"if context has "if" then false else true"#)
2114            .unwrap()
2115            .into_expr::<Builder>();
2116        assert_eq!(
2117            format!("{expr}"),
2118            r#"if (context has "if") then false else true"#
2119        );
2120
2121        let expr = parse_expr(r#"context has "has""#)
2122            .unwrap()
2123            .into_expr::<Builder>();
2124        assert_eq!(format!("{expr}"), r#"context has "has""#);
2125
2126        let expr = parse_expr(r#"if context has "foo-baz" then false else true"#)
2127            .unwrap()
2128            .into_expr::<Builder>();
2129        assert_eq!(
2130            format!("{expr}"),
2131            r#"if (context has "foo-baz") then false else true"#
2132        );
2133    }
2134
2135    #[test]
2136    fn has_attr_serde() {
2137        use nonempty::nonempty;
2138        let test_cases = vec![
2139            // Has can have a single string argument.
2140            (
2141                Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple {
2142                    left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2143                    attr: "department".into(),
2144                })),
2145                serde_json::json!({"has": {"left": {"Var": "principal"}, "attr": "department"}}),
2146            ),
2147            // Has can have a singleton list as argument
2148            (
2149                Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2150                    left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2151                    attr: nonempty!["user".into()],
2152                })),
2153                serde_json::json!({"has": {"left": {"Var": "context"}, "attr": ["user"]}}),
2154            ),
2155            // Has can have a list of strings as argument
2156            (
2157                Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2158                    left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2159                    attr: nonempty!["user".into(), "profile".into(), "email".into()],
2160                })),
2161                serde_json::json!({"has": {"left": {"Var": "context"}, "attr": ["user", "profile", "email"]}}),
2162            ),
2163        ];
2164        for (expr, json_repr) in test_cases {
2165            // Serialize = json_repr
2166            assert_eq!(serde_json::to_value(&expr).unwrap(), json_repr);
2167            // expr = Deserialize
2168            assert_eq!(expr, serde_json::from_value(json_repr).unwrap());
2169        }
2170    }
2171
2172    #[test]
2173    fn deserialize_has_attr_empty_errors() {
2174        let json = serde_json::json!({"has": {"left": {"Var": "context"}, "attr": []}});
2175        let expr_result: Result<Expr, _> = serde_json::from_value(json);
2176        assert!(expr_result.is_err())
2177    }
2178
2179    #[test]
2180    fn extended_has_display() {
2181        // Other display functions are tested from the ast, but we can't curretly do that for the
2182        // extended has operator
2183        use nonempty::nonempty;
2184
2185        // Simple has
2186        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple {
2187            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2188            attr: "department".into(),
2189        }));
2190        assert_eq!(format!("{expr}"), "principal has department");
2191
2192        // Extended has with single attribute
2193        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2194            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2195            attr: nonempty!["user".into()],
2196        }));
2197        assert_eq!(format!("{expr}"), "context has user");
2198
2199        // Extended has with multiple attributes
2200        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2201            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2202            attr: nonempty!["user".into(), "profile".into(), "email".into()],
2203        }));
2204        assert_eq!(format!("{expr}"), "context has user.profile.email");
2205    }
2206
2207    #[test]
2208    fn height_leaf() {
2209        let expr = Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)));
2210        assert_eq!(expr.height(), 0);
2211        let expr = Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal));
2212        assert_eq!(expr.height(), 0);
2213    }
2214
2215    #[test]
2216    fn height_unary() {
2217        let expr = Expr::ExprNoExt(ExprNoExt::Not {
2218            arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2219        });
2220        assert_eq!(expr.height(), 1);
2221    }
2222
2223    #[test]
2224    fn height_binary() {
2225        let expr = Expr::ExprNoExt(ExprNoExt::Eq {
2226            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2227            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2228        });
2229        assert_eq!(expr.height(), 1);
2230    }
2231
2232    #[test]
2233    fn height_nested() {
2234        let expr = Expr::ExprNoExt(ExprNoExt::Not {
2235            arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Not {
2236                arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2237            })),
2238        });
2239        assert_eq!(expr.height(), 2);
2240    }
2241
2242    #[test]
2243    fn height_asymmetric() {
2244        let deep_left = Expr::ExprNoExt(ExprNoExt::Not {
2245            arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Not {
2246                arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2247            })),
2248        });
2249        let expr = Expr::ExprNoExt(ExprNoExt::Eq {
2250            left: Arc::new(deep_left),
2251            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2252        });
2253        assert_eq!(expr.height(), 3);
2254    }
2255
2256    #[test]
2257    fn height_if_then_else() {
2258        let expr = Expr::ExprNoExt(ExprNoExt::If {
2259            cond_expr: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Bool(
2260                true,
2261            )))),
2262            then_expr: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2263            else_expr: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2264        });
2265        assert_eq!(expr.height(), 1);
2266    }
2267
2268    #[test]
2269    fn height_set() {
2270        let expr = Expr::ExprNoExt(ExprNoExt::Set(vec![
2271            Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1))),
2272            Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2))),
2273        ]));
2274        assert_eq!(expr.height(), 1);
2275    }
2276
2277    #[test]
2278    fn height_empty_set() {
2279        let expr = Expr::ExprNoExt(ExprNoExt::Set(vec![]));
2280        assert_eq!(expr.height(), 0);
2281    }
2282
2283    #[test]
2284    fn height_record() {
2285        let mut map = BTreeMap::new();
2286        map.insert(
2287            "a".into(),
2288            Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1))),
2289        );
2290        let expr = Expr::ExprNoExt(ExprNoExt::Record(map));
2291        assert_eq!(expr.height(), 1);
2292    }
2293
2294    #[test]
2295    fn height_neg() {
2296        // Simple negation of a leaf has height 1
2297        let expr = Expr::ExprNoExt(ExprNoExt::Neg {
2298            arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(5)))),
2299        });
2300        assert_eq!(expr.height(), 1);
2301
2302        // Nested negation has height 2
2303        let expr = Expr::ExprNoExt(ExprNoExt::Neg {
2304            arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Neg {
2305                arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(5)))),
2306            })),
2307        });
2308        assert_eq!(expr.height(), 2);
2309    }
2310
2311    #[test]
2312    fn height_not_eq() {
2313        let expr = Expr::ExprNoExt(ExprNoExt::NotEq {
2314            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2315            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2316        });
2317        assert_eq!(expr.height(), 1);
2318    }
2319
2320    #[test]
2321    fn height_in() {
2322        let expr = Expr::ExprNoExt(ExprNoExt::In {
2323            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2324            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Resource))),
2325        });
2326        assert_eq!(expr.height(), 1);
2327    }
2328
2329    #[test]
2330    fn height_less() {
2331        let expr = Expr::ExprNoExt(ExprNoExt::Less {
2332            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2333            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2334        });
2335        assert_eq!(expr.height(), 1);
2336    }
2337
2338    #[test]
2339    fn height_less_eq() {
2340        let expr = Expr::ExprNoExt(ExprNoExt::LessEq {
2341            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2342            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2343        });
2344        assert_eq!(expr.height(), 1);
2345    }
2346
2347    #[test]
2348    fn height_greater() {
2349        let expr = Expr::ExprNoExt(ExprNoExt::Greater {
2350            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2351            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2352        });
2353        assert_eq!(expr.height(), 1);
2354    }
2355
2356    #[test]
2357    fn height_greater_eq() {
2358        let expr = Expr::ExprNoExt(ExprNoExt::GreaterEq {
2359            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2360            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2361        });
2362        assert_eq!(expr.height(), 1);
2363    }
2364
2365    #[test]
2366    fn height_and() {
2367        let expr = Expr::ExprNoExt(ExprNoExt::And {
2368            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Bool(
2369                true,
2370            )))),
2371            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Bool(
2372                false,
2373            )))),
2374        });
2375        assert_eq!(expr.height(), 1);
2376    }
2377
2378    #[test]
2379    fn height_or() {
2380        let expr = Expr::ExprNoExt(ExprNoExt::Or {
2381            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Bool(
2382                true,
2383            )))),
2384            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Bool(
2385                false,
2386            )))),
2387        });
2388        assert_eq!(expr.height(), 1);
2389    }
2390
2391    #[test]
2392    fn height_add() {
2393        let expr = Expr::ExprNoExt(ExprNoExt::Add {
2394            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2395            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2396        });
2397        assert_eq!(expr.height(), 1);
2398    }
2399
2400    #[test]
2401    fn height_sub() {
2402        let expr = Expr::ExprNoExt(ExprNoExt::Sub {
2403            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(3)))),
2404            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2405        });
2406        assert_eq!(expr.height(), 1);
2407    }
2408
2409    #[test]
2410    fn height_mul() {
2411        let expr = Expr::ExprNoExt(ExprNoExt::Mul {
2412            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(2)))),
2413            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(3)))),
2414        });
2415        assert_eq!(expr.height(), 1);
2416    }
2417
2418    #[test]
2419    fn height_contains() {
2420        let expr = Expr::ExprNoExt(ExprNoExt::Contains {
2421            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Set(vec![Expr::ExprNoExt(
2422                ExprNoExt::Value(CedarValueJson::Long(1)),
2423            )]))),
2424            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Long(1)))),
2425        });
2426        assert_eq!(expr.height(), 2);
2427    }
2428
2429    #[test]
2430    fn height_contains_all() {
2431        let expr = Expr::ExprNoExt(ExprNoExt::ContainsAll {
2432            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2433            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Set(vec![]))),
2434        });
2435        assert_eq!(expr.height(), 1);
2436    }
2437
2438    #[test]
2439    fn height_contains_any() {
2440        let expr = Expr::ExprNoExt(ExprNoExt::ContainsAny {
2441            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2442            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Set(vec![]))),
2443        });
2444        assert_eq!(expr.height(), 1);
2445    }
2446
2447    #[test]
2448    fn height_get_tag() {
2449        let expr = Expr::ExprNoExt(ExprNoExt::GetTag {
2450            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2451            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::String(
2452                "tag_name".into(),
2453            )))),
2454        });
2455        assert_eq!(expr.height(), 1);
2456    }
2457
2458    #[test]
2459    fn height_has_tag() {
2460        let expr = Expr::ExprNoExt(ExprNoExt::HasTag {
2461            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2462            right: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::String(
2463                "tag_name".into(),
2464            )))),
2465        });
2466        assert_eq!(expr.height(), 1);
2467    }
2468
2469    #[test]
2470    fn height_get_attr() {
2471        let expr = Expr::ExprNoExt(ExprNoExt::GetAttr {
2472            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2473            attr: "foo".into(),
2474        });
2475        assert_eq!(expr.height(), 1);
2476    }
2477
2478    #[test]
2479    fn height_has_attr() {
2480        // Simple HasAttr
2481        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple {
2482            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2483            attr: "foo".into(),
2484        }));
2485        assert_eq!(expr.height(), 1);
2486
2487        // Extended HasAttr
2488        use nonempty::nonempty;
2489        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2490            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2491            attr: nonempty!["user".into(), "profile".into()],
2492        }));
2493        assert_eq!(expr.height(), 1);
2494    }
2495
2496    #[test]
2497    fn height_like() {
2498        let expr = Expr::ExprNoExt(ExprNoExt::Like {
2499            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2500            pattern: vec![PatternElem::Wildcard],
2501        });
2502        assert_eq!(expr.height(), 1);
2503    }
2504
2505    #[test]
2506    fn height_is() {
2507        // Is without in_expr
2508        let expr = Expr::ExprNoExt(ExprNoExt::Is {
2509            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2510            entity_type: "User".into(),
2511            in_expr: None,
2512        });
2513        assert_eq!(expr.height(), 1);
2514
2515        // Is with in_expr
2516        let expr = Expr::ExprNoExt(ExprNoExt::Is {
2517            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2518            entity_type: "User".into(),
2519            in_expr: Some(Arc::new(Expr::ExprNoExt(ExprNoExt::Var(
2520                ast::Var::Resource,
2521            )))),
2522        });
2523        assert_eq!(expr.height(), 1);
2524
2525        // Is with deeper in_expr
2526        let expr = Expr::ExprNoExt(ExprNoExt::Is {
2527            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2528            entity_type: "User".into(),
2529            in_expr: Some(Arc::new(Expr::ExprNoExt(ExprNoExt::Not {
2530                arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Resource))),
2531            }))),
2532        });
2533        assert_eq!(expr.height(), 2);
2534    }
2535
2536    #[test]
2537    fn height_ext_func_call() {
2538        // ExtFuncCall with a leaf argument has height 1
2539        let expr = Expr::ExtFuncCall(ExtFuncCall {
2540            call: HashMap::from([(
2541                "ip".to_smolstr(),
2542                vec![Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::String(
2543                    "127.0.0.1".into(),
2544                )))],
2545            )]),
2546        });
2547        assert_eq!(expr.height(), 1);
2548
2549        // ExtFuncCall with a nested argument has height 2
2550        let expr = Expr::ExtFuncCall(ExtFuncCall {
2551            call: HashMap::from([(
2552                "ip".to_smolstr(),
2553                vec![Expr::ExprNoExt(ExprNoExt::Not {
2554                    arg: Arc::new(Expr::ExprNoExt(ExprNoExt::Value(CedarValueJson::Bool(
2555                        true,
2556                    )))),
2557                })],
2558            )]),
2559        });
2560        assert_eq!(expr.height(), 2);
2561    }
2562}