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
967impl Expr {
968    /// Convert this `est::Expr` into an expression of type `B::Expr` using the builder `B`,
969    /// In the case of an error, returns the builder's own `BuildError`.
970    pub fn try_into_expr<B>(self) -> Result<B::Expr, B::BuildError>
971    where
972        B: ExprBuilder,
973        B::BuildError: From<FromJsonError>,
974    {
975        // This implementation looks a lot like try_into_ast but:
976        // - it doesn't have the policy ID information (could be injected after though)
977        // - it uses the fallible extension call constructor; validation is handled by builder
978        // - it doesn't directly desugars is_in, rather defers to the builder
979        // This implementation is used by PST. Using this for AST would result in a different
980        // behavior than try_into_ast, particularly for the second point which would fail
981        // conversions that are currently non-failing (method call with no arguments).
982        let builder = B::new();
983        match self {
984            Expr::ExprNoExt(ExprNoExt::Value(jsonvalue)) => {
985                // Through AST expressions to convert to B::Expr
986                let ast_expr: ast::Expr = jsonvalue
987                    .into_expr(&|| JsonDeserializationErrorContext::Policy {
988                        id: ast::PolicyID::from_string(""),
989                    })
990                    .map(ast::Expr::from)
991                    .map_err(|j| B::BuildError::from(j.into()))?;
992                ast_expr.try_into_expr::<B>()
993            }
994            Expr::ExprNoExt(ExprNoExt::Var(var)) => Ok(builder.var(var)),
995            Expr::ExprNoExt(ExprNoExt::Slot(slot)) => Ok(builder.slot(slot)),
996            Expr::ExprNoExt(ExprNoExt::Not { arg }) => {
997                Ok(builder.not(Arc::unwrap_or_clone(arg).try_into_expr::<B>()?))
998            }
999            Expr::ExprNoExt(ExprNoExt::Neg { arg }) => {
1000                Ok(builder.neg(Arc::unwrap_or_clone(arg).try_into_expr::<B>()?))
1001            }
1002            Expr::ExprNoExt(ExprNoExt::Eq { left, right }) => Ok(builder.is_eq(
1003                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1004                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1005            )),
1006            Expr::ExprNoExt(ExprNoExt::NotEq { left, right }) => Ok(builder.noteq(
1007                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1008                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1009            )),
1010            Expr::ExprNoExt(ExprNoExt::In { left, right }) => Ok(builder.is_in(
1011                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1012                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1013            )),
1014            Expr::ExprNoExt(ExprNoExt::Less { left, right }) => Ok(builder.less(
1015                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1016                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1017            )),
1018            Expr::ExprNoExt(ExprNoExt::LessEq { left, right }) => Ok(builder.lesseq(
1019                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1020                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1021            )),
1022            Expr::ExprNoExt(ExprNoExt::Greater { left, right }) => Ok(builder.greater(
1023                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1024                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1025            )),
1026            Expr::ExprNoExt(ExprNoExt::GreaterEq { left, right }) => Ok(builder.greatereq(
1027                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1028                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1029            )),
1030            Expr::ExprNoExt(ExprNoExt::And { left, right }) => Ok(builder.and(
1031                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1032                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1033            )),
1034            Expr::ExprNoExt(ExprNoExt::Or { left, right }) => Ok(builder.or(
1035                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1036                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1037            )),
1038            Expr::ExprNoExt(ExprNoExt::Add { left, right }) => Ok(builder.add(
1039                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1040                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1041            )),
1042            Expr::ExprNoExt(ExprNoExt::Sub { left, right }) => Ok(builder.sub(
1043                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1044                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1045            )),
1046            Expr::ExprNoExt(ExprNoExt::Mul { left, right }) => Ok(builder.mul(
1047                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1048                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1049            )),
1050            Expr::ExprNoExt(ExprNoExt::Contains { left, right }) => Ok(builder.contains(
1051                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1052                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1053            )),
1054            Expr::ExprNoExt(ExprNoExt::ContainsAll { left, right }) => Ok(builder.contains_all(
1055                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1056                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1057            )),
1058            Expr::ExprNoExt(ExprNoExt::ContainsAny { left, right }) => Ok(builder.contains_any(
1059                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1060                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1061            )),
1062            Expr::ExprNoExt(ExprNoExt::IsEmpty { arg }) => {
1063                Ok(builder.is_empty(Arc::unwrap_or_clone(arg).try_into_expr::<B>()?))
1064            }
1065            Expr::ExprNoExt(ExprNoExt::GetTag { left, right }) => Ok(builder.get_tag(
1066                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1067                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1068            )),
1069            Expr::ExprNoExt(ExprNoExt::HasTag { left, right }) => Ok(builder.has_tag(
1070                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1071                Arc::unwrap_or_clone(right).try_into_expr::<B>()?,
1072            )),
1073            Expr::ExprNoExt(ExprNoExt::GetAttr { left, attr }) => {
1074                Ok(builder.get_attr(Arc::unwrap_or_clone(left).try_into_expr::<B>()?, attr))
1075            }
1076            Expr::ExprNoExt(ExprNoExt::HasAttr(repr)) => match repr {
1077                HasAttrRepr::Simple { left, attr } => {
1078                    Ok(builder.has_attr(Arc::unwrap_or_clone(left).try_into_expr::<B>()?, attr))
1079                }
1080                HasAttrRepr::Extended { left, attr } => Ok(builder
1081                    .extended_has_attr(Arc::unwrap_or_clone(left).try_into_expr::<B>()?, attr)),
1082            },
1083            Expr::ExprNoExt(ExprNoExt::Like { left, pattern }) => Ok(builder.like(
1084                Arc::unwrap_or_clone(left).try_into_expr::<B>()?,
1085                ast::Pattern::from(pattern.as_slice()),
1086            )),
1087            Expr::ExprNoExt(ExprNoExt::Is {
1088                left,
1089                entity_type,
1090                in_expr,
1091            }) => {
1092                let entity_type_name = ast::EntityType::from_normalized_str(&entity_type)
1093                    .map_err(FromJsonError::InvalidEntityType)?;
1094                let left_expr = Arc::unwrap_or_clone(left).try_into_expr::<B>()?;
1095                match in_expr {
1096                    Some(in_expr) => Ok(builder.is_in_entity_type(
1097                        left_expr,
1098                        entity_type_name,
1099                        Arc::unwrap_or_clone(in_expr).try_into_expr::<B>()?,
1100                    )),
1101                    None => Ok(builder.is_entity_type(left_expr, entity_type_name)),
1102                }
1103            }
1104            Expr::ExprNoExt(ExprNoExt::If {
1105                cond_expr,
1106                then_expr,
1107                else_expr,
1108            }) => Ok(builder.ite(
1109                Arc::unwrap_or_clone(cond_expr).try_into_expr::<B>()?,
1110                Arc::unwrap_or_clone(then_expr).try_into_expr::<B>()?,
1111                Arc::unwrap_or_clone(else_expr).try_into_expr::<B>()?,
1112            )),
1113            Expr::ExprNoExt(ExprNoExt::Set(elements)) => Ok(builder.set(
1114                elements
1115                    .into_iter()
1116                    .map(|el| el.try_into_expr::<B>())
1117                    .collect::<Result<Vec<_>, _>>()?,
1118            )),
1119            Expr::ExprNoExt(ExprNoExt::Record(map)) =>
1120            {
1121                #[expect(
1122                    clippy::expect_used,
1123                    reason = "can't have duplicate keys here because the input was already a HashMap"
1124                )]
1125                Ok(builder
1126                    .record(
1127                        map.into_iter()
1128                            .map(|(k, v)| Ok((k, v.try_into_expr::<B>()?)))
1129                            .collect::<Result<Vec<_>, B::BuildError>>()?,
1130                    )
1131                    .expect("map should not have duplicate keys"))
1132            }
1133            Expr::ExtFuncCall(e) => {
1134                let (fn_name, args) = e.try_into_components()?;
1135                let fn_name = Name::from_normalized_str(&fn_name).map_err(|errs| {
1136                    JsonDeserializationError::parse_escape(EscapeKind::Extension, fn_name, errs)
1137                        .into()
1138                })?;
1139                // unlike into_ast, some validation is handled by calling the fallible version
1140                // of the extension call builder
1141                builder.call_extension_fn(
1142                    fn_name,
1143                    args.into_iter()
1144                        .map(|arg| arg.try_into_expr::<B>())
1145                        .collect::<Result<Vec<_>, _>>()?,
1146                )
1147            }
1148            #[cfg(feature = "tolerant-ast")]
1149            Expr::ExprNoExt(ExprNoExt::Error(_)) => Err(FromJsonError::ASTErrorNode.into()),
1150        }
1151    }
1152
1153    /// Attempt to convert this `est::Expr` into an `ast::Expr`
1154    ///
1155    /// `id`: the ID of the policy this `Expr` belongs to, used only for reporting errors
1156    pub fn try_into_ast(self, id: &ast::PolicyID) -> Result<ast::Expr, FromJsonError> {
1157        match self {
1158            Expr::ExprNoExt(ExprNoExt::Value(jsonvalue)) => jsonvalue
1159                .into_expr(&|| JsonDeserializationErrorContext::Policy { id: id.clone() })
1160                .map(Into::into)
1161                .map_err(Into::into),
1162            Expr::ExprNoExt(ExprNoExt::Var(var)) => Ok(ast::Expr::var(var)),
1163            Expr::ExprNoExt(ExprNoExt::Slot(slot)) => Ok(ast::Expr::slot(slot)),
1164            Expr::ExprNoExt(ExprNoExt::Not { arg }) => {
1165                Ok(ast::Expr::not(Arc::unwrap_or_clone(arg).try_into_ast(id)?))
1166            }
1167            Expr::ExprNoExt(ExprNoExt::Neg { arg }) => {
1168                Ok(ast::Expr::neg(Arc::unwrap_or_clone(arg).try_into_ast(id)?))
1169            }
1170            Expr::ExprNoExt(ExprNoExt::Eq { left, right }) => Ok(ast::Expr::is_eq(
1171                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1172                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1173            )),
1174            Expr::ExprNoExt(ExprNoExt::NotEq { left, right }) => Ok(ast::Expr::noteq(
1175                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1176                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1177            )),
1178            Expr::ExprNoExt(ExprNoExt::In { left, right }) => Ok(ast::Expr::is_in(
1179                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1180                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1181            )),
1182            Expr::ExprNoExt(ExprNoExt::Less { left, right }) => Ok(ast::Expr::less(
1183                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1184                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1185            )),
1186            Expr::ExprNoExt(ExprNoExt::LessEq { left, right }) => Ok(ast::Expr::lesseq(
1187                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1188                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1189            )),
1190            Expr::ExprNoExt(ExprNoExt::Greater { left, right }) => Ok(ast::Expr::greater(
1191                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1192                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1193            )),
1194            Expr::ExprNoExt(ExprNoExt::GreaterEq { left, right }) => Ok(ast::Expr::greatereq(
1195                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1196                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1197            )),
1198            Expr::ExprNoExt(ExprNoExt::And { left, right }) => Ok(ast::Expr::and(
1199                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1200                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1201            )),
1202            Expr::ExprNoExt(ExprNoExt::Or { left, right }) => Ok(ast::Expr::or(
1203                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1204                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1205            )),
1206            Expr::ExprNoExt(ExprNoExt::Add { left, right }) => Ok(ast::Expr::add(
1207                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1208                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1209            )),
1210            Expr::ExprNoExt(ExprNoExt::Sub { left, right }) => Ok(ast::Expr::sub(
1211                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1212                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1213            )),
1214            Expr::ExprNoExt(ExprNoExt::Mul { left, right }) => Ok(ast::Expr::mul(
1215                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1216                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1217            )),
1218            Expr::ExprNoExt(ExprNoExt::Contains { left, right }) => Ok(ast::Expr::contains(
1219                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1220                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1221            )),
1222            Expr::ExprNoExt(ExprNoExt::ContainsAll { left, right }) => Ok(ast::Expr::contains_all(
1223                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1224                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1225            )),
1226            Expr::ExprNoExt(ExprNoExt::ContainsAny { left, right }) => Ok(ast::Expr::contains_any(
1227                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1228                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1229            )),
1230            Expr::ExprNoExt(ExprNoExt::IsEmpty { arg }) => Ok(ast::Expr::is_empty(
1231                Arc::unwrap_or_clone(arg).try_into_ast(id)?,
1232            )),
1233            Expr::ExprNoExt(ExprNoExt::GetTag { left, right }) => Ok(ast::Expr::get_tag(
1234                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1235                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1236            )),
1237            Expr::ExprNoExt(ExprNoExt::HasTag { left, right }) => Ok(ast::Expr::has_tag(
1238                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1239                Arc::unwrap_or_clone(right).try_into_ast(id)?,
1240            )),
1241            Expr::ExprNoExt(ExprNoExt::GetAttr { left, attr }) => Ok(ast::Expr::get_attr(
1242                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1243                attr,
1244            )),
1245            Expr::ExprNoExt(ExprNoExt::HasAttr(repr)) => match repr {
1246                HasAttrRepr::Simple { left, attr } => Ok(ast::Expr::has_attr(
1247                    Arc::unwrap_or_clone(left).try_into_ast(id)?,
1248                    attr,
1249                )),
1250                HasAttrRepr::Extended { left, attr } => Ok(ast::ExprBuilder::new()
1251                    .extended_has_attr(Arc::unwrap_or_clone(left).try_into_ast(id)?, attr)),
1252            },
1253            Expr::ExprNoExt(ExprNoExt::Like { left, pattern }) => Ok(ast::Expr::like(
1254                Arc::unwrap_or_clone(left).try_into_ast(id)?,
1255                crate::ast::Pattern::from(pattern.as_slice()),
1256            )),
1257            Expr::ExprNoExt(ExprNoExt::Is {
1258                left,
1259                entity_type,
1260                in_expr,
1261            }) => ast::EntityType::from_normalized_str(entity_type.as_str())
1262                .map_err(FromJsonError::InvalidEntityType)
1263                .and_then(|entity_type_name| {
1264                    let left: ast::Expr = Arc::unwrap_or_clone(left).try_into_ast(id)?;
1265                    match in_expr {
1266                        Some(in_expr) => Ok(ast::ExprBuilder::new().is_in_entity_type(
1267                            left,
1268                            entity_type_name,
1269                            Arc::unwrap_or_clone(in_expr).try_into_ast(id)?,
1270                        )),
1271                        None => Ok(ast::ExprBuilder::new().is_entity_type(left, entity_type_name)),
1272                    }
1273                }),
1274            Expr::ExprNoExt(ExprNoExt::If {
1275                cond_expr,
1276                then_expr,
1277                else_expr,
1278            }) => Ok(ast::Expr::ite(
1279                Arc::unwrap_or_clone(cond_expr).try_into_ast(id)?,
1280                Arc::unwrap_or_clone(then_expr).try_into_ast(id)?,
1281                Arc::unwrap_or_clone(else_expr).try_into_ast(id)?,
1282            )),
1283            Expr::ExprNoExt(ExprNoExt::Set(elements)) => Ok(ast::Expr::set(
1284                elements
1285                    .into_iter()
1286                    .map(|el| el.try_into_ast(id))
1287                    .collect::<Result<Vec<_>, FromJsonError>>()?,
1288            )),
1289            Expr::ExprNoExt(ExprNoExt::Record(map)) =>
1290            {
1291                #[expect(
1292                    clippy::expect_used,
1293                    reason = "can't have duplicate keys here because the input was already a HashMap"
1294                )]
1295                Ok(ast::Expr::record(
1296                    map.into_iter()
1297                        .map(|(k, v)| Ok((k, v.try_into_ast(id)?)))
1298                        .collect::<Result<HashMap<SmolStr, _>, FromJsonError>>()?,
1299                )
1300                .expect("can't have duplicate keys here because the input was already a HashMap"))
1301            }
1302            Expr::ExtFuncCall(e) => {
1303                let (fn_name, args) = e.try_into_components()?;
1304                let fn_name = Name::from_normalized_str(&fn_name).map_err(|errs| {
1305                    JsonDeserializationError::parse_escape(EscapeKind::Extension, fn_name, errs)
1306                })?;
1307                if ExtStyles::is_known_extension_func_name(&fn_name) {
1308                    Ok(ast::Expr::call_extension_fn(
1309                        fn_name,
1310                        args.into_iter()
1311                            .map(|arg| arg.try_into_ast(id))
1312                            .collect::<Result<_, _>>()?,
1313                    ))
1314                } else {
1315                    Err(FromJsonError::UnknownExtensionFunction(fn_name))
1316                }
1317            }
1318            #[cfg(feature = "tolerant-ast")]
1319            Expr::ExprNoExt(ExprNoExt::Error(_)) => Err(FromJsonError::ASTErrorNode),
1320        }
1321    }
1322}
1323
1324impl From<ast::Literal> for Expr {
1325    fn from(lit: ast::Literal) -> Expr {
1326        Builder::new().val(lit)
1327    }
1328}
1329
1330impl From<ast::Var> for Expr {
1331    fn from(var: ast::Var) -> Expr {
1332        Builder::new().var(var)
1333    }
1334}
1335
1336impl From<ast::SlotId> for Expr {
1337    fn from(slot: ast::SlotId) -> Expr {
1338        Builder::new().slot(slot)
1339    }
1340}
1341
1342impl TryFrom<&Node<Option<cst::Expr>>> for Expr {
1343    type Error = ParseErrors;
1344    fn try_from(e: &Node<Option<cst::Expr>>) -> Result<Expr, ParseErrors> {
1345        e.to_expr::<Builder>()
1346    }
1347}
1348
1349impl std::fmt::Display for Expr {
1350    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1351        match self {
1352            Self::ExprNoExt(e) => write!(f, "{e}"),
1353            Self::ExtFuncCall(e) => write!(f, "{e}"),
1354        }
1355    }
1356}
1357
1358impl BoundedDisplay for Expr {
1359    fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
1360        match self {
1361            Self::ExprNoExt(e) => BoundedDisplay::fmt(e, f, n),
1362            Self::ExtFuncCall(e) => BoundedDisplay::fmt(e, f, n),
1363        }
1364    }
1365}
1366
1367fn display_cedarvaluejson(
1368    f: &mut impl std::fmt::Write,
1369    v: &CedarValueJson,
1370    n: Option<usize>,
1371) -> std::fmt::Result {
1372    match v {
1373        // Add parentheses around negative numeric literals otherwise
1374        // round-tripping fuzzer fails for expressions like `(-1)["a"]`.
1375        CedarValueJson::Long(i) if *i < 0 => write!(f, "({i})"),
1376        CedarValueJson::Long(i) => write!(f, "{i}"),
1377        CedarValueJson::Bool(b) => write!(f, "{b}"),
1378        CedarValueJson::String(s) => write!(f, "\"{}\"", s.escape_debug()),
1379        CedarValueJson::EntityEscape { __entity } => {
1380            match ast::EntityUID::try_from(__entity.clone()) {
1381                Ok(euid) => write!(f, "{euid}"),
1382                Err(e) => write!(f, "(invalid entity uid: {e})"),
1383            }
1384        }
1385        CedarValueJson::ExprEscape { __expr } => write!(f, "({__expr})"),
1386        CedarValueJson::ExtnEscape {
1387            __extn: FnAndArgs::Single { ext_fn, arg },
1388        } => {
1389            // search for the name and callstyle
1390            let style = Extensions::all_available().all_funcs().find_map(|f| {
1391                if &f.name().to_smolstr() == ext_fn {
1392                    Some(f.style())
1393                } else {
1394                    None
1395                }
1396            });
1397            match style {
1398                Some(ast::CallStyle::MethodStyle) => {
1399                    display_cedarvaluejson(f, arg, n)?;
1400                    write!(f, ".{ext_fn}()")?;
1401                    Ok(())
1402                }
1403                Some(ast::CallStyle::FunctionStyle) | None => {
1404                    write!(f, "{ext_fn}(")?;
1405                    display_cedarvaluejson(f, arg, n)?;
1406                    write!(f, ")")?;
1407                    Ok(())
1408                }
1409            }
1410        }
1411        CedarValueJson::ExtnEscape {
1412            __extn: FnAndArgs::Multi { ext_fn, args },
1413        } => {
1414            // search for the name and callstyle
1415            let style = Extensions::all_available().all_funcs().find_map(|f| {
1416                if &f.name().to_smolstr() == ext_fn {
1417                    Some(f.style())
1418                } else {
1419                    None
1420                }
1421            });
1422            match style {
1423                Some(ast::CallStyle::MethodStyle) => {
1424                    #[expect(
1425                        clippy::indexing_slicing,
1426                        reason = "method-style calls must have more than one argument"
1427                    )]
1428                    display_cedarvaluejson(f, &args[0], n)?;
1429                    write!(f, ".{ext_fn}(")?;
1430                    #[expect(
1431                        clippy::indexing_slicing,
1432                        reason = "method-style calls must have more than one argument"
1433                    )]
1434                    match &args[1..] {
1435                        [] => {}
1436                        [args @ .., last] => {
1437                            for arg in args {
1438                                display_cedarvaluejson(f, arg, n)?;
1439                                write!(f, ", ")?;
1440                            }
1441                            display_cedarvaluejson(f, last, n)?;
1442                        }
1443                    }
1444                    write!(f, ")")?;
1445                    Ok(())
1446                }
1447                Some(ast::CallStyle::FunctionStyle) | None => {
1448                    write!(f, "{ext_fn}(")?;
1449                    match &args[..] {
1450                        [] => {}
1451                        [args @ .., last] => {
1452                            for arg in args {
1453                                display_cedarvaluejson(f, arg, n)?;
1454                                write!(f, ", ")?;
1455                            }
1456                            display_cedarvaluejson(f, last, n)?;
1457                        }
1458                    }
1459                    write!(f, ")")?;
1460                    Ok(())
1461                }
1462            }
1463        }
1464        CedarValueJson::Set(v) => {
1465            match n {
1466                Some(n) if v.len() > n => {
1467                    // truncate to n elements
1468                    write!(f, "[")?;
1469                    for val in v.iter().take(n) {
1470                        display_cedarvaluejson(f, val, Some(n))?;
1471                        write!(f, ", ")?;
1472                    }
1473                    write!(f, "..]")?;
1474                    Ok(())
1475                }
1476                _ => {
1477                    // no truncation
1478                    write!(f, "[")?;
1479                    for (i, val) in v.iter().enumerate() {
1480                        display_cedarvaluejson(f, val, n)?;
1481                        if i < v.len() - 1 {
1482                            write!(f, ", ")?;
1483                        }
1484                    }
1485                    write!(f, "]")?;
1486                    Ok(())
1487                }
1488            }
1489        }
1490        CedarValueJson::Record(r) => {
1491            match n {
1492                Some(n) if r.len() > n => {
1493                    // truncate to n key-value pairs
1494                    write!(f, "{{")?;
1495                    for (k, v) in r.iter().take(n) {
1496                        write!(f, "\"{}\": ", k.escape_debug())?;
1497                        display_cedarvaluejson(f, v, Some(n))?;
1498                        write!(f, ", ")?;
1499                    }
1500                    write!(f, "..}}")?;
1501                    Ok(())
1502                }
1503                _ => {
1504                    // no truncation
1505                    write!(f, "{{")?;
1506                    for (i, (k, v)) in r.iter().enumerate() {
1507                        write!(f, "\"{}\": ", k.escape_debug())?;
1508                        display_cedarvaluejson(f, v, n)?;
1509                        if i < r.len() - 1 {
1510                            write!(f, ", ")?;
1511                        }
1512                    }
1513                    write!(f, "}}")?;
1514                    Ok(())
1515                }
1516            }
1517        }
1518        CedarValueJson::Null => {
1519            write!(f, "null")?;
1520            Ok(())
1521        }
1522    }
1523}
1524
1525impl std::fmt::Display for ExprNoExt {
1526    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1527        BoundedDisplay::fmt_unbounded(self, f)
1528    }
1529}
1530
1531impl BoundedDisplay for ExprNoExt {
1532    fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
1533        match &self {
1534            ExprNoExt::Value(v) => display_cedarvaluejson(f, v, n),
1535            ExprNoExt::Var(v) => write!(f, "{v}"),
1536            ExprNoExt::Slot(id) => write!(f, "{id}"),
1537            ExprNoExt::Not { arg } => {
1538                write!(f, "!")?;
1539                maybe_with_parens(f, arg, n)
1540            }
1541            ExprNoExt::Neg { arg } => {
1542                // Always add parentheses instead of calling
1543                // `maybe_with_parens`.
1544                // This makes sure that we always get a negation operation back
1545                // (as opposed to e.g., a negative number) when parsing the
1546                // printed form, thus preserving the round-tripping property.
1547                write!(f, "-({arg})")
1548            }
1549            ExprNoExt::Eq { left, right } => {
1550                maybe_with_parens(f, left, n)?;
1551                write!(f, " == ")?;
1552                maybe_with_parens(f, right, n)
1553            }
1554            ExprNoExt::NotEq { left, right } => {
1555                maybe_with_parens(f, left, n)?;
1556                write!(f, " != ")?;
1557                maybe_with_parens(f, right, n)
1558            }
1559            ExprNoExt::In { left, right } => {
1560                maybe_with_parens(f, left, n)?;
1561                write!(f, " in ")?;
1562                maybe_with_parens(f, right, n)
1563            }
1564            ExprNoExt::Less { left, right } => {
1565                maybe_with_parens(f, left, n)?;
1566                write!(f, " < ")?;
1567                maybe_with_parens(f, right, n)
1568            }
1569            ExprNoExt::LessEq { left, right } => {
1570                maybe_with_parens(f, left, n)?;
1571                write!(f, " <= ")?;
1572                maybe_with_parens(f, right, n)
1573            }
1574            ExprNoExt::Greater { left, right } => {
1575                maybe_with_parens(f, left, n)?;
1576                write!(f, " > ")?;
1577                maybe_with_parens(f, right, n)
1578            }
1579            ExprNoExt::GreaterEq { left, right } => {
1580                maybe_with_parens(f, left, n)?;
1581                write!(f, " >= ")?;
1582                maybe_with_parens(f, right, n)
1583            }
1584            ExprNoExt::And { left, right } => {
1585                maybe_with_parens(f, left, n)?;
1586                write!(f, " && ")?;
1587                maybe_with_parens(f, right, n)
1588            }
1589            ExprNoExt::Or { left, right } => {
1590                maybe_with_parens(f, left, n)?;
1591                write!(f, " || ")?;
1592                maybe_with_parens(f, right, n)
1593            }
1594            ExprNoExt::Add { left, right } => {
1595                maybe_with_parens(f, left, n)?;
1596                write!(f, " + ")?;
1597                maybe_with_parens(f, right, n)
1598            }
1599            ExprNoExt::Sub { left, right } => {
1600                maybe_with_parens(f, left, n)?;
1601                write!(f, " - ")?;
1602                maybe_with_parens(f, right, n)
1603            }
1604            ExprNoExt::Mul { left, right } => {
1605                maybe_with_parens(f, left, n)?;
1606                write!(f, " * ")?;
1607                maybe_with_parens(f, right, n)
1608            }
1609            ExprNoExt::Contains { left, right } => {
1610                maybe_with_parens(f, left, n)?;
1611                write!(f, ".contains({right})")
1612            }
1613            ExprNoExt::ContainsAll { left, right } => {
1614                maybe_with_parens(f, left, n)?;
1615                write!(f, ".containsAll({right})")
1616            }
1617            ExprNoExt::ContainsAny { left, right } => {
1618                maybe_with_parens(f, left, n)?;
1619                write!(f, ".containsAny({right})")
1620            }
1621            ExprNoExt::IsEmpty { arg } => {
1622                maybe_with_parens(f, arg, n)?;
1623                write!(f, ".isEmpty()")
1624            }
1625            ExprNoExt::GetTag { left, right } => {
1626                maybe_with_parens(f, left, n)?;
1627                write!(f, ".getTag({right})")
1628            }
1629            ExprNoExt::HasTag { left, right } => {
1630                maybe_with_parens(f, left, n)?;
1631                write!(f, ".hasTag({right})")
1632            }
1633            ExprNoExt::GetAttr { left, attr } => {
1634                maybe_with_parens(f, left, n)?;
1635                if is_normalized_ident(attr) {
1636                    write!(f, ".{}", attr)
1637                } else {
1638                    write!(f, "[\"{}\"]", attr.escape_debug())
1639                }
1640            }
1641            ExprNoExt::HasAttr(repr) => match repr {
1642                HasAttrRepr::Simple { left, attr } => {
1643                    maybe_with_parens(f, left, n)?;
1644                    if is_normalized_ident(attr) {
1645                        write!(f, " has {}", attr)
1646                    } else {
1647                        write!(f, " has \"{}\"", attr.escape_debug())
1648                    }
1649                }
1650                HasAttrRepr::Extended { left, attr } => {
1651                    maybe_with_parens(f, left, n)?;
1652                    if is_normalized_ident(&attr.head) {
1653                        write!(f, " has {}", attr.head)?;
1654                    } else {
1655                        write!(f, " has \"{}\"", attr.head.escape_debug())?;
1656                    }
1657                    for attr in attr.tail.iter() {
1658                        // TODO: validation, we shouldn't have non-idents here because
1659                        // `principal has "foo".bar` doesn't parse
1660                        if is_normalized_ident(attr) {
1661                            write!(f, ".{}", attr)?;
1662                        } else {
1663                            write!(f, ".\"{}\"", attr.escape_debug())?;
1664                        }
1665                    }
1666                    Ok(())
1667                }
1668            },
1669            ExprNoExt::Like { left, pattern } => {
1670                maybe_with_parens(f, left, n)?;
1671                write!(
1672                    f,
1673                    " like \"{}\"",
1674                    crate::ast::Pattern::from(pattern.as_slice())
1675                )
1676            }
1677            ExprNoExt::Is {
1678                left,
1679                entity_type,
1680                in_expr,
1681            } => {
1682                maybe_with_parens(f, left, n)?;
1683                write!(f, " is {entity_type}")?;
1684                match in_expr {
1685                    Some(in_expr) => {
1686                        write!(f, " in ")?;
1687                        maybe_with_parens(f, in_expr, n)
1688                    }
1689                    None => Ok(()),
1690                }
1691            }
1692            ExprNoExt::If {
1693                cond_expr,
1694                then_expr,
1695                else_expr,
1696            } => {
1697                write!(f, "if ")?;
1698                maybe_with_parens(f, cond_expr, n)?;
1699                write!(f, " then ")?;
1700                maybe_with_parens(f, then_expr, n)?;
1701                write!(f, " else ")?;
1702                maybe_with_parens(f, else_expr, n)
1703            }
1704            ExprNoExt::Set(v) => {
1705                match n {
1706                    Some(n) if v.len() > n => {
1707                        // truncate to n elements
1708                        write!(f, "[")?;
1709                        for element in v.iter().take(n) {
1710                            BoundedDisplay::fmt(element, f, Some(n))?;
1711                            write!(f, ", ")?;
1712                        }
1713                        write!(f, "..]")?;
1714                        Ok(())
1715                    }
1716                    _ => {
1717                        // no truncation
1718                        write!(f, "[")?;
1719                        for (i, element) in v.iter().enumerate() {
1720                            BoundedDisplay::fmt(element, f, n)?;
1721                            if i < v.len() - 1 {
1722                                write!(f, ", ")?;
1723                            }
1724                        }
1725                        write!(f, "]")?;
1726                        Ok(())
1727                    }
1728                }
1729            }
1730            ExprNoExt::Record(m) => {
1731                match n {
1732                    Some(n) if m.len() > n => {
1733                        // truncate to n key-value pairs
1734                        write!(f, "{{")?;
1735                        for (k, v) in m.iter().take(n) {
1736                            if is_normalized_ident(k) {
1737                                write!(f, "{k}: ")?;
1738                            } else {
1739                                write!(f, "\"{}\": ", k.escape_debug())?;
1740                            }
1741                            BoundedDisplay::fmt(v, f, Some(n))?;
1742                            write!(f, ", ")?;
1743                        }
1744                        write!(f, "..}}")?;
1745                        Ok(())
1746                    }
1747                    _ => {
1748                        // no truncation
1749                        write!(f, "{{")?;
1750                        for (i, (k, v)) in m.iter().enumerate() {
1751                            if is_normalized_ident(k) {
1752                                write!(f, "{k}: ")?;
1753                            } else {
1754                                write!(f, "\"{}\": ", k.escape_debug())?;
1755                            }
1756                            BoundedDisplay::fmt(v, f, n)?;
1757                            if i < m.len() - 1 {
1758                                write!(f, ", ")?;
1759                            }
1760                        }
1761                        write!(f, "}}")?;
1762                        Ok(())
1763                    }
1764                }
1765            }
1766            #[cfg(feature = "tolerant-ast")]
1767            ExprNoExt::Error(e) => {
1768                write!(f, "{e}")?;
1769                Ok(())
1770            }
1771        }
1772    }
1773}
1774
1775impl std::fmt::Display for ExtFuncCall {
1776    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1777        BoundedDisplay::fmt_unbounded(self, f)
1778    }
1779}
1780
1781impl BoundedDisplay for ExtFuncCall {
1782    fn fmt(&self, f: &mut impl std::fmt::Write, n: Option<usize>) -> std::fmt::Result {
1783        #[expect(clippy::unwrap_used, reason = "safe due to INVARIANT on `ExtFuncCall`")]
1784        let (fn_name, args) = self.try_components().unwrap();
1785        // search for the name and callstyle
1786        let style = Extensions::all_available().all_funcs().find_map(|ext_fn| {
1787            if &ext_fn.name().to_smolstr() == fn_name {
1788                Some(ext_fn.style())
1789            } else {
1790                None
1791            }
1792        });
1793        match (style, args) {
1794            (Some(ast::CallStyle::MethodStyle), [receiver, rest @ ..]) => {
1795                maybe_with_parens(f, receiver, n)?;
1796                write!(f, ".{}({})", fn_name, rest.iter().join(", "))
1797            }
1798            (_, _) => {
1799                write!(f, "{}({})", fn_name, args.iter().join(", "))
1800            }
1801        }
1802    }
1803}
1804
1805/// returns the `BoundedDisplay` representation of the Expr, adding parens around
1806/// the entire string if necessary.
1807/// E.g., won't add parens for constants or `principal` etc, but will for things
1808/// like `(2 < 5)`.
1809/// When in doubt, add the parens.
1810fn maybe_with_parens(
1811    f: &mut impl std::fmt::Write,
1812    expr: &Expr,
1813    n: Option<usize>,
1814) -> std::fmt::Result {
1815    match expr {
1816        Expr::ExprNoExt(ExprNoExt::Set(_)) |
1817        Expr::ExprNoExt(ExprNoExt::Record(_)) |
1818        Expr::ExprNoExt(ExprNoExt::Value(_)) |
1819        Expr::ExprNoExt(ExprNoExt::Var(_)) |
1820        Expr::ExprNoExt(ExprNoExt::Slot(_)) => BoundedDisplay::fmt(expr, f, n),
1821
1822        // we want parens here because things like parse((!x).y)
1823        // would be printed into !x.y which has a different meaning
1824        Expr::ExprNoExt(ExprNoExt::Not { .. }) |
1825        // we want parens here because things like parse((-x).y)
1826        // would be printed into -x.y which has a different meaning
1827        Expr::ExprNoExt(ExprNoExt::Neg { .. })  |
1828        Expr::ExprNoExt(ExprNoExt::Eq { .. }) |
1829        Expr::ExprNoExt(ExprNoExt::NotEq { .. }) |
1830        Expr::ExprNoExt(ExprNoExt::In { .. }) |
1831        Expr::ExprNoExt(ExprNoExt::Less { .. }) |
1832        Expr::ExprNoExt(ExprNoExt::LessEq { .. }) |
1833        Expr::ExprNoExt(ExprNoExt::Greater { .. }) |
1834        Expr::ExprNoExt(ExprNoExt::GreaterEq { .. }) |
1835        Expr::ExprNoExt(ExprNoExt::And { .. }) |
1836        Expr::ExprNoExt(ExprNoExt::Or { .. }) |
1837        Expr::ExprNoExt(ExprNoExt::Add { .. }) |
1838        Expr::ExprNoExt(ExprNoExt::Sub { .. }) |
1839        Expr::ExprNoExt(ExprNoExt::Mul { .. }) |
1840        Expr::ExprNoExt(ExprNoExt::Contains { .. }) |
1841        Expr::ExprNoExt(ExprNoExt::ContainsAll { .. }) |
1842        Expr::ExprNoExt(ExprNoExt::ContainsAny { .. }) |
1843        Expr::ExprNoExt(ExprNoExt::IsEmpty { .. }) |
1844        Expr::ExprNoExt(ExprNoExt::GetAttr { .. }) |
1845        Expr::ExprNoExt(ExprNoExt::HasAttr { .. }) |
1846        Expr::ExprNoExt(ExprNoExt::GetTag { .. }) |
1847        Expr::ExprNoExt(ExprNoExt::HasTag { .. }) |
1848        Expr::ExprNoExt(ExprNoExt::Like { .. }) |
1849        Expr::ExprNoExt(ExprNoExt::Is { .. }) |
1850        Expr::ExprNoExt(ExprNoExt::If { .. }) |
1851        Expr::ExtFuncCall { .. } => {
1852            write!(f, "(")?;
1853            BoundedDisplay::fmt(expr, f, n)?;
1854            write!(f, ")")?;
1855            Ok(())
1856        },
1857        #[cfg(feature = "tolerant-ast")]
1858        Expr::ExprNoExt(ExprNoExt::Error { .. }) => {
1859            write!(f, "(")?;
1860            BoundedDisplay::fmt(expr, f, n)?;
1861            write!(f, ")")?;
1862            Ok(())
1863        }
1864    }
1865}
1866
1867#[cfg(test)]
1868#[expect(clippy::indexing_slicing, reason = "this is unit test code")]
1869#[expect(clippy::panic, reason = "Unit Test Code")]
1870mod test {
1871    use crate::parser::{
1872        err::{ParseError, ToASTErrorKind},
1873        parse_expr,
1874    };
1875
1876    use super::*;
1877    use ast::BoundedToString;
1878    use cool_asserts::assert_matches;
1879
1880    #[test]
1881    fn test_invalid_expr_from_cst_name() {
1882        let e = crate::parser::text_to_cst::parse_expr("some_long_str::else").unwrap();
1883        assert_matches!(Expr::try_from(&e), Err(e) => {
1884            assert!(e.len() == 1);
1885            assert_matches!(&e[0],
1886                ParseError::ToAST(to_ast_error) => {
1887                    assert_matches!(to_ast_error.kind(), ToASTErrorKind::ReservedIdentifier(s) => {
1888                        assert_eq!(s.to_string(), "else");
1889                    });
1890                }
1891            );
1892        });
1893    }
1894
1895    #[test]
1896    fn display_and_bounded_display() {
1897        let expr = parse_expr(r#"[100, [3, 4, 5], -20, "foo"]"#)
1898            .unwrap()
1899            .into_expr::<Builder>();
1900        assert_eq!(format!("{expr}"), r#"[100, [3, 4, 5], (-20), "foo"]"#);
1901        assert_eq!(
1902            BoundedToString::to_string(&expr, None),
1903            r#"[100, [3, 4, 5], (-20), "foo"]"#
1904        );
1905        assert_eq!(
1906            BoundedToString::to_string(&expr, Some(4)),
1907            r#"[100, [3, 4, 5], (-20), "foo"]"#
1908        );
1909        assert_eq!(
1910            BoundedToString::to_string(&expr, Some(3)),
1911            r#"[100, [3, 4, 5], (-20), ..]"#
1912        );
1913        assert_eq!(
1914            BoundedToString::to_string(&expr, Some(2)),
1915            r#"[100, [3, 4, ..], ..]"#
1916        );
1917        assert_eq!(BoundedToString::to_string(&expr, Some(1)), r#"[100, ..]"#);
1918        assert_eq!(BoundedToString::to_string(&expr, Some(0)), r#"[..]"#);
1919
1920        let expr = parse_expr(
1921            r#"{
1922            a: 12,
1923            b: [3, 4, true],
1924            c: -20,
1925            "hello ∞ world": "∂µß≈¥"
1926        }"#,
1927        )
1928        .unwrap()
1929        .into_expr::<Builder>();
1930        assert_eq!(
1931            format!("{expr}"),
1932            r#"{a: 12, b: [3, 4, true], c: (-20), "hello ∞ world": "∂µß≈¥"}"#
1933        );
1934        assert_eq!(
1935            BoundedToString::to_string(&expr, None),
1936            r#"{a: 12, b: [3, 4, true], c: (-20), "hello ∞ world": "∂µß≈¥"}"#
1937        );
1938        assert_eq!(
1939            BoundedToString::to_string(&expr, Some(4)),
1940            r#"{a: 12, b: [3, 4, true], c: (-20), "hello ∞ world": "∂µß≈¥"}"#
1941        );
1942        assert_eq!(
1943            BoundedToString::to_string(&expr, Some(3)),
1944            r#"{a: 12, b: [3, 4, true], c: (-20), ..}"#
1945        );
1946        assert_eq!(
1947            BoundedToString::to_string(&expr, Some(2)),
1948            r#"{a: 12, b: [3, 4, ..], ..}"#
1949        );
1950        assert_eq!(BoundedToString::to_string(&expr, Some(1)), r#"{a: 12, ..}"#);
1951        assert_eq!(BoundedToString::to_string(&expr, Some(0)), r#"{..}"#);
1952    }
1953
1954    #[test]
1955    fn display_get_attr() {
1956        // Ensuring we prefer printing Expr::GetAttr in . notation if the attribute contains valid identifiers.
1957        let expr = parse_expr(r#"context.foo"#).unwrap().into_expr::<Builder>();
1958        assert_eq!(format!("{expr}"), r#"context.foo"#);
1959
1960        let expr = parse_expr(r#"context["foo"]"#)
1961            .unwrap()
1962            .into_expr::<Builder>();
1963        assert_eq!(format!("{expr}"), r#"context.foo"#);
1964
1965        // Ensure we escape the identifier if it contains whitespaces.
1966        let expr = parse_expr(r#"context["foo "]"#)
1967            .unwrap()
1968            .into_expr::<Builder>();
1969        assert_eq!(format!("{expr}"), r#"context["foo "]"#);
1970
1971        let expr = parse_expr(r#"context["foo-baz"]"#)
1972            .unwrap()
1973            .into_expr::<Builder>();
1974        assert_eq!(format!("{expr}"), r#"context["foo-baz"]"#);
1975
1976        let expr = parse_expr(r#"context["true"]"#)
1977            .unwrap()
1978            .into_expr::<Builder>();
1979        assert_eq!(format!("{expr}"), r#"context["true"]"#);
1980
1981        // Similarly for Expr::HasAttr
1982        let expr = parse_expr(r#"context has foo"#)
1983            .unwrap()
1984            .into_expr::<Builder>();
1985        assert_eq!(format!("{expr}"), r#"context has foo"#);
1986
1987        let expr = parse_expr(r#"context has "foo""#)
1988            .unwrap()
1989            .into_expr::<Builder>();
1990        assert_eq!(format!("{expr}"), r#"context has foo"#);
1991
1992        let expr = parse_expr(r#"context has "foo-baz""#)
1993            .unwrap()
1994            .into_expr::<Builder>();
1995        assert_eq!(format!("{expr}"), r#"context has "foo-baz""#);
1996
1997        let expr = parse_expr(r#"if context has "if" then false else true"#)
1998            .unwrap()
1999            .into_expr::<Builder>();
2000        assert_eq!(
2001            format!("{expr}"),
2002            r#"if (context has "if") then false else true"#
2003        );
2004
2005        let expr = parse_expr(r#"context has "has""#)
2006            .unwrap()
2007            .into_expr::<Builder>();
2008        assert_eq!(format!("{expr}"), r#"context has "has""#);
2009
2010        let expr = parse_expr(r#"if context has "foo-baz" then false else true"#)
2011            .unwrap()
2012            .into_expr::<Builder>();
2013        assert_eq!(
2014            format!("{expr}"),
2015            r#"if (context has "foo-baz") then false else true"#
2016        );
2017    }
2018
2019    #[test]
2020    fn has_attr_serde() {
2021        use nonempty::nonempty;
2022        let test_cases = vec![
2023            // Has can have a single string argument.
2024            (
2025                Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple {
2026                    left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2027                    attr: "department".into(),
2028                })),
2029                serde_json::json!({"has": {"left": {"Var": "principal"}, "attr": "department"}}),
2030            ),
2031            // Has can have a singleton list as argument
2032            (
2033                Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2034                    left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2035                    attr: nonempty!["user".into()],
2036                })),
2037                serde_json::json!({"has": {"left": {"Var": "context"}, "attr": ["user"]}}),
2038            ),
2039            // Has can have a list of strings as argument
2040            (
2041                Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2042                    left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2043                    attr: nonempty!["user".into(), "profile".into(), "email".into()],
2044                })),
2045                serde_json::json!({"has": {"left": {"Var": "context"}, "attr": ["user", "profile", "email"]}}),
2046            ),
2047        ];
2048        for (expr, json_repr) in test_cases {
2049            // Serialize = json_repr
2050            assert_eq!(serde_json::to_value(&expr).unwrap(), json_repr);
2051            // expr = Deserialize
2052            assert_eq!(expr, serde_json::from_value(json_repr).unwrap());
2053        }
2054    }
2055
2056    #[test]
2057    fn deserialize_has_attr_empty_errors() {
2058        let json = serde_json::json!({"has": {"left": {"Var": "context"}, "attr": []}});
2059        let expr_result: Result<Expr, _> = serde_json::from_value(json);
2060        assert!(expr_result.is_err())
2061    }
2062
2063    #[test]
2064    fn extended_has_display() {
2065        // Other display functions are tested from the ast, but we can't curretly do that for the
2066        // extended has operator
2067        use nonempty::nonempty;
2068
2069        // Simple has
2070        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Simple {
2071            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Principal))),
2072            attr: "department".into(),
2073        }));
2074        assert_eq!(format!("{expr}"), "principal has department");
2075
2076        // Extended has with single attribute
2077        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2078            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2079            attr: nonempty!["user".into()],
2080        }));
2081        assert_eq!(format!("{expr}"), "context has user");
2082
2083        // Extended has with multiple attributes
2084        let expr = Expr::ExprNoExt(ExprNoExt::HasAttr(HasAttrRepr::Extended {
2085            left: Arc::new(Expr::ExprNoExt(ExprNoExt::Var(ast::Var::Context))),
2086            attr: nonempty!["user".into(), "profile".into(), "email".into()],
2087        }));
2088        assert_eq!(format!("{expr}"), "context has user.profile.email");
2089    }
2090}