cedar_policy_core/
est.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This module contains the External Syntax Tree (EST)
18
19mod err;
20pub use err::*;
21mod expr;
22pub use expr::*;
23mod policy_set;
24pub use policy_set::*;
25mod scope_constraints;
26pub use scope_constraints::*;
27mod annotation;
28pub use annotation::*;
29
30use crate::ast::EntityUID;
31use crate::ast::{self, Annotation};
32use crate::entities::json::{err::JsonDeserializationError, EntityUidJson};
33use crate::expr_builder::ExprBuilder;
34use crate::parser::cst;
35use crate::parser::err::{parse_errors, ParseErrors, ToASTError, ToASTErrorKind};
36use crate::parser::util::{flatten_tuple_2, flatten_tuple_4};
37#[cfg(feature = "tolerant-ast")]
38use crate::parser::Loc;
39use serde::{Deserialize, Serialize};
40use serde_with::serde_as;
41use std::collections::{BTreeMap, HashMap};
42
43#[cfg(feature = "wasm")]
44extern crate tsify;
45
46/// Serde JSON structure for policies and templates in the EST format
47/// Note: Before attempting to build an `est::Policy` from a `cst::Policy` you
48/// must first ensure that the CST can be transformed into an AST. The
49/// CST-to-EST transformation does not duplicate all checks performed by the
50/// CST-to-AST transformation, so attempting to convert an invalid CST to an EST
51/// may succeed.
52#[serde_as]
53#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
54#[serde(deny_unknown_fields)]
55#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
56#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
57#[cfg_attr(feature = "wasm", serde(rename = "PolicyJson"))]
58pub struct Policy {
59    /// `Effect` of the policy or template
60    effect: ast::Effect,
61    /// Principal scope constraint
62    principal: PrincipalConstraint,
63    /// Action scope constraint
64    action: ActionConstraint,
65    /// Resource scope constraint
66    resource: ResourceConstraint,
67    /// `when` and/or `unless` clauses
68    conditions: Vec<Clause>,
69    /// annotations
70    #[serde(default)]
71    #[serde(skip_serializing_if = "Annotations::is_empty")]
72    annotations: Annotations,
73}
74
75/// Serde JSON structure for a `when` or `unless` clause in the EST format
76#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
77#[serde(deny_unknown_fields)]
78#[serde(tag = "kind", content = "body")]
79#[serde(rename_all = "camelCase")]
80#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
81#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
82pub enum Clause {
83    /// A `when` clause
84    When(Expr),
85    /// An `unless` clause
86    Unless(Expr),
87}
88
89impl Policy {
90    /// Fill in any slots in the policy using the values in `vals`. Throws an
91    /// error if `vals` doesn't contain a necessary mapping, but does not throw
92    /// an error if `vals` contains unused mappings -- and in particular if
93    /// `self` is an inline policy (in which case it is returned unchanged).
94    pub fn link(self, vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
95        Ok(Policy {
96            effect: self.effect,
97            principal: self.principal.link(vals)?,
98            action: self.action.link(vals)?,
99            resource: self.resource.link(vals)?,
100            conditions: self
101                .conditions
102                .into_iter()
103                .map(|clause| clause.link(vals))
104                .collect::<Result<Vec<_>, _>>()?,
105            annotations: self.annotations,
106        })
107    }
108
109    /// Substitute entity literals
110    pub fn sub_entity_literals(
111        self,
112        mapping: &BTreeMap<EntityUID, EntityUID>,
113    ) -> Result<Self, JsonDeserializationError> {
114        Ok(Policy {
115            effect: self.effect,
116            principal: self.principal.sub_entity_literals(mapping)?,
117            action: self.action.sub_entity_literals(mapping)?,
118            resource: self.resource.sub_entity_literals(mapping)?,
119            conditions: self
120                .conditions
121                .into_iter()
122                .map(|clause| clause.sub_entity_literals(mapping))
123                .collect::<Result<Vec<_>, _>>()?,
124            annotations: self.annotations,
125        })
126    }
127
128    /// Returns true if this policy is a template, i.e., it has at least one slot.
129    pub fn is_template(&self) -> bool {
130        self.principal.has_slot()
131            || self.action.has_slot()
132            || self.resource.has_slot()
133            || self.conditions.iter().any(|c| c.has_slot())
134    }
135}
136
137impl Clause {
138    /// Fill in any slots in the clause using the values in `vals`. Throws an
139    /// error if `vals` doesn't contain a necessary mapping, but does not throw
140    /// an error if `vals` contains unused mappings.
141    pub fn link(self, _vals: &HashMap<ast::SlotId, EntityUidJson>) -> Result<Self, LinkingError> {
142        // currently, slots are not allowed in clauses
143        Ok(self)
144    }
145
146    /// Substitute entity literals
147    pub fn sub_entity_literals(
148        self,
149        mapping: &BTreeMap<EntityUID, EntityUID>,
150    ) -> Result<Self, JsonDeserializationError> {
151        use Clause::{Unless, When};
152        match self {
153            When(e) => Ok(When(e.sub_entity_literals(mapping)?)),
154            Unless(e) => Ok(Unless(e.sub_entity_literals(mapping)?)),
155        }
156    }
157
158    /// Returns true if this clause has a slot.
159    pub fn has_slot(&self) -> bool {
160        // currently, slots are not allowed in clauses
161        false
162    }
163}
164
165impl TryFrom<cst::Policy> for Policy {
166    type Error = ParseErrors;
167    fn try_from(policy: cst::Policy) -> Result<Policy, ParseErrors> {
168        let policy = match policy {
169            cst::Policy::Policy(policy_impl) => policy_impl,
170            #[cfg(feature = "tolerant-ast")]
171            cst::Policy::PolicyError => {
172                return Err(ParseErrors::singleton(ToASTError::new(
173                    ToASTErrorKind::CSTErrorNode,
174                    // Since we don't have a loc when doing this transformation, we create an arbitrary one
175                    Loc::new(0..1, "CSTErrorNode".into()),
176                )));
177            }
178        };
179        let maybe_effect = policy.effect.to_effect();
180        let maybe_scope = policy.extract_scope();
181        let maybe_annotations = policy.get_ast_annotations(|v, l| {
182            Some(Annotation {
183                val: v?,
184                loc: Some(l.clone()),
185            })
186        });
187        let maybe_conditions = ParseErrors::transpose(policy.conds.into_iter().map(|node| {
188            let (cond, loc) = node.into_inner();
189            let cond = cond.ok_or_else(|| {
190                ParseErrors::singleton(ToASTError::new(ToASTErrorKind::EmptyClause(None), loc))
191            })?;
192            cond.try_into()
193        }));
194
195        let (effect, annotations, (principal, action, resource), conditions) = flatten_tuple_4(
196            maybe_effect,
197            maybe_annotations,
198            maybe_scope,
199            maybe_conditions,
200        )?;
201        Ok(Policy {
202            effect,
203            principal: principal.into(),
204            action: action.into(),
205            resource: resource.into(),
206            conditions,
207            annotations: Annotations(annotations),
208        })
209    }
210}
211
212impl TryFrom<cst::Cond> for Clause {
213    type Error = ParseErrors;
214    fn try_from(cond: cst::Cond) -> Result<Clause, ParseErrors> {
215        let maybe_is_when = cond.cond.to_cond_is_when();
216        match cond.expr {
217            None => {
218                let maybe_ident = maybe_is_when.map(|is_when| {
219                    cst::Ident::Ident(if is_when { "when" } else { "unless" }.into())
220                });
221                Err(cond
222                    .cond
223                    .to_ast_err(ToASTErrorKind::EmptyClause(maybe_ident.ok()))
224                    .into())
225            }
226            Some(ref e) => {
227                let maybe_expr = e.try_into();
228                let (is_when, expr) = flatten_tuple_2(maybe_is_when, maybe_expr)?;
229                Ok(if is_when {
230                    Clause::When(expr)
231                } else {
232                    Clause::Unless(expr)
233                })
234            }
235        }
236    }
237}
238
239impl Policy {
240    /// Try to convert a [`Policy`] into a [`ast::Policy`].
241    ///
242    /// This process requires a policy ID. If not supplied, this method will
243    /// fill it in as "JSON policy".
244    pub fn try_into_ast_policy(
245        self,
246        id: Option<ast::PolicyID>,
247    ) -> Result<ast::Policy, FromJsonError> {
248        let template: ast::Template = self.try_into_ast_policy_or_template(id)?;
249        ast::StaticPolicy::try_from(template)
250            .map(Into::into)
251            .map_err(Into::into)
252    }
253
254    /// Try to convert a [`Policy`] into a [`ast::Template`]. Returns an error
255    /// if the input is a static policy.
256    ///
257    /// This process requires a policy ID. If not supplied, this method will
258    /// fill it in as "JSON policy".
259    pub fn try_into_ast_template(
260        self,
261        id: Option<ast::PolicyID>,
262    ) -> Result<ast::Template, FromJsonError> {
263        let template: ast::Template = self.try_into_ast_policy_or_template(id)?;
264        if template.slots().count() == 0 {
265            Err(FromJsonError::PolicyToTemplate(
266                parse_errors::ExpectedTemplate::new(),
267            ))
268        } else {
269            Ok(template)
270        }
271    }
272
273    /// Try to convert a [`Policy`] into a [`ast::Template`]. The `Template` may
274    /// represent a template or static policy (which is a template with zero slots).
275    ///
276    /// This process requires a policy ID. If not supplied, this method will
277    /// fill it in as "JSON policy".
278    pub fn try_into_ast_policy_or_template(
279        self,
280        id: Option<ast::PolicyID>,
281    ) -> Result<ast::Template, FromJsonError> {
282        let id = id.unwrap_or_else(|| ast::PolicyID::from_string("JSON policy"));
283        let mut conditions_iter = self
284            .conditions
285            .into_iter()
286            .map(|cond| cond.try_into_ast(id.clone()));
287        let conditions = match conditions_iter.next() {
288            None => ast::Expr::val(true),
289            Some(first) => ast::ExprBuilder::with_data(())
290                .and_nary(first?, conditions_iter.collect::<Result<Vec<_>, _>>()?),
291        };
292        Ok(ast::Template::new(
293            id,
294            None,
295            self.annotations
296                .0
297                .into_iter()
298                .map(|(key, val)| {
299                    (
300                        key,
301                        ast::Annotation::with_optional_value(val.map(|v| v.val), None),
302                    )
303                })
304                .collect(),
305            self.effect,
306            self.principal.try_into()?,
307            self.action.try_into()?,
308            self.resource.try_into()?,
309            conditions,
310        ))
311    }
312}
313
314impl Clause {
315    fn filter_slots(e: ast::Expr, is_when: bool) -> Result<ast::Expr, FromJsonError> {
316        let first_slot = e.slots().next();
317        if let Some(slot) = first_slot {
318            Err(parse_errors::SlotsInConditionClause {
319                slot,
320                clause_type: if is_when { "when" } else { "unless" },
321            }
322            .into())
323        } else {
324            Ok(e)
325        }
326    }
327    /// `id` is the ID of the policy the clause belongs to, used only for reporting errors
328    fn try_into_ast(self, id: ast::PolicyID) -> Result<ast::Expr, FromJsonError> {
329        match self {
330            Clause::When(expr) => Self::filter_slots(expr.try_into_ast(id)?, true),
331            Clause::Unless(expr) => {
332                Self::filter_slots(ast::Expr::not(expr.try_into_ast(id)?), false)
333            }
334        }
335    }
336}
337
338/// Convert AST to EST
339impl From<ast::Policy> for Policy {
340    fn from(ast: ast::Policy) -> Policy {
341        Policy {
342            effect: ast.effect(),
343            principal: ast.principal_constraint().into(),
344            action: ast.action_constraint().clone().into(),
345            resource: ast.resource_constraint().into(),
346            conditions: vec![ast.non_scope_constraints().clone().into()],
347            annotations: Annotations(
348                ast.annotations()
349                    // When converting from AST to EST, we will always interpret an
350                    // empty-string annotation as an explicit `""` rather than
351                    // `null` (which is implicitly equivalent to `""`).
352                    .map(|(k, v)| (k.clone(), Some(v.clone())))
353                    .collect(),
354            ),
355        }
356    }
357}
358
359/// Convert AST to EST
360impl From<ast::Template> for Policy {
361    fn from(ast: ast::Template) -> Policy {
362        Policy {
363            effect: ast.effect(),
364            principal: ast.principal_constraint().clone().into(),
365            action: ast.action_constraint().clone().into(),
366            resource: ast.resource_constraint().clone().into(),
367            conditions: vec![ast.non_scope_constraints().clone().into()],
368            annotations: Annotations(
369                ast.annotations()
370                    // When converting from AST to EST, we will always interpret an
371                    // empty-string annotation as an explicit `""` rather than
372                    // `null` (which is implicitly equivalent to `""`)
373                    .map(|(k, v)| (k.clone(), Some(v.clone())))
374                    .collect(),
375            ),
376        }
377    }
378}
379
380impl<T: Clone> From<ast::Expr<T>> for Clause {
381    fn from(expr: ast::Expr<T>) -> Clause {
382        Clause::When(expr.into_expr::<Builder>())
383    }
384}
385
386impl std::fmt::Display for Policy {
387    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
388        for (k, v) in self.annotations.0.iter() {
389            write!(f, "@{k}")?;
390            if let Some(v) = v {
391                write!(f, "({v})")?;
392            }
393            writeln!(f)?;
394        }
395        write!(
396            f,
397            "{}({}, {}, {})",
398            self.effect, self.principal, self.action, self.resource
399        )?;
400        for condition in &self.conditions {
401            write!(f, " {condition}")?;
402        }
403        write!(f, ";")
404    }
405}
406
407impl std::fmt::Display for Clause {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        match self {
410            Self::When(expr) => write!(f, "when {{ {expr} }}"),
411            Self::Unless(expr) => write!(f, "unless {{ {expr} }}"),
412        }
413    }
414}
415
416// PANIC SAFETY: Unit Test Code
417#[allow(clippy::panic)]
418// PANIC SAFETY: Unit Test Code
419#[allow(clippy::indexing_slicing)]
420#[cfg(test)]
421mod test {
422    use super::*;
423    use crate::parser::{self, parse_policy_or_template_to_est};
424    use crate::test_utils::*;
425    use cool_asserts::assert_matches;
426    use serde_json::json;
427
428    /// helper function to just do EST data structure --> JSON --> EST data structure.
429    /// This roundtrip should be lossless for all policies.
430    #[track_caller]
431    fn est_roundtrip(est: Policy) -> Policy {
432        let json = serde_json::to_value(est).expect("failed to serialize to JSON");
433        serde_json::from_value(json.clone()).unwrap_or_else(|e| {
434            panic!(
435                "failed to deserialize from JSON: {e}\n\nJSON was:\n{}",
436                serde_json::to_string_pretty(&json).expect("failed to convert JSON to string")
437            )
438        })
439    }
440
441    /// helper function to take EST-->text-->CST-->EST, which directly tests the Display impl for EST.
442    /// This roundtrip should be lossless for all policies.
443    #[track_caller]
444    fn text_roundtrip(est: &Policy) -> Policy {
445        let text = est.to_string();
446        let cst = parser::text_to_cst::parse_policy(&text)
447            .expect("Failed to convert to CST")
448            .node
449            .expect("Node should not be empty");
450        cst.try_into().expect("Failed to convert to EST")
451    }
452
453    /// helper function to take EST-->AST-->EST for inline policies.
454    /// This roundtrip is not always lossless, because EST-->AST can be lossy.
455    #[track_caller]
456    fn ast_roundtrip(est: Policy) -> Policy {
457        let ast = est
458            .try_into_ast_policy(None)
459            .expect("Failed to convert to AST");
460        ast.into()
461    }
462
463    /// helper function to take EST-->AST-->EST for templates.
464    /// This roundtrip is not always lossless, because EST-->AST can be lossy.
465    #[track_caller]
466    fn ast_roundtrip_template(est: Policy) -> Policy {
467        let ast = est
468            .try_into_ast_policy_or_template(None)
469            .expect("Failed to convert to AST");
470        ast.into()
471    }
472
473    /// helper function to take EST-->AST-->text-->CST-->EST for inline policies.
474    /// This roundtrip is not always lossless, because EST-->AST can be lossy.
475    #[track_caller]
476    fn circular_roundtrip(est: Policy) -> Policy {
477        let ast = est
478            .try_into_ast_policy(None)
479            .expect("Failed to convert to AST");
480        let text = ast.to_string();
481        let cst = parser::text_to_cst::parse_policy(&text)
482            .expect("Failed to convert to CST")
483            .node
484            .expect("Node should not be empty");
485        cst.try_into().expect("Failed to convert to EST")
486    }
487
488    /// helper function to take EST-->AST-->text-->CST-->EST for templates.
489    /// This roundtrip is not always lossless, because EST-->AST can be lossy.
490    #[track_caller]
491    fn circular_roundtrip_template(est: Policy) -> Policy {
492        let ast = est
493            .try_into_ast_policy_or_template(None)
494            .expect("Failed to convert to AST");
495        let text = ast.to_string();
496        let cst = parser::text_to_cst::parse_policy(&text)
497            .expect("Failed to convert to CST")
498            .node
499            .expect("Node should not be empty");
500        cst.try_into().expect("Failed to convert to EST")
501    }
502
503    #[test]
504    fn empty_policy() {
505        let policy = "permit(principal, action, resource);";
506        let cst = parser::text_to_cst::parse_policy(policy)
507            .unwrap()
508            .node
509            .unwrap();
510        let est: Policy = cst.try_into().unwrap();
511        let expected_json = json!(
512            {
513                "effect": "permit",
514                "principal": {
515                    "op": "All",
516                },
517                "action": {
518                    "op": "All",
519                },
520                "resource": {
521                    "op": "All",
522                },
523                "conditions": [],
524            }
525        );
526        assert_eq!(
527            serde_json::to_value(&est).unwrap(),
528            expected_json,
529            "\nExpected:\n{}\n\nActual:\n{}\n\n",
530            serde_json::to_string_pretty(&expected_json).unwrap(),
531            serde_json::to_string_pretty(&est).unwrap()
532        );
533        let old_est = est.clone();
534        let roundtripped = est_roundtrip(est);
535        assert_eq!(&old_est, &roundtripped);
536        let est = text_roundtrip(&old_est);
537        assert_eq!(&old_est, &est);
538
539        // during the lossy transform to AST, the only difference for this policy is that
540        // a `when { true }` is added
541        let expected_json_after_roundtrip = json!(
542            {
543                "effect": "permit",
544                "principal": {
545                    "op": "All",
546                },
547                "action": {
548                    "op": "All",
549                },
550                "resource": {
551                    "op": "All",
552                },
553                "conditions": [
554                    {
555                        "kind": "when",
556                        "body": {
557                            "Value": true
558                        }
559                    }
560                ],
561            }
562        );
563        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
564        assert_eq!(
565            roundtripped,
566            expected_json_after_roundtrip,
567            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
568            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
569            serde_json::to_string_pretty(&roundtripped).unwrap()
570        );
571        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
572        assert_eq!(
573            roundtripped,
574            expected_json_after_roundtrip,
575            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
576            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
577            serde_json::to_string_pretty(&roundtripped).unwrap()
578        );
579    }
580
581    #[test]
582    fn annotated_policy() {
583        let policy = r#"
584            @foo("bar")
585            @this1is2a3valid_identifier("any arbitrary ! string \" is @ allowed in 🦀 here_")
586            permit(principal, action, resource);
587        "#;
588        let cst = parser::text_to_cst::parse_policy(policy)
589            .unwrap()
590            .node
591            .unwrap();
592        let est: Policy = cst.try_into().unwrap();
593        let expected_json = json!(
594            {
595                "effect": "permit",
596                "principal": {
597                    "op": "All",
598                },
599                "action": {
600                    "op": "All",
601                },
602                "resource": {
603                    "op": "All",
604                },
605                "conditions": [],
606                "annotations": {
607                    "foo": "bar",
608                    "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
609                }
610            }
611        );
612        assert_eq!(
613            serde_json::to_value(&est).unwrap(),
614            expected_json,
615            "\nExpected:\n{}\n\nActual:\n{}\n\n",
616            serde_json::to_string_pretty(&expected_json).unwrap(),
617            serde_json::to_string_pretty(&est).unwrap()
618        );
619        let old_est = est.clone();
620        let roundtripped = est_roundtrip(est);
621        assert_eq!(&old_est, &roundtripped);
622        let est = text_roundtrip(&old_est);
623        assert_eq!(&old_est, &est);
624
625        // during the lossy transform to AST, the only difference for this policy is that
626        // a `when { true }` is added
627        let expected_json_after_roundtrip = json!(
628            {
629                "effect": "permit",
630                "principal": {
631                    "op": "All",
632                },
633                "action": {
634                    "op": "All",
635                },
636                "resource": {
637                    "op": "All",
638                },
639                "conditions": [
640                    {
641                        "kind": "when",
642                        "body": {
643                            "Value": true
644                        }
645                    }
646                ],
647                "annotations": {
648                    "foo": "bar",
649                    "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
650                }
651            }
652        );
653        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
654        assert_eq!(
655            roundtripped,
656            expected_json_after_roundtrip,
657            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
658            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
659            serde_json::to_string_pretty(&roundtripped).unwrap()
660        );
661        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
662        assert_eq!(
663            roundtripped,
664            expected_json_after_roundtrip,
665            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
666            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
667            serde_json::to_string_pretty(&roundtripped).unwrap()
668        );
669    }
670
671    #[test]
672    fn annotated_without_value_policy() {
673        let policy = r#"@foo permit(principal, action, resource);"#;
674        let cst = parser::text_to_cst::parse_policy(policy)
675            .unwrap()
676            .node
677            .unwrap();
678        let est: Policy = cst.try_into().unwrap();
679        let expected_json = json!(
680            {
681                "effect": "permit",
682                "principal": {
683                    "op": "All",
684                },
685                "action": {
686                    "op": "All",
687                },
688                "resource": {
689                    "op": "All",
690                },
691                "conditions": [],
692                "annotations": { "foo": null, }
693            }
694        );
695        assert_eq!(
696            serde_json::to_value(&est).unwrap(),
697            expected_json,
698            "\nExpected:\n{}\n\nActual:\n{}\n\n",
699            serde_json::to_string_pretty(&expected_json).unwrap(),
700            serde_json::to_string_pretty(&est).unwrap()
701        );
702        let old_est = est.clone();
703        let roundtripped = est_roundtrip(est);
704        assert_eq!(&old_est, &roundtripped);
705        let est = text_roundtrip(&old_est);
706        assert_eq!(&old_est, &est);
707
708        // during the lossy transform to AST, the `null` annotation becomes an empty string
709        let expected_json_after_roundtrip = json!(
710            {
711                "effect": "permit",
712                "principal": {
713                    "op": "All",
714                },
715                "action": {
716                    "op": "All",
717                },
718                "resource": {
719                    "op": "All",
720                },
721                "conditions": [
722                    {
723                        "kind": "when",
724                        "body": {
725                            "Value": true
726                        }
727                    }
728                ],
729                "annotations": { "foo": "", }
730            }
731        );
732        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
733        assert_eq!(
734            roundtripped,
735            expected_json_after_roundtrip,
736            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
737            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
738            serde_json::to_string_pretty(&roundtripped).unwrap()
739        );
740        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
741        assert_eq!(
742            roundtripped,
743            expected_json_after_roundtrip,
744            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
745            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
746            serde_json::to_string_pretty(&roundtripped).unwrap()
747        );
748    }
749
750    /// Test that we can use Cedar reserved words like `if` and `has` as annotation keys
751    #[test]
752    fn reserved_words_as_annotations() {
753        let policy = r#"
754            @if("this is the annotation for `if`")
755            @then("this is the annotation for `then`")
756            @else("this is the annotation for `else`")
757            @true("this is the annotation for `true`")
758            @false("this is the annotation for `false`")
759            @in("this is the annotation for `in`")
760            @is("this is the annotation for `is`")
761            @like("this is the annotation for `like`")
762            @has("this is the annotation for `has`")
763            @principal("this is the annotation for `principal`") // not reserved at time of this writing, but we test it anyway
764            permit(principal, action, resource) when { 2 == 2 };
765        "#;
766
767        let cst = parser::text_to_cst::parse_policy(policy)
768            .unwrap()
769            .node
770            .unwrap();
771        let est: Policy = cst.try_into().unwrap();
772        let expected_json = json!(
773            {
774                "effect": "permit",
775                "principal": {
776                    "op": "All",
777                },
778                "action": {
779                    "op": "All",
780                },
781                "resource": {
782                    "op": "All",
783                },
784                "conditions": [
785                    {
786                        "kind": "when",
787                        "body": {
788                            "==": {
789                                "left": { "Value": 2 },
790                                "right": { "Value": 2 },
791                            }
792                        }
793                    }
794                ],
795                "annotations": {
796                    "if": "this is the annotation for `if`",
797                    "then": "this is the annotation for `then`",
798                    "else": "this is the annotation for `else`",
799                    "true": "this is the annotation for `true`",
800                    "false": "this is the annotation for `false`",
801                    "in": "this is the annotation for `in`",
802                    "is": "this is the annotation for `is`",
803                    "like": "this is the annotation for `like`",
804                    "has": "this is the annotation for `has`",
805                    "principal": "this is the annotation for `principal`",
806                }
807            }
808        );
809        assert_eq!(
810            serde_json::to_value(&est).unwrap(),
811            expected_json,
812            "\nExpected:\n{}\n\nActual:\n{}\n\n",
813            serde_json::to_string_pretty(&expected_json).unwrap(),
814            serde_json::to_string_pretty(&est).unwrap()
815        );
816        let old_est = est.clone();
817        let roundtripped = est_roundtrip(est);
818        assert_eq!(&old_est, &roundtripped);
819        let est = text_roundtrip(&old_est);
820        assert_eq!(&old_est, &est);
821
822        assert_eq!(ast_roundtrip(est.clone()), est);
823        assert_eq!(circular_roundtrip(est.clone()), est);
824    }
825
826    #[test]
827    fn annotation_errors() {
828        let policy = r#"
829            @foo("1")
830            @foo("2")
831            permit(principal, action, resource);
832        "#;
833        let cst = parser::text_to_cst::parse_policy(policy)
834            .unwrap()
835            .node
836            .unwrap();
837        assert_matches!(Policy::try_from(cst), Err(e) => {
838            parser::test_utils::expect_exactly_one_error(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("2")"#).build());
839        });
840
841        let policy = r#"
842            @foo("1")
843            @foo("1")
844            permit(principal, action, resource);
845        "#;
846        let cst = parser::text_to_cst::parse_policy(policy)
847            .unwrap()
848            .node
849            .unwrap();
850        assert_matches!(Policy::try_from(cst), Err(e) => {
851            parser::test_utils::expect_exactly_one_error(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("1")"#).build());
852        });
853
854        let policy = r#"
855            @foo("1")
856            @bar("yellow")
857            @foo("abc")
858            @hello("goodbye")
859            @bar("123")
860            @foo("def")
861            permit(principal, action, resource);
862        "#;
863        let cst = parser::text_to_cst::parse_policy(policy)
864            .unwrap()
865            .node
866            .unwrap();
867        assert_matches!(Policy::try_from(cst), Err(e) => {
868            assert_eq!(e.len(), 3); // two errors for @foo and one for @bar
869            parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("abc")"#).build());
870            parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("def")"#).build());
871            parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @bar").exactly_one_underline(r#"@bar("123")"#).build());
872        });
873
874        // the above tests ensure that we give the correct errors for CSTs
875        // containing duplicate annotations.
876        // This test ensures that we give the correct errors for JSON text
877        // containing duplicate annotations.
878        // Note that we have to use a string here as input (and not
879        // serde_json::Value) because serde_json::Value would already remove
880        // duplicates
881        let est = r#"
882            {
883                "effect": "permit",
884                "principal": {
885                    "op": "All"
886                },
887                "action": {
888                    "op": "All"
889                },
890                "resource": {
891                    "op": "All"
892                },
893                "conditions": [],
894                "annotations": {
895                    "foo": "1",
896                    "foo": "2"
897                }
898            }
899        "#;
900        assert_matches!(serde_json::from_str::<Policy>(est), Err(e) => {
901            assert_eq!(e.to_string(), "invalid entry: found duplicate key at line 17 column 17");
902        });
903    }
904
905    #[test]
906    fn rbac_policy() {
907        let policy = r#"
908            permit(
909                principal == User::"12UA45",
910                action == Action::"view",
911                resource in Folder::"abc"
912            );
913        "#;
914        let cst = parser::text_to_cst::parse_policy(policy)
915            .unwrap()
916            .node
917            .unwrap();
918        let est: Policy = cst.try_into().unwrap();
919        let expected_json = json!(
920            {
921                "effect": "permit",
922                "principal": {
923                    "op": "==",
924                    "entity": { "type": "User", "id": "12UA45" },
925                },
926                "action": {
927                    "op": "==",
928                    "entity": { "type": "Action", "id": "view" },
929                },
930                "resource": {
931                    "op": "in",
932                    "entity": { "type": "Folder", "id": "abc" },
933                },
934                "conditions": []
935            }
936        );
937        assert_eq!(
938            serde_json::to_value(&est).unwrap(),
939            expected_json,
940            "\nExpected:\n{}\n\nActual:\n{}\n\n",
941            serde_json::to_string_pretty(&expected_json).unwrap(),
942            serde_json::to_string_pretty(&est).unwrap()
943        );
944        let old_est = est.clone();
945        let roundtripped = est_roundtrip(est);
946        assert_eq!(&old_est, &roundtripped);
947        let est = text_roundtrip(&old_est);
948        assert_eq!(&old_est, &est);
949
950        // during the lossy transform to AST, the only difference for this policy is that
951        // a `when { true }` is added
952        let expected_json_after_roundtrip = json!(
953            {
954                "effect": "permit",
955                "principal": {
956                    "op": "==",
957                    "entity": { "type": "User", "id": "12UA45" },
958                },
959                "action": {
960                    "op": "==",
961                    "entity": { "type": "Action", "id": "view" },
962                },
963                "resource": {
964                    "op": "in",
965                    "entity": { "type": "Folder", "id": "abc" },
966                },
967                "conditions": [
968                    {
969                        "kind": "when",
970                        "body": {
971                            "Value": true
972                        }
973                    }
974                ]
975            }
976        );
977        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
978        assert_eq!(
979            roundtripped,
980            expected_json_after_roundtrip,
981            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
982            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
983            serde_json::to_string_pretty(&roundtripped).unwrap()
984        );
985        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
986        assert_eq!(
987            roundtripped,
988            expected_json_after_roundtrip,
989            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
990            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
991            serde_json::to_string_pretty(&roundtripped).unwrap()
992        );
993    }
994
995    #[test]
996    fn rbac_template() {
997        let template = r#"
998            permit(
999                principal == ?principal,
1000                action == Action::"view",
1001                resource in ?resource
1002            );
1003        "#;
1004        let cst = parser::text_to_cst::parse_policy(template)
1005            .unwrap()
1006            .node
1007            .unwrap();
1008        let est: Policy = cst.try_into().unwrap();
1009        let expected_json = json!(
1010            {
1011                "effect": "permit",
1012                "principal": {
1013                    "op": "==",
1014                    "slot": "?principal",
1015                },
1016                "action": {
1017                    "op": "==",
1018                    "entity": { "type": "Action", "id": "view" },
1019                },
1020                "resource": {
1021                    "op": "in",
1022                    "slot": "?resource",
1023                },
1024                "conditions": []
1025            }
1026        );
1027        assert_eq!(
1028            serde_json::to_value(&est).unwrap(),
1029            expected_json,
1030            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1031            serde_json::to_string_pretty(&expected_json).unwrap(),
1032            serde_json::to_string_pretty(&est).unwrap()
1033        );
1034        let old_est = est.clone();
1035        let roundtripped = est_roundtrip(est);
1036        assert_eq!(&old_est, &roundtripped);
1037        let est = text_roundtrip(&old_est);
1038        assert_eq!(&old_est, &est);
1039
1040        // during the lossy transform to AST, the only difference for this policy is that
1041        // a `when { true }` is added
1042        let expected_json_after_roundtrip = json!(
1043            {
1044                "effect": "permit",
1045                "principal": {
1046                    "op": "==",
1047                    "slot": "?principal",
1048                },
1049                "action": {
1050                    "op": "==",
1051                    "entity": { "type": "Action", "id": "view" },
1052                },
1053                "resource": {
1054                    "op": "in",
1055                    "slot": "?resource",
1056                },
1057                "conditions": [
1058                    {
1059                        "kind": "when",
1060                        "body": {
1061                            "Value": true
1062                        }
1063                    }
1064                ]
1065            }
1066        );
1067        let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
1068        assert_eq!(
1069            roundtripped,
1070            expected_json_after_roundtrip,
1071            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1072            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1073            serde_json::to_string_pretty(&roundtripped).unwrap()
1074        );
1075        let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
1076        assert_eq!(
1077            roundtripped,
1078            expected_json_after_roundtrip,
1079            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1080            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1081            serde_json::to_string_pretty(&roundtripped).unwrap()
1082        );
1083    }
1084
1085    #[test]
1086    fn abac_policy() {
1087        let policy = r#"
1088            permit(
1089                principal == User::"12UA45",
1090                action == Action::"view",
1091                resource in Folder::"abc"
1092            ) when {
1093                context.tls_version == "1.3"
1094            };
1095        "#;
1096        let cst = parser::text_to_cst::parse_policy(policy)
1097            .unwrap()
1098            .node
1099            .unwrap();
1100        let est: Policy = cst.try_into().unwrap();
1101        let expected_json = json!(
1102            {
1103                "effect": "permit",
1104                "principal": {
1105                    "op": "==",
1106                    "entity": { "type": "User", "id": "12UA45" },
1107                },
1108                "action": {
1109                    "op": "==",
1110                    "entity": { "type": "Action", "id": "view" },
1111                },
1112                "resource": {
1113                    "op": "in",
1114                    "entity": { "type": "Folder", "id": "abc" },
1115                },
1116                "conditions": [
1117                    {
1118                        "kind": "when",
1119                        "body": {
1120                            "==": {
1121                                "left": {
1122                                    ".": {
1123                                        "left": { "Var": "context" },
1124                                        "attr": "tls_version",
1125                                    },
1126                                },
1127                                "right": {
1128                                    "Value": "1.3"
1129                                }
1130                            }
1131                        }
1132                    }
1133                ]
1134            }
1135        );
1136        assert_eq!(
1137            serde_json::to_value(&est).unwrap(),
1138            expected_json,
1139            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1140            serde_json::to_string_pretty(&expected_json).unwrap(),
1141            serde_json::to_string_pretty(&est).unwrap()
1142        );
1143        let old_est = est.clone();
1144        let roundtripped = est_roundtrip(est);
1145        assert_eq!(&old_est, &roundtripped);
1146        let est = text_roundtrip(&old_est);
1147        assert_eq!(&old_est, &est);
1148
1149        assert_eq!(ast_roundtrip(est.clone()), est);
1150        assert_eq!(circular_roundtrip(est.clone()), est);
1151    }
1152
1153    #[test]
1154    fn action_list() {
1155        let policy = r#"
1156            permit(
1157                principal == User::"12UA45",
1158                action in [Action::"read", Action::"write"],
1159                resource
1160            );
1161        "#;
1162        let cst = parser::text_to_cst::parse_policy(policy)
1163            .unwrap()
1164            .node
1165            .unwrap();
1166        let est: Policy = cst.try_into().unwrap();
1167        let expected_json = json!(
1168            {
1169                "effect": "permit",
1170                "principal": {
1171                    "op": "==",
1172                    "entity": { "type": "User", "id": "12UA45" },
1173                },
1174                "action": {
1175                    "op": "in",
1176                    "entities": [
1177                        { "type": "Action", "id": "read" },
1178                        { "type": "Action", "id": "write" },
1179                    ]
1180                },
1181                "resource": {
1182                    "op": "All",
1183                },
1184                "conditions": []
1185            }
1186        );
1187        assert_eq!(
1188            serde_json::to_value(&est).unwrap(),
1189            expected_json,
1190            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1191            serde_json::to_string_pretty(&expected_json).unwrap(),
1192            serde_json::to_string_pretty(&est).unwrap()
1193        );
1194        let old_est = est.clone();
1195        let roundtripped = est_roundtrip(est);
1196        assert_eq!(&old_est, &roundtripped);
1197        let est = text_roundtrip(&old_est);
1198        assert_eq!(&old_est, &est);
1199
1200        // during the lossy transform to AST, the only difference for this policy is that
1201        // a `when { true }` is added
1202        let expected_json_after_roundtrip = json!(
1203            {
1204                "effect": "permit",
1205                "principal": {
1206                    "op": "==",
1207                    "entity": { "type": "User", "id": "12UA45" },
1208                },
1209                "action": {
1210                    "op": "in",
1211                    "entities": [
1212                        { "type": "Action", "id": "read" },
1213                        { "type": "Action", "id": "write" },
1214                    ]
1215                },
1216                "resource": {
1217                    "op": "All",
1218                },
1219                "conditions": [
1220                    {
1221                        "kind": "when",
1222                        "body": {
1223                            "Value": true
1224                        }
1225                    }
1226                ]
1227            }
1228        );
1229        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1230        assert_eq!(
1231            roundtripped,
1232            expected_json_after_roundtrip,
1233            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1234            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1235            serde_json::to_string_pretty(&roundtripped).unwrap()
1236        );
1237        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1238        assert_eq!(
1239            roundtripped,
1240            expected_json_after_roundtrip,
1241            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1242            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1243            serde_json::to_string_pretty(&roundtripped).unwrap()
1244        );
1245    }
1246
1247    #[test]
1248    fn num_literals() {
1249        let policy = r#"
1250            permit(principal, action, resource)
1251            when { 1 == 2 };
1252        "#;
1253        let cst = parser::text_to_cst::parse_policy(policy)
1254            .unwrap()
1255            .node
1256            .unwrap();
1257        let est: Policy = cst.try_into().unwrap();
1258        let expected_json = json!(
1259            {
1260                "effect": "permit",
1261                "principal": {
1262                    "op": "All",
1263                },
1264                "action": {
1265                    "op": "All",
1266                },
1267                "resource": {
1268                    "op": "All",
1269                },
1270                "conditions": [
1271                    {
1272                        "kind": "when",
1273                        "body": {
1274                            "==": {
1275                                "left": {
1276                                    "Value": 1
1277                                },
1278                                "right": {
1279                                    "Value": 2
1280                                }
1281                            }
1282                        }
1283                    }
1284                ]
1285            }
1286        );
1287        assert_eq!(
1288            serde_json::to_value(&est).unwrap(),
1289            expected_json,
1290            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1291            serde_json::to_string_pretty(&expected_json).unwrap(),
1292            serde_json::to_string_pretty(&est).unwrap()
1293        );
1294        let old_est = est.clone();
1295        let roundtripped = est_roundtrip(est);
1296        assert_eq!(&old_est, &roundtripped);
1297        let est = text_roundtrip(&old_est);
1298        assert_eq!(&old_est, &est);
1299
1300        assert_eq!(ast_roundtrip(est.clone()), est);
1301        assert_eq!(circular_roundtrip(est.clone()), est);
1302    }
1303
1304    #[test]
1305    fn entity_literals() {
1306        let policy = r#"
1307            permit(principal, action, resource)
1308            when { User::"alice" == Namespace::Type::"foo" };
1309        "#;
1310        let cst = parser::text_to_cst::parse_policy(policy)
1311            .unwrap()
1312            .node
1313            .unwrap();
1314        let est: Policy = cst.try_into().unwrap();
1315        let expected_json = json!(
1316            {
1317                "effect": "permit",
1318                "principal": {
1319                    "op": "All",
1320                },
1321                "action": {
1322                    "op": "All",
1323                },
1324                "resource": {
1325                    "op": "All",
1326                },
1327                "conditions": [
1328                    {
1329                        "kind": "when",
1330                        "body": {
1331                            "==": {
1332                                "left": {
1333                                    "Value": {
1334                                        "__entity": {
1335                                            "type": "User",
1336                                            "id": "alice"
1337                                        }
1338                                    }
1339                                },
1340                                "right": {
1341                                    "Value": {
1342                                        "__entity": {
1343                                            "type": "Namespace::Type",
1344                                            "id": "foo"
1345                                        }
1346                                    }
1347                                }
1348                            }
1349                        }
1350                    }
1351                ]
1352            }
1353        );
1354        assert_eq!(
1355            serde_json::to_value(&est).unwrap(),
1356            expected_json,
1357            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1358            serde_json::to_string_pretty(&expected_json).unwrap(),
1359            serde_json::to_string_pretty(&est).unwrap()
1360        );
1361        let old_est = est.clone();
1362        let roundtripped = est_roundtrip(est);
1363        assert_eq!(&old_est, &roundtripped);
1364        let est = text_roundtrip(&old_est);
1365        assert_eq!(&old_est, &est);
1366
1367        assert_eq!(ast_roundtrip(est.clone()), est);
1368        assert_eq!(circular_roundtrip(est.clone()), est);
1369    }
1370
1371    #[test]
1372    fn bool_literals() {
1373        let policy = r#"
1374            permit(principal, action, resource)
1375            when { false == true };
1376        "#;
1377        let cst = parser::text_to_cst::parse_policy(policy)
1378            .unwrap()
1379            .node
1380            .unwrap();
1381        let est: Policy = cst.try_into().unwrap();
1382        let expected_json = json!(
1383            {
1384                "effect": "permit",
1385                "principal": {
1386                    "op": "All",
1387                },
1388                "action": {
1389                    "op": "All",
1390                },
1391                "resource": {
1392                    "op": "All",
1393                },
1394                "conditions": [
1395                    {
1396                        "kind": "when",
1397                        "body": {
1398                            "==": {
1399                                "left": {
1400                                    "Value": false
1401                                },
1402                                "right": {
1403                                    "Value": true
1404                                }
1405                            }
1406                        }
1407                    }
1408                ]
1409            }
1410        );
1411        assert_eq!(
1412            serde_json::to_value(&est).unwrap(),
1413            expected_json,
1414            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1415            serde_json::to_string_pretty(&expected_json).unwrap(),
1416            serde_json::to_string_pretty(&est).unwrap()
1417        );
1418        let old_est = est.clone();
1419        let roundtripped = est_roundtrip(est);
1420        assert_eq!(&old_est, &roundtripped);
1421        let est = text_roundtrip(&old_est);
1422        assert_eq!(&old_est, &est);
1423
1424        assert_eq!(ast_roundtrip(est.clone()), est);
1425        assert_eq!(circular_roundtrip(est.clone()), est);
1426    }
1427
1428    #[test]
1429    fn string_literals() {
1430        let policy = r#"
1431            permit(principal, action, resource)
1432            when { "spam" == "eggs" };
1433        "#;
1434        let cst = parser::text_to_cst::parse_policy(policy)
1435            .unwrap()
1436            .node
1437            .unwrap();
1438        let est: Policy = cst.try_into().unwrap();
1439        let expected_json = json!(
1440            {
1441                "effect": "permit",
1442                "principal": {
1443                    "op": "All",
1444                },
1445                "action": {
1446                    "op": "All",
1447                },
1448                "resource": {
1449                    "op": "All",
1450                },
1451                "conditions": [
1452                    {
1453                        "kind": "when",
1454                        "body": {
1455                            "==": {
1456                                "left": {
1457                                    "Value": "spam"
1458                                },
1459                                "right": {
1460                                    "Value": "eggs"
1461                                }
1462                            }
1463                        }
1464                    }
1465                ]
1466            }
1467        );
1468        assert_eq!(
1469            serde_json::to_value(&est).unwrap(),
1470            expected_json,
1471            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1472            serde_json::to_string_pretty(&expected_json).unwrap(),
1473            serde_json::to_string_pretty(&est).unwrap()
1474        );
1475        let old_est = est.clone();
1476        let roundtripped = est_roundtrip(est);
1477        assert_eq!(&old_est, &roundtripped);
1478        let est = text_roundtrip(&old_est);
1479        assert_eq!(&old_est, &est);
1480
1481        assert_eq!(ast_roundtrip(est.clone()), est);
1482        assert_eq!(circular_roundtrip(est.clone()), est);
1483    }
1484
1485    #[test]
1486    fn set_literals() {
1487        let policy = r#"
1488            permit(principal, action, resource)
1489            when { [1, 2, "foo"] == [4, 5, "spam"] };
1490        "#;
1491        let cst = parser::text_to_cst::parse_policy(policy)
1492            .unwrap()
1493            .node
1494            .unwrap();
1495        let est: Policy = cst.try_into().unwrap();
1496        let expected_json = json!(
1497            {
1498                "effect": "permit",
1499                "principal": {
1500                    "op": "All",
1501                },
1502                "action": {
1503                    "op": "All",
1504                },
1505                "resource": {
1506                    "op": "All",
1507                },
1508                "conditions": [
1509                    {
1510                        "kind": "when",
1511                        "body": {
1512                            "==": {
1513                                "left": {
1514                                    "Set": [
1515                                        { "Value": 1 },
1516                                        { "Value": 2 },
1517                                        { "Value": "foo" },
1518                                    ]
1519                                },
1520                                "right": {
1521                                    "Set": [
1522                                        { "Value": 4 },
1523                                        { "Value": 5 },
1524                                        { "Value": "spam" },
1525                                    ]
1526                                }
1527                            }
1528                        }
1529                    }
1530                ]
1531            }
1532        );
1533        assert_eq!(
1534            serde_json::to_value(&est).unwrap(),
1535            expected_json,
1536            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1537            serde_json::to_string_pretty(&expected_json).unwrap(),
1538            serde_json::to_string_pretty(&est).unwrap()
1539        );
1540        let old_est = est.clone();
1541        let roundtripped = est_roundtrip(est);
1542        assert_eq!(&old_est, &roundtripped);
1543        let est = text_roundtrip(&old_est);
1544        assert_eq!(&old_est, &est);
1545
1546        assert_eq!(ast_roundtrip(est.clone()), est);
1547        assert_eq!(circular_roundtrip(est.clone()), est);
1548    }
1549
1550    #[test]
1551    fn record_literals() {
1552        let policy = r#"
1553            permit(principal, action, resource)
1554            when { {foo: "spam", bar: false} == {} };
1555        "#;
1556        let cst = parser::text_to_cst::parse_policy(policy)
1557            .unwrap()
1558            .node
1559            .unwrap();
1560        let est: Policy = cst.try_into().unwrap();
1561        let expected_json = json!(
1562            {
1563                "effect": "permit",
1564                "principal": {
1565                    "op": "All",
1566                },
1567                "action": {
1568                    "op": "All",
1569                },
1570                "resource": {
1571                    "op": "All",
1572                },
1573                "conditions": [
1574                    {
1575                        "kind": "when",
1576                        "body": {
1577                            "==": {
1578                                "left": {
1579                                    "Record": {
1580                                        "foo": { "Value": "spam" },
1581                                        "bar": { "Value": false },
1582                                    }
1583                                },
1584                                "right": {
1585                                    "Record": {}
1586                                }
1587                            }
1588                        }
1589                    }
1590                ]
1591            }
1592        );
1593        assert_eq!(
1594            serde_json::to_value(&est).unwrap(),
1595            expected_json,
1596            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1597            serde_json::to_string_pretty(&expected_json).unwrap(),
1598            serde_json::to_string_pretty(&est).unwrap()
1599        );
1600        let old_est = est.clone();
1601        let roundtripped = est_roundtrip(est);
1602        assert_eq!(&old_est, &roundtripped);
1603        let est = text_roundtrip(&old_est);
1604        assert_eq!(&old_est, &est);
1605
1606        assert_eq!(ast_roundtrip(est.clone()), est);
1607        assert_eq!(circular_roundtrip(est.clone()), est);
1608    }
1609
1610    #[test]
1611    fn policy_variables() {
1612        let policy = r#"
1613            permit(principal, action, resource)
1614            when { principal == action && resource == context };
1615        "#;
1616        let cst = parser::text_to_cst::parse_policy(policy)
1617            .unwrap()
1618            .node
1619            .unwrap();
1620        let est: Policy = cst.try_into().unwrap();
1621        let expected_json = json!(
1622            {
1623                "effect": "permit",
1624                "principal": {
1625                    "op": "All",
1626                },
1627                "action": {
1628                    "op": "All",
1629                },
1630                "resource": {
1631                    "op": "All",
1632                },
1633                "conditions": [
1634                    {
1635                        "kind": "when",
1636                        "body": {
1637                            "&&": {
1638                                "left": {
1639                                    "==": {
1640                                        "left": {
1641                                            "Var": "principal"
1642                                        },
1643                                        "right": {
1644                                            "Var": "action"
1645                                        }
1646                                    }
1647                                },
1648                                "right": {
1649                                    "==": {
1650                                        "left": {
1651                                            "Var": "resource"
1652                                        },
1653                                        "right": {
1654                                            "Var": "context"
1655                                        }
1656                                    }
1657                                }
1658                            }
1659                        }
1660                    }
1661                ]
1662            }
1663        );
1664        assert_eq!(
1665            serde_json::to_value(&est).unwrap(),
1666            expected_json,
1667            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1668            serde_json::to_string_pretty(&expected_json).unwrap(),
1669            serde_json::to_string_pretty(&est).unwrap()
1670        );
1671        let old_est = est.clone();
1672        let roundtripped = est_roundtrip(est);
1673        assert_eq!(&old_est, &roundtripped);
1674        let est = text_roundtrip(&old_est);
1675        assert_eq!(&old_est, &est);
1676
1677        assert_eq!(ast_roundtrip(est.clone()), est);
1678        assert_eq!(circular_roundtrip(est.clone()), est);
1679    }
1680
1681    #[test]
1682    fn not() {
1683        let policy = r#"
1684            permit(principal, action, resource)
1685            when { !context.foo && principal != context.bar };
1686        "#;
1687        let cst = parser::text_to_cst::parse_policy(policy)
1688            .unwrap()
1689            .node
1690            .unwrap();
1691        let est: Policy = cst.try_into().unwrap();
1692        let expected_json = json!(
1693            {
1694                "effect": "permit",
1695                "principal": {
1696                    "op": "All",
1697                },
1698                "action": {
1699                    "op": "All",
1700                },
1701                "resource": {
1702                    "op": "All",
1703                },
1704                "conditions": [
1705                    {
1706                        "kind": "when",
1707                        "body": {
1708                            "&&": {
1709                                "left": {
1710                                    "!": {
1711                                        "arg": {
1712                                            ".": {
1713                                                "left": {
1714                                                    "Var": "context"
1715                                                },
1716                                                "attr": "foo"
1717                                            }
1718                                        }
1719                                    }
1720                                },
1721                                "right": {
1722                                    "!=": {
1723                                        "left": {
1724                                            "Var": "principal"
1725                                        },
1726                                        "right": {
1727                                            ".": {
1728                                                "left": {
1729                                                    "Var": "context"
1730                                                },
1731                                                "attr": "bar"
1732                                            }
1733                                        }
1734                                    }
1735                                }
1736                            }
1737                        }
1738                    }
1739                ]
1740            }
1741        );
1742        assert_eq!(
1743            serde_json::to_value(&est).unwrap(),
1744            expected_json,
1745            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1746            serde_json::to_string_pretty(&expected_json).unwrap(),
1747            serde_json::to_string_pretty(&est).unwrap()
1748        );
1749        let old_est = est.clone();
1750        let roundtripped = est_roundtrip(est);
1751        assert_eq!(&old_est, &roundtripped);
1752        let est = text_roundtrip(&old_est);
1753        assert_eq!(&old_est, &est);
1754
1755        // during the lossy transform to AST, the only difference for this policy is that
1756        // `!=` is expanded to `!(==)`
1757        let expected_json_after_roundtrip = json!(
1758            {
1759                "effect": "permit",
1760                "principal": {
1761                    "op": "All",
1762                },
1763                "action": {
1764                    "op": "All",
1765                },
1766                "resource": {
1767                    "op": "All",
1768                },
1769                "conditions": [
1770                    {
1771                        "kind": "when",
1772                        "body": {
1773                            "&&": {
1774                                "left": {
1775                                    "!": {
1776                                        "arg": {
1777                                            ".": {
1778                                                "left": {
1779                                                    "Var": "context"
1780                                                },
1781                                                "attr": "foo"
1782                                            }
1783                                        }
1784                                    }
1785                                },
1786                                "right": {
1787                                    "!": {
1788                                        "arg": {
1789                                            "==": {
1790                                                "left": {
1791                                                    "Var": "principal"
1792                                                },
1793                                                "right": {
1794                                                    ".": {
1795                                                        "left": {
1796                                                            "Var": "context"
1797                                                        },
1798                                                        "attr": "bar"
1799                                                    }
1800                                                }
1801                                            }
1802                                        }
1803                                    }
1804                                }
1805                            }
1806                        }
1807                    }
1808                ]
1809            }
1810        );
1811        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1812        assert_eq!(
1813            roundtripped,
1814            expected_json_after_roundtrip,
1815            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1816            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1817            serde_json::to_string_pretty(&roundtripped).unwrap()
1818        );
1819        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1820        assert_eq!(
1821            roundtripped,
1822            expected_json_after_roundtrip,
1823            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1824            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1825            serde_json::to_string_pretty(&roundtripped).unwrap()
1826        );
1827    }
1828
1829    #[test]
1830    fn hierarchy_in() {
1831        let policy = r#"
1832            permit(principal, action, resource)
1833            when { resource in principal.department };
1834        "#;
1835        let cst = parser::text_to_cst::parse_policy(policy)
1836            .unwrap()
1837            .node
1838            .unwrap();
1839        let est: Policy = cst.try_into().unwrap();
1840        let expected_json = json!(
1841            {
1842                "effect": "permit",
1843                "principal": {
1844                    "op": "All",
1845                },
1846                "action": {
1847                    "op": "All",
1848                },
1849                "resource": {
1850                    "op": "All",
1851                },
1852                "conditions": [
1853                    {
1854                        "kind": "when",
1855                        "body": {
1856                            "in": {
1857                                "left": {
1858                                    "Var": "resource"
1859                                },
1860                                "right": {
1861                                    ".": {
1862                                        "left": {
1863                                            "Var": "principal"
1864                                        },
1865                                        "attr": "department"
1866                                    }
1867                                }
1868                            }
1869                        }
1870                    }
1871                ]
1872            }
1873        );
1874        assert_eq!(
1875            serde_json::to_value(&est).unwrap(),
1876            expected_json,
1877            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1878            serde_json::to_string_pretty(&expected_json).unwrap(),
1879            serde_json::to_string_pretty(&est).unwrap()
1880        );
1881        let old_est = est.clone();
1882        let roundtripped = est_roundtrip(est);
1883        assert_eq!(&old_est, &roundtripped);
1884        let est = text_roundtrip(&old_est);
1885        assert_eq!(&old_est, &est);
1886
1887        assert_eq!(ast_roundtrip(est.clone()), est);
1888        assert_eq!(circular_roundtrip(est.clone()), est);
1889    }
1890
1891    #[test]
1892    fn nested_records() {
1893        let policy = r#"
1894            permit(principal, action, resource)
1895            when { context.something1.something2.something3 };
1896        "#;
1897        let cst = parser::text_to_cst::parse_policy(policy)
1898            .unwrap()
1899            .node
1900            .unwrap();
1901        let est: Policy = cst.try_into().unwrap();
1902        let expected_json = json!(
1903            {
1904                "effect": "permit",
1905                "principal": {
1906                    "op": "All",
1907                },
1908                "action": {
1909                    "op": "All",
1910                },
1911                "resource": {
1912                    "op": "All",
1913                },
1914                "conditions": [
1915                    {
1916                        "kind": "when",
1917                        "body": {
1918                            ".": {
1919                                "left": {
1920                                    ".": {
1921                                        "left": {
1922                                            ".": {
1923                                                "left": {
1924                                                    "Var": "context"
1925                                                },
1926                                                "attr": "something1"
1927                                            }
1928                                        },
1929                                        "attr": "something2"
1930                                    }
1931                                },
1932                                "attr": "something3"
1933                            }
1934                        }
1935                    }
1936                ]
1937            }
1938        );
1939        assert_eq!(
1940            serde_json::to_value(&est).unwrap(),
1941            expected_json,
1942            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1943            serde_json::to_string_pretty(&expected_json).unwrap(),
1944            serde_json::to_string_pretty(&est).unwrap()
1945        );
1946        let old_est = est.clone();
1947        let roundtripped = est_roundtrip(est);
1948        assert_eq!(&old_est, &roundtripped);
1949        let est = text_roundtrip(&old_est);
1950        assert_eq!(&old_est, &est);
1951
1952        assert_eq!(ast_roundtrip(est.clone()), est);
1953        assert_eq!(circular_roundtrip(est.clone()), est);
1954    }
1955
1956    #[test]
1957    fn neg_less_and_greater() {
1958        let policy = r#"
1959            permit(principal, action, resource)
1960            when { -3 < 2 && 4 > -(23 - 1) || 0 <= 0 && 7 >= 1};
1961        "#;
1962        let cst = parser::text_to_cst::parse_policy(policy)
1963            .unwrap()
1964            .node
1965            .unwrap();
1966        let est: Policy = cst.try_into().unwrap();
1967        let expected_json = json!(
1968            {
1969                "effect": "permit",
1970                "principal": {
1971                    "op": "All",
1972                },
1973                "action": {
1974                    "op": "All",
1975                },
1976                "resource": {
1977                    "op": "All",
1978                },
1979                "conditions": [
1980                    {
1981                        "kind": "when",
1982                        "body": {
1983                            "||": {
1984                                "left": {
1985                                    "&&": {
1986                                        "left": {
1987                                            "<": {
1988                                                "left": {
1989                                                    "Value": -3
1990                                                },
1991                                                "right": {
1992                                                    "Value": 2
1993                                                }
1994                                            }
1995                                        },
1996                                        "right": {
1997                                            ">": {
1998                                                "left": {
1999                                                    "Value": 4
2000                                                },
2001                                                "right": {
2002                                                    "neg": {
2003                                                        "arg": {
2004                                                            "-": {
2005                                                                "left": {
2006                                                                    "Value": 23
2007                                                                },
2008                                                                "right": {
2009                                                                    "Value": 1
2010                                                                }
2011                                                            }
2012                                                        }
2013                                                    }
2014                                                }
2015                                            }
2016                                        }
2017                                    }
2018                                },
2019                                "right": {
2020                                    "&&": {
2021                                        "left": {
2022                                            "<=": {
2023                                                "left": {
2024                                                    "Value": 0
2025                                                },
2026                                                "right": {
2027                                                    "Value": 0
2028                                                }
2029                                            }
2030                                        },
2031                                        "right": {
2032                                            ">=": {
2033                                                "left": {
2034                                                    "Value": 7
2035                                                },
2036                                                "right": {
2037                                                    "Value": 1
2038                                                }
2039                                            }
2040                                        }
2041                                    }
2042                                }
2043                            }
2044                        }
2045                    }
2046                ]
2047            }
2048        );
2049        assert_eq!(
2050            serde_json::to_value(&est).unwrap(),
2051            expected_json,
2052            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2053            serde_json::to_string_pretty(&expected_json).unwrap(),
2054            serde_json::to_string_pretty(&est).unwrap()
2055        );
2056        let old_est = est.clone();
2057        let roundtripped = est_roundtrip(est);
2058        assert_eq!(&old_est, &roundtripped);
2059        let est = text_roundtrip(&old_est);
2060        assert_eq!(&old_est, &est);
2061
2062        // during the lossy transform to AST, the `>` and `>=` ops are desugared to `<` and
2063        // `<=` ops with the operands flipped
2064        let expected_json_after_roundtrip = json!(
2065            {
2066                "effect": "permit",
2067                "principal": {
2068                    "op": "All",
2069                },
2070                "action": {
2071                    "op": "All",
2072                },
2073                "resource": {
2074                    "op": "All",
2075                },
2076                "conditions": [
2077                    {
2078                        "kind": "when",
2079                        "body": {
2080                            "||": {
2081                                "left": {
2082                                    "&&": {
2083                                        "left": {
2084                                            "<": {
2085                                                "left": {
2086                                                    "Value": -3
2087                                                },
2088                                                "right": {
2089                                                    "Value": 2
2090                                                }
2091                                            }
2092                                        },
2093                                        "right": {
2094                                            "!": {
2095                                                "arg":{
2096                                                    "<=": {
2097                                                        "left": {
2098                                                            "Value": 4
2099                                                        },
2100                                                        "right": {
2101                                                            "neg": {
2102                                                                "arg": {
2103                                                                    "-": {
2104                                                                        "left": {
2105                                                                            "Value": 23
2106                                                                        },
2107                                                                        "right": {
2108                                                                            "Value": 1
2109                                                                        }
2110                                                                    }
2111                                                                }
2112                                                            }
2113                                                        }
2114                                                    }
2115                                                }
2116                                            }
2117                                        }
2118                                    }
2119                                },
2120                                "right": {
2121                                    "&&": {
2122                                        "left": {
2123                                            "<=": {
2124                                                "left": {
2125                                                    "Value": 0
2126                                                },
2127                                                "right": {
2128                                                    "Value": 0
2129                                                }
2130                                            }
2131                                        },
2132                                        "right": {
2133                                            "!": {
2134                                                "arg": {
2135                                                    "<": {
2136                                                        "left": {
2137                                                            "Value": 7
2138                                                        },
2139                                                        "right": {
2140                                                            "Value": 1
2141                                                        }
2142                                                    }
2143                                                }
2144                                            }
2145                                        }
2146                                    }
2147                                }
2148                            }
2149                        }
2150                    }
2151                ]
2152            }
2153        );
2154        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
2155        assert_eq!(
2156            roundtripped,
2157            expected_json_after_roundtrip,
2158            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2159            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2160            serde_json::to_string_pretty(&roundtripped).unwrap()
2161        );
2162        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
2163        assert_eq!(
2164            roundtripped,
2165            expected_json_after_roundtrip,
2166            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2167            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2168            serde_json::to_string_pretty(&roundtripped).unwrap()
2169        );
2170    }
2171
2172    #[test]
2173    fn add_sub_and_mul() {
2174        let policy = r#"
2175            permit(principal, action, resource)
2176            when { 2 + 3 - principal.numFoos * (-10) == 7 };
2177        "#;
2178        let cst = parser::text_to_cst::parse_policy(policy)
2179            .unwrap()
2180            .node
2181            .unwrap();
2182        let est: Policy = cst.try_into().unwrap();
2183        let expected_json = json!(
2184            {
2185                "effect": "permit",
2186                "principal": {
2187                    "op": "All",
2188                },
2189                "action": {
2190                    "op": "All",
2191                },
2192                "resource": {
2193                    "op": "All",
2194                },
2195                "conditions": [
2196                    {
2197                        "kind": "when",
2198                        "body": {
2199                            "==": {
2200                                "left": {
2201                                    "-": {
2202                                        "left": {
2203                                            "+": {
2204                                                "left": {
2205                                                    "Value": 2
2206                                                },
2207                                                "right": {
2208                                                    "Value": 3
2209                                                }
2210                                            }
2211                                        },
2212                                        "right": {
2213                                            "*": {
2214                                                "left": {
2215                                                    ".": {
2216                                                        "left": {
2217                                                            "Var": "principal"
2218                                                        },
2219                                                        "attr": "numFoos"
2220                                                    }
2221                                                },
2222                                                "right": {
2223                                                    "Value": -10
2224                                                }
2225                                            }
2226                                        }
2227                                    }
2228                                },
2229                                "right": {
2230                                    "Value": 7
2231                                }
2232                            }
2233                        }
2234                    }
2235                ]
2236            }
2237        );
2238        assert_eq!(
2239            serde_json::to_value(&est).unwrap(),
2240            expected_json,
2241            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2242            serde_json::to_string_pretty(&expected_json).unwrap(),
2243            serde_json::to_string_pretty(&est).unwrap()
2244        );
2245        let old_est = est.clone();
2246        let roundtripped = est_roundtrip(est);
2247        assert_eq!(&old_est, &roundtripped);
2248        let est = text_roundtrip(&old_est);
2249        assert_eq!(&old_est, &est);
2250
2251        assert_eq!(ast_roundtrip(est.clone()), est);
2252        assert_eq!(circular_roundtrip(est.clone()), est);
2253    }
2254
2255    #[test]
2256    fn contains_all_any() {
2257        let policy = r#"
2258            permit(principal, action, resource)
2259            when {
2260                principal.owners.contains("foo")
2261                && principal.owners.containsAny([1, Linux::Group::"sudoers"])
2262                && [2+3, "spam"].containsAll(resource.foos)
2263                && context.violations.isEmpty()
2264            };
2265        "#;
2266        let cst = parser::text_to_cst::parse_policy(policy)
2267            .unwrap()
2268            .node
2269            .unwrap();
2270        let est: Policy = cst.try_into().unwrap();
2271        let expected_json = json!(
2272            {
2273                "effect": "permit",
2274                "principal": {
2275                    "op": "All",
2276                },
2277                "action": {
2278                    "op": "All",
2279                },
2280                "resource": {
2281                    "op": "All",
2282                },
2283                "conditions": [
2284                    {
2285                        "kind": "when",
2286                        "body": {
2287                            "&&": {
2288                                "left": {
2289                                    "&&": {
2290                                        "left": {
2291                                            "&&": {
2292                                                "left": {
2293                                                    "contains": {
2294                                                        "left": {
2295                                                            ".": {
2296                                                                "left": {
2297                                                                    "Var": "principal"
2298                                                                },
2299                                                                "attr": "owners"
2300                                                            }
2301                                                        },
2302                                                        "right": {
2303                                                            "Value": "foo"
2304                                                        }
2305                                                    }
2306                                                },
2307                                                "right": {
2308                                                    "containsAny": {
2309                                                        "left": {
2310                                                            ".": {
2311                                                                "left": {
2312                                                                    "Var": "principal"
2313                                                                },
2314                                                                "attr": "owners"
2315                                                            }
2316                                                        },
2317                                                        "right": {
2318                                                            "Set": [
2319                                                                { "Value": 1 },
2320                                                                { "Value": {
2321                                                                    "__entity": {
2322                                                                        "type": "Linux::Group",
2323                                                                        "id": "sudoers"
2324                                                                    }
2325                                                                } }
2326                                                            ]
2327                                                        }
2328                                                    }
2329                                                }
2330                                            }
2331                                        },
2332                                        "right": {
2333                                            "containsAll": {
2334                                                "left": {
2335                                                    "Set": [
2336                                                        { "+": {
2337                                                            "left": {
2338                                                                "Value": 2
2339                                                            },
2340                                                            "right": {
2341                                                                "Value": 3
2342                                                            }
2343                                                        } },
2344                                                        { "Value": "spam" },
2345                                                    ]
2346                                                },
2347                                                "right": {
2348                                                    ".": {
2349                                                        "left": {
2350                                                            "Var": "resource"
2351                                                        },
2352                                                        "attr": "foos"
2353                                                    }
2354                                                }
2355                                            }
2356                                        }
2357                                    }
2358                                },
2359                                "right": {
2360                                    "isEmpty": {
2361                                        "arg": {
2362                                            ".": {
2363                                                "left": {
2364                                                    "Var": "context"
2365                                                },
2366                                                "attr": "violations"
2367                                            }
2368                                        }
2369                                    }
2370                                }
2371                            }
2372                        }
2373                    }
2374                ]
2375            }
2376        );
2377        assert_eq!(
2378            serde_json::to_value(&est).unwrap(),
2379            expected_json,
2380            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2381            serde_json::to_string_pretty(&expected_json).unwrap(),
2382            serde_json::to_string_pretty(&est).unwrap()
2383        );
2384        let old_est = est.clone();
2385        let roundtripped = est_roundtrip(est);
2386        assert_eq!(&old_est, &roundtripped);
2387        let est = text_roundtrip(&old_est);
2388        assert_eq!(&old_est, &est);
2389
2390        assert_eq!(ast_roundtrip(est.clone()), est);
2391        assert_eq!(circular_roundtrip(est.clone()), est);
2392    }
2393
2394    #[test]
2395    fn entity_tags() {
2396        let policy = r#"
2397            permit(principal, action, resource)
2398            when {
2399                resource.hasTag("writeable")
2400                && resource.getTag("writeable").contains(principal.group)
2401                && principal.hasTag(context.foo)
2402                && principal.getTag(context.foo) == 72
2403            };
2404        "#;
2405        let cst = parser::text_to_cst::parse_policy(policy)
2406            .unwrap()
2407            .node
2408            .unwrap();
2409        let est: Policy = cst.try_into().unwrap();
2410        let expected_json = json!(
2411            {
2412                "effect": "permit",
2413                "principal": {
2414                    "op": "All",
2415                },
2416                "action": {
2417                    "op": "All",
2418                },
2419                "resource": {
2420                    "op": "All",
2421                },
2422                "conditions": [
2423                    {
2424                        "kind": "when",
2425                        "body": {
2426                            "&&": {
2427                                "left": {
2428                                    "&&": {
2429                                        "left": {
2430                                            "&&": {
2431                                                "left": {
2432                                                    "hasTag": {
2433                                                        "left": {
2434                                                            "Var": "resource"
2435                                                        },
2436                                                        "right": {
2437                                                            "Value": "writeable"
2438                                                        }
2439                                                    }
2440                                                },
2441                                                "right": {
2442                                                    "contains": {
2443                                                        "left": {
2444                                                            "getTag": {
2445                                                                "left": {
2446                                                                    "Var": "resource"
2447                                                                },
2448                                                                "right": {
2449                                                                    "Value": "writeable"
2450                                                                }
2451                                                            }
2452                                                        },
2453                                                        "right": {
2454                                                            ".": {
2455                                                                "left": {
2456                                                                    "Var": "principal"
2457                                                                },
2458                                                                "attr": "group"
2459                                                            }
2460                                                        }
2461                                                    }
2462                                                }
2463                                            }
2464                                        },
2465                                        "right": {
2466                                            "hasTag": {
2467                                                "left": {
2468                                                    "Var": "principal"
2469                                                },
2470                                                "right": {
2471                                                    ".": {
2472                                                        "left": {
2473                                                            "Var": "context",
2474                                                        },
2475                                                        "attr": "foo"
2476                                                    }
2477                                                }
2478                                            }
2479                                        },
2480                                    }
2481                                },
2482                                "right": {
2483                                    "==": {
2484                                        "left": {
2485                                            "getTag": {
2486                                                "left": {
2487                                                    "Var": "principal"
2488                                                },
2489                                                "right": {
2490                                                    ".": {
2491                                                        "left": {
2492                                                            "Var": "context",
2493                                                        },
2494                                                        "attr": "foo"
2495                                                    }
2496                                                }
2497                                            }
2498                                        },
2499                                        "right": {
2500                                            "Value": 72
2501                                        }
2502                                    }
2503                                }
2504                            }
2505                        }
2506                    }
2507                ]
2508            }
2509        );
2510        assert_eq!(
2511            serde_json::to_value(&est).unwrap(),
2512            expected_json,
2513            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2514            serde_json::to_string_pretty(&expected_json).unwrap(),
2515            serde_json::to_string_pretty(&est).unwrap()
2516        );
2517        let old_est = est.clone();
2518        let roundtripped = est_roundtrip(est);
2519        assert_eq!(&old_est, &roundtripped);
2520        let est = text_roundtrip(&old_est);
2521        assert_eq!(&old_est, &est);
2522
2523        assert_eq!(ast_roundtrip(est.clone()), est);
2524        assert_eq!(circular_roundtrip(est.clone()), est);
2525    }
2526
2527    #[test]
2528    fn like_special_patterns() {
2529        let policy = r#"
2530        permit(principal, action, resource)
2531        when {
2532
2533            "" like "ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"
2534        };
2535    "#;
2536        let cst = parser::text_to_cst::parse_policy(policy)
2537            .unwrap()
2538            .node
2539            .unwrap();
2540        let est: Policy = cst.try_into().unwrap();
2541        let expected_json = json!(
2542        {
2543            "effect": "permit",
2544            "principal": {
2545              "op": "All"
2546            },
2547            "action": {
2548              "op": "All"
2549            },
2550            "resource": {
2551              "op": "All"
2552            },
2553            "conditions": [
2554              {
2555                "kind": "when",
2556                "body": {
2557                  "like": {
2558                    "left": {
2559                      "Value": ""
2560                    },
2561                    "pattern": [
2562                      {
2563                        "Literal": "e"
2564                      },
2565                      {
2566                        "Literal": "̶"
2567                      },
2568                      {
2569                        "Literal": "͑"
2570                      },
2571                      {
2572                        "Literal": "͝"
2573                      },
2574                      {
2575                        "Literal": "̰"
2576                      },
2577                      {
2578                        "Literal": "x"
2579                      },
2580                      {
2581                        "Literal": "̶"
2582                      },
2583                      {
2584                        "Literal": "͛"
2585                      },
2586                      {
2587                        "Literal": "͔"
2588                      },
2589                      {
2590                        "Literal": "a"
2591                      },
2592                      {
2593                        "Literal": "̵"
2594                      },
2595                      {
2596                        "Literal": "͛"
2597                      },
2598                      {
2599                        "Literal": "̰"
2600                      },
2601                      {
2602                        "Literal": "̯"
2603                      },
2604                      {
2605                        "Literal": "m"
2606                      },
2607                      {
2608                        "Literal": "̴"
2609                      },
2610                      {
2611                        "Literal": "̋"
2612                      },
2613                      {
2614                        "Literal": "́"
2615                      },
2616                      {
2617                        "Literal": "͉"
2618                      },
2619                      {
2620                        "Literal": "p"
2621                      },
2622                      {
2623                        "Literal": "̷"
2624                      },
2625                      {
2626                        "Literal": "͂"
2627                      },
2628                      {
2629                        "Literal": "̠"
2630                      },
2631                      {
2632                        "Literal": "l"
2633                      },
2634                      {
2635                        "Literal": "̵"
2636                      },
2637                      {
2638                        "Literal": "̍"
2639                      },
2640                      {
2641                        "Literal": "̔"
2642                      },
2643                      {
2644                        "Literal": "͇"
2645                      },
2646                      {
2647                        "Literal": "e"
2648                      },
2649                      {
2650                        "Literal": "̶"
2651                      },
2652                      {
2653                        "Literal": "͝"
2654                      },
2655                      {
2656                        "Literal": "̧"
2657                      },
2658                      {
2659                        "Literal": "̣"
2660                      }
2661                    ]
2662                  }
2663                }
2664              }
2665            ]
2666          });
2667        assert_eq!(
2668            serde_json::to_value(&est).unwrap(),
2669            expected_json,
2670            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2671            serde_json::to_string_pretty(&expected_json).unwrap(),
2672            serde_json::to_string_pretty(&est).unwrap()
2673        );
2674        let old_est = est.clone();
2675        let roundtripped = est_roundtrip(est);
2676        assert_eq!(&old_est, &roundtripped);
2677        let est = text_roundtrip(&old_est);
2678        assert_eq!(&old_est, &est);
2679
2680        assert_eq!(ast_roundtrip(est.clone()), est);
2681        assert_eq!(circular_roundtrip(est.clone()), est);
2682
2683        let alternative_json = json!(
2684            {
2685                "effect": "permit",
2686                "principal": {
2687                  "op": "All"
2688                },
2689                "action": {
2690                  "op": "All"
2691                },
2692                "resource": {
2693                  "op": "All"
2694                },
2695                "conditions": [
2696                  {
2697                    "kind": "when",
2698                    "body": {
2699                      "like": {
2700                        "left": {
2701                          "Value": ""
2702                        },
2703                        "pattern": [
2704                          {
2705                            "Literal": "ḛ̶͑͝x̶͔͛a̵̰̯͛m̴͉̋́p̷̠͂l̵͇̍̔ȩ̶̣͝"
2706                          }
2707                        ]
2708                      }
2709                    }
2710                  }
2711                ]
2712              }
2713        );
2714        let est1: Policy = serde_json::from_value(expected_json).unwrap();
2715        let est2: Policy = serde_json::from_value(alternative_json).unwrap();
2716        let ast1 = est1.try_into_ast_policy(None).unwrap();
2717        let ast2 = est2.try_into_ast_policy(None).unwrap();
2718        assert_eq!(ast1, ast2);
2719    }
2720
2721    #[test]
2722    fn has_like_and_if() {
2723        let policy = r#"
2724            permit(principal, action, resource)
2725            when {
2726                if context.foo
2727                then principal has "-78/%$!"
2728                else resource.email like "*@amazon.com"
2729            };
2730        "#;
2731        let cst = parser::text_to_cst::parse_policy(policy)
2732            .unwrap()
2733            .node
2734            .unwrap();
2735        let est: Policy = cst.try_into().unwrap();
2736        let expected_json = json!(
2737            {
2738                "effect": "permit",
2739                "principal": {
2740                    "op": "All",
2741                },
2742                "action": {
2743                    "op": "All",
2744                },
2745                "resource": {
2746                    "op": "All",
2747                },
2748                "conditions": [
2749                    {
2750                        "kind": "when",
2751                        "body": {
2752                            "if-then-else": {
2753                                "if": {
2754                                    ".": {
2755                                        "left": {
2756                                            "Var": "context"
2757                                        },
2758                                        "attr": "foo"
2759                                    }
2760                                },
2761                                "then": {
2762                                    "has": {
2763                                        "left": {
2764                                            "Var": "principal"
2765                                        },
2766                                        "attr": "-78/%$!"
2767                                    }
2768                                },
2769                                "else": {
2770                                    "like": {
2771                                        "left": {
2772                                            ".": {
2773                                                "left": {
2774                                                    "Var": "resource"
2775                                                },
2776                                                "attr": "email"
2777                                            }
2778                                        },
2779                                        "pattern": [
2780                                            "Wildcard",
2781                                            {
2782                                              "Literal": "@"
2783                                            },
2784                                            {
2785                                              "Literal": "a"
2786                                            },
2787                                            {
2788                                              "Literal": "m"
2789                                            },
2790                                            {
2791                                              "Literal": "a"
2792                                            },
2793                                            {
2794                                              "Literal": "z"
2795                                            },
2796                                            {
2797                                              "Literal": "o"
2798                                            },
2799                                            {
2800                                              "Literal": "n"
2801                                            },
2802                                            {
2803                                              "Literal": "."
2804                                            },
2805                                            {
2806                                              "Literal": "c"
2807                                            },
2808                                            {
2809                                              "Literal": "o"
2810                                            },
2811                                            {
2812                                              "Literal": "m"
2813                                            }
2814                                          ]
2815                                    }
2816                                }
2817                            }
2818                        }
2819                    }
2820                ]
2821            }
2822        );
2823        assert_eq!(
2824            serde_json::to_value(&est).unwrap(),
2825            expected_json,
2826            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2827            serde_json::to_string_pretty(&expected_json).unwrap(),
2828            serde_json::to_string_pretty(&est).unwrap()
2829        );
2830        let old_est = est.clone();
2831        let roundtripped = est_roundtrip(est);
2832        assert_eq!(&old_est, &roundtripped);
2833        let est = text_roundtrip(&old_est);
2834        assert_eq!(&old_est, &est);
2835
2836        assert_eq!(ast_roundtrip(est.clone()), est);
2837        assert_eq!(circular_roundtrip(est.clone()), est);
2838    }
2839
2840    #[test]
2841    fn decimal() {
2842        let policy = r#"
2843            permit(principal, action, resource)
2844            when {
2845                context.confidenceScore.greaterThan(decimal("10.0"))
2846            };
2847        "#;
2848        let cst = parser::text_to_cst::parse_policy(policy)
2849            .unwrap()
2850            .node
2851            .unwrap();
2852        let est: Policy = cst.try_into().unwrap();
2853        let expected_json = json!(
2854            {
2855                "effect": "permit",
2856                "principal": {
2857                    "op": "All",
2858                },
2859                "action": {
2860                    "op": "All",
2861                },
2862                "resource": {
2863                    "op": "All",
2864                },
2865                "conditions": [
2866                    {
2867                        "kind": "when",
2868                        "body": {
2869                            "greaterThan": [
2870                                {
2871                                    ".": {
2872                                        "left": {
2873                                            "Var": "context"
2874                                        },
2875                                        "attr": "confidenceScore"
2876                                    }
2877                                },
2878                                {
2879                                    "decimal": [
2880                                        {
2881                                            "Value": "10.0"
2882                                        }
2883                                    ]
2884                                }
2885                            ]
2886                        }
2887                    }
2888                ]
2889            }
2890        );
2891        assert_eq!(
2892            serde_json::to_value(&est).unwrap(),
2893            expected_json,
2894            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2895            serde_json::to_string_pretty(&expected_json).unwrap(),
2896            serde_json::to_string_pretty(&est).unwrap()
2897        );
2898        let old_est = est.clone();
2899        let roundtripped = est_roundtrip(est);
2900        assert_eq!(&old_est, &roundtripped);
2901        let est = text_roundtrip(&old_est);
2902        assert_eq!(&old_est, &est);
2903
2904        assert_eq!(ast_roundtrip(est.clone()), est);
2905        assert_eq!(circular_roundtrip(est.clone()), est);
2906    }
2907
2908    #[test]
2909    fn ip() {
2910        let policy = r#"
2911            permit(principal, action, resource)
2912            when {
2913                context.source_ip.isInRange(ip("222.222.222.0/24"))
2914            };
2915        "#;
2916        let cst = parser::text_to_cst::parse_policy(policy)
2917            .unwrap()
2918            .node
2919            .unwrap();
2920        let est: Policy = cst.try_into().unwrap();
2921        let expected_json = json!(
2922            {
2923                "effect": "permit",
2924                "principal": {
2925                    "op": "All",
2926                },
2927                "action": {
2928                    "op": "All",
2929                },
2930                "resource": {
2931                    "op": "All",
2932                },
2933                "conditions": [
2934                    {
2935                        "kind": "when",
2936                        "body": {
2937                            "isInRange": [
2938                                {
2939                                    ".": {
2940                                        "left": {
2941                                            "Var": "context"
2942                                        },
2943                                        "attr": "source_ip"
2944                                    }
2945                                },
2946                                {
2947                                    "ip": [
2948                                        {
2949                                            "Value": "222.222.222.0/24"
2950                                        }
2951                                    ]
2952                                }
2953                            ]
2954                        }
2955                    }
2956                ]
2957            }
2958        );
2959        assert_eq!(
2960            serde_json::to_value(&est).unwrap(),
2961            expected_json,
2962            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2963            serde_json::to_string_pretty(&expected_json).unwrap(),
2964            serde_json::to_string_pretty(&est).unwrap()
2965        );
2966        let old_est = est.clone();
2967        let roundtripped = est_roundtrip(est);
2968        assert_eq!(&old_est, &roundtripped);
2969        let est = text_roundtrip(&old_est);
2970        assert_eq!(&old_est, &est);
2971
2972        assert_eq!(ast_roundtrip(est.clone()), est);
2973        assert_eq!(circular_roundtrip(est.clone()), est);
2974    }
2975
2976    #[test]
2977    fn negative_numbers() {
2978        let policy = r#"
2979        permit(principal, action, resource)
2980        when { -1 };
2981        "#;
2982        let cst = parser::text_to_cst::parse_policy(policy)
2983            .unwrap()
2984            .node
2985            .unwrap();
2986        let est: Policy = cst.try_into().unwrap();
2987        let expected_json = json!(
2988        {
2989            "effect": "permit",
2990            "principal": {
2991                "op": "All",
2992            },
2993            "action": {
2994                "op": "All",
2995            },
2996            "resource": {
2997                "op": "All",
2998            },
2999            "conditions": [
3000                {
3001                    "kind": "when",
3002                    "body": {
3003                        "Value": -1
3004                    }
3005                }]});
3006        assert_eq!(
3007            serde_json::to_value(&est).unwrap(),
3008            expected_json,
3009            "\nExpected:\n{}\n\nActual:\n{}\n\n",
3010            serde_json::to_string_pretty(&expected_json).unwrap(),
3011            serde_json::to_string_pretty(&est).unwrap()
3012        );
3013        let policy = r#"
3014        permit(principal, action, resource)
3015        when { -(1) };
3016        "#;
3017        let cst = parser::text_to_cst::parse_policy(policy)
3018            .unwrap()
3019            .node
3020            .unwrap();
3021        let est: Policy = cst.try_into().unwrap();
3022        let expected_json = json!(
3023        {
3024            "effect": "permit",
3025            "principal": {
3026                "op": "All",
3027            },
3028            "action": {
3029                "op": "All",
3030            },
3031            "resource": {
3032                "op": "All",
3033            },
3034            "conditions": [
3035                {
3036                    "kind": "when",
3037                    "body": {
3038                      "neg": {
3039                        "arg": {
3040                          "Value": 1
3041                        }
3042                      }
3043                    }
3044                  }]});
3045        assert_eq!(
3046            serde_json::to_value(&est).unwrap(),
3047            expected_json,
3048            "\nExpected:\n{}\n\nActual:\n{}\n\n",
3049            serde_json::to_string_pretty(&expected_json).unwrap(),
3050            serde_json::to_string_pretty(&est).unwrap()
3051        );
3052    }
3053
3054    #[test]
3055    fn string_escapes() {
3056        let est = parse_policy_or_template_to_est(
3057            r#"permit(principal, action, resource) when { "\n" };"#,
3058        )
3059        .unwrap();
3060        let new_est = text_roundtrip(&est);
3061        assert_eq!(est, new_est);
3062    }
3063
3064    #[test]
3065    fn eid_escapes() {
3066        let est = parse_policy_or_template_to_est(
3067            r#"permit(principal, action, resource) when { Foo::"\n" };"#,
3068        )
3069        .unwrap();
3070        let new_est = text_roundtrip(&est);
3071        assert_eq!(est, new_est);
3072    }
3073
3074    #[test]
3075    fn multiple_clauses() {
3076        let policy = r#"
3077            permit(principal, action, resource)
3078            when { context.foo }
3079            unless { context.bar }
3080            when { principal.eggs };
3081        "#;
3082        let cst = parser::text_to_cst::parse_policy(policy)
3083            .unwrap()
3084            .node
3085            .unwrap();
3086        let est: Policy = cst.try_into().unwrap();
3087        let expected_json = json!(
3088            {
3089                "effect": "permit",
3090                "principal": {
3091                    "op": "All",
3092                },
3093                "action": {
3094                    "op": "All",
3095                },
3096                "resource": {
3097                    "op": "All",
3098                },
3099                "conditions": [
3100                    {
3101                        "kind": "when",
3102                        "body": {
3103                            ".": {
3104                                "left": {
3105                                    "Var": "context"
3106                                },
3107                                "attr": "foo"
3108                            }
3109                        }
3110                    },
3111                    {
3112                        "kind": "unless",
3113                        "body": {
3114                            ".": {
3115                                "left": {
3116                                    "Var": "context"
3117                                },
3118                                "attr": "bar"
3119                            }
3120                        }
3121                    },
3122                    {
3123                        "kind": "when",
3124                        "body": {
3125                            ".": {
3126                                "left": {
3127                                    "Var": "principal"
3128                                },
3129                                "attr": "eggs"
3130                            }
3131                        }
3132                    }
3133                ]
3134            }
3135        );
3136        assert_eq!(
3137            serde_json::to_value(&est).unwrap(),
3138            expected_json,
3139            "\nExpected:\n{}\n\nActual:\n{}\n\n",
3140            serde_json::to_string_pretty(&expected_json).unwrap(),
3141            serde_json::to_string_pretty(&est).unwrap()
3142        );
3143        let old_est = est.clone();
3144        let roundtripped = est_roundtrip(est);
3145        assert_eq!(&old_est, &roundtripped);
3146        let est = text_roundtrip(&old_est);
3147        assert_eq!(&old_est, &est);
3148
3149        // during the lossy transform to AST, the multiple clauses on this policy are
3150        // combined into a single `when` clause
3151        let expected_json_after_roundtrip = json!(
3152            {
3153                "effect": "permit",
3154                "principal": {
3155                    "op": "All",
3156                },
3157                "action": {
3158                    "op": "All",
3159                },
3160                "resource": {
3161                    "op": "All",
3162                },
3163                "conditions": [
3164                    {
3165                        "kind": "when",
3166                        "body": {
3167                            "&&": {
3168                                "left": {
3169                                    "&&": {
3170                                        "left": {
3171                                            ".": {
3172                                                "left": {
3173                                                    "Var": "context"
3174                                                },
3175                                                "attr": "foo"
3176                                            }
3177                                        },
3178                                        "right": {
3179                                            "!": {
3180                                                "arg": {
3181                                                    ".": {
3182                                                        "left": {
3183                                                            "Var": "context"
3184                                                        },
3185                                                        "attr": "bar"
3186                                                    }
3187                                                }
3188                                            }
3189                                        }
3190                                    }
3191                                },
3192                                "right": {
3193                                    ".": {
3194                                        "left": {
3195                                            "Var": "principal"
3196                                        },
3197                                        "attr": "eggs"
3198                                    }
3199                                }
3200                            }
3201                        }
3202                    }
3203                ]
3204            }
3205        );
3206        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3207        assert_eq!(
3208            roundtripped,
3209            expected_json_after_roundtrip,
3210            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3211            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3212            serde_json::to_string_pretty(&roundtripped).unwrap()
3213        );
3214        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3215        assert_eq!(
3216            roundtripped,
3217            expected_json_after_roundtrip,
3218            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3219            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3220            serde_json::to_string_pretty(&roundtripped).unwrap()
3221        );
3222    }
3223
3224    #[test]
3225    fn link() {
3226        let template = r#"
3227            permit(
3228                principal == ?principal,
3229                action == Action::"view",
3230                resource in ?resource
3231            ) when {
3232                principal in resource.owners
3233            };
3234        "#;
3235        let cst = parser::text_to_cst::parse_policy(template)
3236            .unwrap()
3237            .node
3238            .unwrap();
3239        let est: Policy = cst.try_into().unwrap();
3240        let err = est
3241            .clone()
3242            .link(&HashMap::from_iter([]))
3243            .expect_err("didn't fill all the slots");
3244        expect_err(
3245            "",
3246            &miette::Report::new(err),
3247            &ExpectedErrorMessageBuilder::error(
3248                "failed to link template: no value provided for `?principal`",
3249            )
3250            .build(),
3251        );
3252        let err = est
3253            .clone()
3254            .link(&HashMap::from_iter([(
3255                ast::SlotId::principal(),
3256                EntityUidJson::new("XYZCorp::User", "12UA45"),
3257            )]))
3258            .expect_err("didn't fill all the slots");
3259        expect_err(
3260            "",
3261            &miette::Report::new(err),
3262            &ExpectedErrorMessageBuilder::error(
3263                "failed to link template: no value provided for `?resource`",
3264            )
3265            .build(),
3266        );
3267        let linked = est
3268            .link(&HashMap::from_iter([
3269                (
3270                    ast::SlotId::principal(),
3271                    EntityUidJson::new("XYZCorp::User", "12UA45"),
3272                ),
3273                (ast::SlotId::resource(), EntityUidJson::new("Folder", "abc")),
3274            ]))
3275            .expect("did fill all the slots");
3276        let expected_json = json!(
3277            {
3278                "effect": "permit",
3279                "principal": {
3280                    "op": "==",
3281                    "entity": { "type": "XYZCorp::User", "id": "12UA45" },
3282                },
3283                "action": {
3284                    "op": "==",
3285                    "entity": { "type": "Action", "id": "view" },
3286                },
3287                "resource": {
3288                    "op": "in",
3289                    "entity": { "type": "Folder", "id": "abc" },
3290                },
3291                "conditions": [
3292                    {
3293                        "kind": "when",
3294                        "body": {
3295                            "in": {
3296                                "left": {
3297                                    "Var": "principal"
3298                                },
3299                                "right": {
3300                                    ".": {
3301                                        "left": {
3302                                            "Var": "resource"
3303                                        },
3304                                        "attr": "owners"
3305                                    }
3306                                }
3307                            }
3308                        }
3309                    }
3310                ],
3311            }
3312        );
3313        let linked_json = serde_json::to_value(linked).unwrap();
3314        assert_eq!(
3315            linked_json,
3316            expected_json,
3317            "\nExpected:\n{}\n\nActual:\n{}\n\n",
3318            serde_json::to_string_pretty(&expected_json).unwrap(),
3319            serde_json::to_string_pretty(&linked_json).unwrap(),
3320        );
3321    }
3322
3323    #[test]
3324    fn eid_with_nulls() {
3325        let policy = r#"
3326            permit(
3327                principal == a::"\0\0\0J",
3328                action == Action::"view",
3329                resource
3330            );
3331        "#;
3332        let cst = parser::text_to_cst::parse_policy(policy)
3333            .unwrap()
3334            .node
3335            .unwrap();
3336        let est: Policy = cst.try_into().unwrap();
3337        let expected_json = json!(
3338            {
3339                "effect": "permit",
3340                "principal": {
3341                    "op": "==",
3342                    "entity": {
3343                        "type": "a",
3344                        "id": "\0\0\0J",
3345                    }
3346                },
3347                "action": {
3348                    "op": "==",
3349                    "entity": {
3350                        "type": "Action",
3351                        "id": "view",
3352                    }
3353                },
3354                "resource": {
3355                    "op": "All"
3356                },
3357                "conditions": []
3358            }
3359        );
3360        assert_eq!(
3361            serde_json::to_value(&est).unwrap(),
3362            expected_json,
3363            "\nExpected:\n{}\n\nActual:\n{}\n\n",
3364            serde_json::to_string_pretty(&expected_json).unwrap(),
3365            serde_json::to_string_pretty(&est).unwrap()
3366        );
3367        let old_est = est.clone();
3368        let roundtripped = est_roundtrip(est);
3369        assert_eq!(&old_est, &roundtripped);
3370        let est = text_roundtrip(&old_est);
3371        assert_eq!(&old_est, &est);
3372
3373        // during the lossy transform to AST, the only difference for this policy is that
3374        // a `when { true }` is added
3375        let expected_json_after_roundtrip = json!(
3376            {
3377                "effect": "permit",
3378                "principal": {
3379                    "op": "==",
3380                    "entity": {
3381                        "type": "a",
3382                        "id": "\0\0\0J",
3383                    }
3384                },
3385                "action": {
3386                    "op": "==",
3387                    "entity": {
3388                        "type": "Action",
3389                        "id": "view",
3390                    }
3391                },
3392                "resource": {
3393                    "op": "All"
3394                },
3395                "conditions": [
3396                    {
3397                        "kind": "when",
3398                        "body": {
3399                            "Value": true
3400                        }
3401                    }
3402                ]
3403            }
3404        );
3405        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3406        assert_eq!(
3407            roundtripped,
3408            expected_json_after_roundtrip,
3409            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3410            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3411            serde_json::to_string_pretty(&roundtripped).unwrap()
3412        );
3413        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3414        assert_eq!(
3415            roundtripped,
3416            expected_json_after_roundtrip,
3417            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3418            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3419            serde_json::to_string_pretty(&roundtripped).unwrap()
3420        );
3421    }
3422
3423    #[test]
3424    fn invalid_json_ests() {
3425        let bad = json!(
3426            {
3427                "effect": "Permit",
3428                "principal": {
3429                    "op": "All"
3430                },
3431                "action": {
3432                    "op": "All"
3433                },
3434                "resource": {
3435                    "op": "All"
3436                },
3437                "conditions": []
3438            }
3439        );
3440        let est: Result<Policy, _> = serde_json::from_value(bad);
3441        assert_matches!(est, Err(_)); // `Permit` cannot be capitalized
3442
3443        let bad = json!(
3444            {
3445                "effect": "permit",
3446                "principal": {
3447                    "op": "All"
3448                },
3449                "action": {
3450                    "op": "All"
3451                },
3452                "resource": {
3453                    "op": "All"
3454                },
3455                "conditions": [
3456                    {
3457                        "kind": "when",
3458                        "body": {}
3459                    }
3460                ]
3461            }
3462        );
3463        assert_matches!(serde_json::from_value::<Policy>(bad), Err(e) => {
3464            assert_eq!(e.to_string(), "empty map is not a valid expression");
3465        });
3466
3467        let bad = json!(
3468            {
3469                "effect": "permit",
3470                "principal": {
3471                    "op": "All"
3472                },
3473                "action": {
3474                    "op": "All"
3475                },
3476                "resource": {
3477                    "op": "All"
3478                },
3479                "conditions": [
3480                    {
3481                        "kind": "when",
3482                        "body": {
3483                            "+": {
3484                                "left": {
3485                                    "Value": 3
3486                                },
3487                                "right": {
3488                                    "Value": 4
3489                                }
3490                            },
3491                            "-": {
3492                                "left": {
3493                                    "Value": 8
3494                                },
3495                                "right": {
3496                                    "Value": 2
3497                                }
3498                            },
3499                        }
3500                    }
3501                ]
3502            }
3503        );
3504        assert_matches!(serde_json::from_value::<Policy>(bad), Err(e) => {
3505            assert_eq!(e.to_string(), "JSON object representing an `Expr` should have only one key, but found two keys: `+` and `-`");
3506        });
3507
3508        let bad = json!(
3509            {
3510                "effect": "permit",
3511                "principal": {
3512                    "op": "All"
3513                },
3514                "action": {
3515                    "op": "All"
3516                },
3517                "resource": {
3518                    "op": "All"
3519                },
3520                "conditions": [
3521                    {
3522                        "kind": "when",
3523                        "body": {
3524                            "+": {
3525                                "left": {
3526                                    "Value": 3
3527                                },
3528                                "right": {
3529                                    "Value": 4
3530                                }
3531                            },
3532                            "-": {
3533                                "left": {
3534                                    "Value": 2
3535                                },
3536                                "right": {
3537                                    "Value": 8
3538                                }
3539                            }
3540                        }
3541                    }
3542                ]
3543            }
3544        );
3545        let est: Result<Policy, _> = serde_json::from_value(bad);
3546        assert_matches!(est, Err(_)); // two expressions in body, not connected
3547
3548        let template = json!(
3549            {
3550                "effect": "permit",
3551                "principal": {
3552                    "op": "==",
3553                    "slot": "?principal",
3554                },
3555                "action": {
3556                    "op": "All"
3557                },
3558                "resource": {
3559                    "op": "All"
3560                },
3561                "conditions": []
3562            }
3563        );
3564        let est: Policy = serde_json::from_value(template).unwrap();
3565        let ast: Result<ast::Policy, _> = est.try_into_ast_policy(None);
3566        assert_matches!(
3567            ast,
3568            Err(e) => {
3569                expect_err(
3570                    "",
3571                    &miette::Report::new(e),
3572                    &ExpectedErrorMessageBuilder::error(r#"expected a static policy, got a template containing the slot ?principal"#)
3573                        .help("try removing the template slot(s) from this policy")
3574                        .build()
3575                );
3576            }
3577        );
3578    }
3579
3580    #[test]
3581    fn record_duplicate_key() {
3582        let bad = r#"
3583            {
3584                "effect": "permit",
3585                "principal": { "op": "All" },
3586                "action": { "op": "All" },
3587                "resource": { "op": "All" },
3588                "conditions": [
3589                    {
3590                        "kind": "when",
3591                        "body": {
3592                            "Record": {
3593                                "foo": {"Value": 0},
3594                                "foo": {"Value": 1}
3595                            }
3596                        }
3597                    }
3598                ]
3599            }
3600        "#;
3601        let est: Result<Policy, _> = serde_json::from_str(bad);
3602        assert_matches!(est, Err(_));
3603    }
3604
3605    #[test]
3606    fn value_record_duplicate_key() {
3607        let bad = r#"
3608            {
3609                "effect": "permit",
3610                "principal": { "op": "All" },
3611                "action": { "op": "All" },
3612                "resource": { "op": "All" },
3613                "conditions": [
3614                    {
3615                        "kind": "when",
3616                        "body": {
3617                            "Value": {
3618                                "foo": 0,
3619                                "foo": 1
3620                            }
3621                        }
3622                    }
3623                ]
3624            }
3625        "#;
3626        let est: Result<Policy, _> = serde_json::from_str(bad);
3627        assert_matches!(est, Err(_));
3628    }
3629
3630    #[test]
3631    fn duplicate_annotations() {
3632        let bad = r#"
3633            {
3634                "effect": "permit",
3635                "principal": { "op": "All" },
3636                "action": { "op": "All" },
3637                "resource": { "op": "All" },
3638                "conditions": [],
3639                "annotations": {
3640                    "foo": "bar",
3641                    "foo": "baz"
3642                }
3643            }
3644        "#;
3645        let est: Result<Policy, _> = serde_json::from_str(bad);
3646        assert_matches!(est, Err(_));
3647    }
3648
3649    #[test]
3650    fn extension_duplicate_keys() {
3651        let bad = r#"
3652            {
3653                "effect": "permit",
3654                "principal": { "op": "All" },
3655                "action": { "op": "All" },
3656                "resource": { "op": "All" },
3657                "conditions": [
3658                    {
3659                        "kind": "when",
3660                        "body": {
3661                            "ip": [
3662                                {
3663                                    "Value": "222.222.222.0/24"
3664                                }
3665                            ],
3666                            "ip": [
3667                                {
3668                                    "Value": "111.111.111.0/24"
3669                                }
3670                            ]
3671                        }
3672                    }
3673                ]
3674            }
3675        "#;
3676        let est: Result<Policy, _> = serde_json::from_str(bad);
3677        assert_matches!(est, Err(_));
3678    }
3679
3680    mod is_type {
3681        use cool_asserts::assert_panics;
3682
3683        use super::*;
3684
3685        #[test]
3686        fn principal() {
3687            let policy = r"permit(principal is User, action, resource);";
3688            let cst = parser::text_to_cst::parse_policy(policy)
3689                .unwrap()
3690                .node
3691                .unwrap();
3692            let est: Policy = cst.try_into().unwrap();
3693            let expected_json = json!(
3694                {
3695                    "effect": "permit",
3696                    "principal": {
3697                        "op": "is",
3698                        "entity_type": "User"
3699                    },
3700                    "action": {
3701                        "op": "All",
3702                    },
3703                    "resource": {
3704                        "op": "All",
3705                    },
3706                    "conditions": [ ]
3707                }
3708            );
3709            assert_eq!(
3710                serde_json::to_value(&est).unwrap(),
3711                expected_json,
3712                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3713                serde_json::to_string_pretty(&expected_json).unwrap(),
3714                serde_json::to_string_pretty(&est).unwrap()
3715            );
3716            let old_est = est.clone();
3717            let roundtripped = est_roundtrip(est);
3718            assert_eq!(&old_est, &roundtripped);
3719            let est = text_roundtrip(&old_est);
3720            assert_eq!(&old_est, &est);
3721
3722            let expected_json_after_roundtrip = json!(
3723                {
3724                    "effect": "permit",
3725                    "principal": {
3726                        "op": "is",
3727                        "entity_type": "User"
3728                    },
3729                    "action": {
3730                        "op": "All",
3731                    },
3732                    "resource": {
3733                        "op": "All",
3734                    },
3735                    "conditions": [
3736                        {
3737                            "kind": "when",
3738                            "body": {
3739                                "Value": true
3740                            }
3741                        }
3742                    ],
3743                }
3744            );
3745            let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3746            assert_eq!(
3747                roundtripped,
3748                expected_json_after_roundtrip,
3749                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3750                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3751                serde_json::to_string_pretty(&roundtripped).unwrap()
3752            );
3753            let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3754            assert_eq!(
3755                roundtripped,
3756                expected_json_after_roundtrip,
3757                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3758                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3759                serde_json::to_string_pretty(&roundtripped).unwrap()
3760            );
3761        }
3762
3763        #[test]
3764        fn resource() {
3765            let policy = r"permit(principal, action, resource is Log);";
3766            let cst = parser::text_to_cst::parse_policy(policy)
3767                .unwrap()
3768                .node
3769                .unwrap();
3770            let est: Policy = cst.try_into().unwrap();
3771            let expected_json = json!(
3772                {
3773                    "effect": "permit",
3774                    "principal": {
3775                        "op": "All",
3776                    },
3777                    "action": {
3778                        "op": "All",
3779                    },
3780                    "resource": {
3781                        "op": "is",
3782                        "entity_type": "Log"
3783                    },
3784                    "conditions": [ ]
3785                }
3786            );
3787            assert_eq!(
3788                serde_json::to_value(&est).unwrap(),
3789                expected_json,
3790                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3791                serde_json::to_string_pretty(&expected_json).unwrap(),
3792                serde_json::to_string_pretty(&est).unwrap()
3793            );
3794            let old_est = est.clone();
3795            let roundtripped = est_roundtrip(est);
3796            assert_eq!(&old_est, &roundtripped);
3797            let est = text_roundtrip(&old_est);
3798            assert_eq!(&old_est, &est);
3799
3800            let expected_json_after_roundtrip = json!(
3801                {
3802                    "effect": "permit",
3803                    "principal": {
3804                        "op": "All",
3805                    },
3806                    "action": {
3807                        "op": "All",
3808                    },
3809                    "resource": {
3810                        "op": "is",
3811                        "entity_type": "Log"
3812                    },
3813                    "conditions": [
3814                        {
3815                            "kind": "when",
3816                            "body": {
3817                                "Value": true
3818                            }
3819                        }
3820                    ],
3821                }
3822            );
3823            let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3824            assert_eq!(
3825                roundtripped,
3826                expected_json_after_roundtrip,
3827                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3828                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3829                serde_json::to_string_pretty(&roundtripped).unwrap()
3830            );
3831            let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3832            assert_eq!(
3833                roundtripped,
3834                expected_json_after_roundtrip,
3835                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3836                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3837                serde_json::to_string_pretty(&roundtripped).unwrap()
3838            );
3839        }
3840
3841        #[test]
3842        fn principal_in_entity() {
3843            let policy = r#"permit(principal is User in Group::"admin", action, resource);"#;
3844            let cst = parser::text_to_cst::parse_policy(policy)
3845                .unwrap()
3846                .node
3847                .unwrap();
3848            let est: Policy = cst.try_into().unwrap();
3849            let expected_json = json!(
3850                {
3851                    "effect": "permit",
3852                    "principal": {
3853                        "op": "is",
3854                        "entity_type": "User",
3855                        "in": { "entity": { "type": "Group", "id": "admin" } }
3856                    },
3857                    "action": {
3858                        "op": "All",
3859                    },
3860                    "resource": {
3861                        "op": "All",
3862                    },
3863                    "conditions": [ ]
3864                }
3865            );
3866            assert_eq!(
3867                serde_json::to_value(&est).unwrap(),
3868                expected_json,
3869                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3870                serde_json::to_string_pretty(&expected_json).unwrap(),
3871                serde_json::to_string_pretty(&est).unwrap()
3872            );
3873            let old_est = est.clone();
3874            let roundtripped = est_roundtrip(est);
3875            assert_eq!(&old_est, &roundtripped);
3876            let est = text_roundtrip(&old_est);
3877            assert_eq!(&old_est, &est);
3878
3879            let expected_json_after_roundtrip = json!(
3880                {
3881                    "effect": "permit",
3882                    "principal": {
3883                        "op": "is",
3884                        "entity_type": "User",
3885                        "in": { "entity": { "type": "Group", "id": "admin" } }
3886                    },
3887                    "action": {
3888                        "op": "All",
3889                    },
3890                    "resource": {
3891                        "op": "All",
3892                    },
3893                    "conditions": [
3894                        {
3895                            "kind": "when",
3896                            "body": {
3897                                "Value": true
3898                            }
3899                        }
3900                    ],
3901                }
3902            );
3903            let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3904            assert_eq!(
3905                roundtripped,
3906                expected_json_after_roundtrip,
3907                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3908                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3909                serde_json::to_string_pretty(&roundtripped).unwrap()
3910            );
3911            let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3912            assert_eq!(
3913                roundtripped,
3914                expected_json_after_roundtrip,
3915                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3916                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3917                serde_json::to_string_pretty(&roundtripped).unwrap()
3918            );
3919        }
3920
3921        #[test]
3922        fn principal_in_slot() {
3923            let policy = r#"permit(principal is User in ?principal, action, resource);"#;
3924            let cst = parser::text_to_cst::parse_policy(policy)
3925                .unwrap()
3926                .node
3927                .unwrap();
3928            let est: Policy = cst.try_into().unwrap();
3929            let expected_json = json!(
3930                {
3931                    "effect": "permit",
3932                    "principal": {
3933                        "op": "is",
3934                        "entity_type": "User",
3935                        "in": { "slot": "?principal" }
3936                    },
3937                    "action": {
3938                        "op": "All",
3939                    },
3940                    "resource": {
3941                        "op": "All",
3942                    },
3943                    "conditions": [ ]
3944                }
3945            );
3946            assert_eq!(
3947                serde_json::to_value(&est).unwrap(),
3948                expected_json,
3949                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3950                serde_json::to_string_pretty(&expected_json).unwrap(),
3951                serde_json::to_string_pretty(&est).unwrap()
3952            );
3953            let old_est = est.clone();
3954            let roundtripped = est_roundtrip(est);
3955            assert_eq!(&old_est, &roundtripped);
3956            let est = text_roundtrip(&old_est);
3957            assert_eq!(&old_est, &est);
3958
3959            let expected_json_after_roundtrip = json!(
3960                {
3961                    "effect": "permit",
3962                    "principal": {
3963                        "op": "is",
3964                        "entity_type": "User",
3965                        "in": { "slot": "?principal" }
3966                    },
3967                    "action": {
3968                        "op": "All",
3969                    },
3970                    "resource": {
3971                        "op": "All",
3972                    },
3973                    "conditions": [
3974                        {
3975                            "kind": "when",
3976                            "body": {
3977                                "Value": true
3978                            }
3979                        }
3980                    ],
3981                }
3982            );
3983            let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
3984            assert_eq!(
3985                roundtripped,
3986                expected_json_after_roundtrip,
3987                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3988                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3989                serde_json::to_string_pretty(&roundtripped).unwrap()
3990            );
3991            let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
3992            assert_eq!(
3993                roundtripped,
3994                expected_json_after_roundtrip,
3995                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3996                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3997                serde_json::to_string_pretty(&roundtripped).unwrap()
3998            );
3999        }
4000
4001        #[test]
4002        fn condition() {
4003            let policy = r#"
4004            permit(principal, action, resource)
4005            when { principal is User };"#;
4006            let cst = parser::text_to_cst::parse_policy(policy)
4007                .unwrap()
4008                .node
4009                .unwrap();
4010            let est: Policy = cst.try_into().unwrap();
4011            let expected_json = json!(
4012                {
4013                    "effect": "permit",
4014                    "principal": {
4015                        "op": "All",
4016                    },
4017                    "action": {
4018                        "op": "All",
4019                    },
4020                    "resource": {
4021                        "op": "All",
4022                    },
4023                    "conditions": [
4024                        {
4025                            "kind": "when",
4026                            "body": {
4027                                "is": {
4028                                    "left": {
4029                                        "Var": "principal"
4030                                    },
4031                                    "entity_type": "User",
4032                                }
4033                            }
4034                        }
4035                    ]
4036                }
4037            );
4038            assert_eq!(
4039                serde_json::to_value(&est).unwrap(),
4040                expected_json,
4041                "\nExpected:\n{}\n\nActual:\n{}\n\n",
4042                serde_json::to_string_pretty(&expected_json).unwrap(),
4043                serde_json::to_string_pretty(&est).unwrap()
4044            );
4045            let old_est = est.clone();
4046            let roundtripped = est_roundtrip(est);
4047            assert_eq!(&old_est, &roundtripped);
4048            let est = text_roundtrip(&old_est);
4049            assert_eq!(&old_est, &est);
4050
4051            assert_eq!(ast_roundtrip(est.clone()), est);
4052            assert_eq!(circular_roundtrip(est.clone()), est);
4053        }
4054
4055        #[test]
4056        fn condition_in() {
4057            let policy = r#"
4058            permit(principal, action, resource)
4059            when { principal is User in 1 };"#;
4060            let cst = parser::text_to_cst::parse_policy(policy)
4061                .unwrap()
4062                .node
4063                .unwrap();
4064            let est: Policy = cst.try_into().unwrap();
4065            let expected_json = json!(
4066                {
4067                    "effect": "permit",
4068                    "principal": {
4069                        "op": "All",
4070                    },
4071                    "action": {
4072                        "op": "All",
4073                    },
4074                    "resource": {
4075                        "op": "All",
4076                    },
4077                    "conditions": [
4078                        {
4079                            "kind": "when",
4080                            "body": {
4081                                "is": {
4082                                    "left": { "Var": "principal" },
4083                                    "entity_type": "User",
4084                                    "in": {"Value": 1}
4085                                }
4086                            }
4087                        }
4088                    ]
4089                }
4090            );
4091            assert_eq!(
4092                serde_json::to_value(&est).unwrap(),
4093                expected_json,
4094                "\nExpected:\n{}\n\nActual:\n{}\n\n",
4095                serde_json::to_string_pretty(&expected_json).unwrap(),
4096                serde_json::to_string_pretty(&est).unwrap()
4097            );
4098            let old_est = est.clone();
4099            let roundtripped = est_roundtrip(est);
4100            assert_eq!(&old_est, &roundtripped);
4101            let est = text_roundtrip(&old_est);
4102            assert_eq!(&old_est, &est);
4103
4104            let expected_json_after_roundtrip = json!(
4105                {
4106                    "effect": "permit",
4107                    "principal": {
4108                        "op": "All",
4109                    },
4110                    "action": {
4111                        "op": "All",
4112                    },
4113                    "resource": {
4114                        "op": "All",
4115                    },
4116                    "conditions": [
4117                        {
4118                            "kind": "when",
4119                            "body": {
4120                                "&&": {
4121                                    "left": {
4122                                        "is": {
4123                                            "left": { "Var": "principal" },
4124                                            "entity_type": "User",
4125                                        }
4126                                    },
4127                                    "right": {
4128                                        "in": {
4129                                            "left": { "Var": "principal" },
4130                                            "right": { "Value": 1}
4131                                        }
4132                                    }
4133                                }
4134                            }
4135                        }
4136                    ],
4137                }
4138            );
4139            let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
4140            assert_eq!(
4141                roundtripped,
4142                expected_json_after_roundtrip,
4143                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
4144                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
4145                serde_json::to_string_pretty(&roundtripped).unwrap()
4146            );
4147            let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
4148            assert_eq!(
4149                roundtripped,
4150                expected_json_after_roundtrip,
4151                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
4152                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
4153                serde_json::to_string_pretty(&roundtripped).unwrap()
4154            );
4155        }
4156
4157        #[test]
4158        fn invalid() {
4159            let bad = json!(
4160                {
4161                    "effect": "permit",
4162                    "principal": {
4163                        "op": "is"
4164                    },
4165                    "action": {
4166                        "op": "All"
4167                    },
4168                    "resource": {
4169                        "op": "All"
4170                    },
4171                    "conditions": []
4172                }
4173            );
4174            assert_panics!(
4175                serde_json::from_value::<Policy>(bad).unwrap(),
4176                includes("missing field `entity_type`"),
4177            );
4178
4179            let bad = json!(
4180                {
4181                    "effect": "permit",
4182                    "principal": {
4183                        "op": "is",
4184                        "entity_type": "!"
4185                    },
4186                    "action": {
4187                        "op": "All"
4188                    },
4189                    "resource": {
4190                        "op": "All"
4191                    },
4192                    "conditions": []
4193                }
4194            );
4195            assert_matches!(
4196                serde_json::from_value::<Policy>(bad)
4197                    .unwrap()
4198                    .try_into_ast_policy(None),
4199                Err(e) => {
4200                    expect_err(
4201                        "!",
4202                        &miette::Report::new(e),
4203                        &ExpectedErrorMessageBuilder::error(r#"invalid entity type: unexpected token `!`"#)
4204                            .exactly_one_underline_with_label("!", "expected identifier")
4205                            .build()
4206                    );
4207                }
4208            );
4209
4210            let bad = json!(
4211                {
4212                    "effect": "permit",
4213                    "principal": {
4214                        "op": "is",
4215                        "entity_type": "User",
4216                        "==": {"entity": { "type": "User", "id": "alice"}}
4217                    },
4218                    "action": {
4219                        "op": "All"
4220                    },
4221                    "resource": {
4222                        "op": "All"
4223                    },
4224                    "conditions": []
4225                }
4226            );
4227            assert_panics!(
4228                serde_json::from_value::<Policy>(bad).unwrap(),
4229                includes("unknown field `==`, expected `entity_type` or `in`"),
4230            );
4231
4232            let bad = json!(
4233                {
4234                    "effect": "permit",
4235                    "principal": {
4236                        "op": "All",
4237                    },
4238                    "action": {
4239                        "op": "is",
4240                        "entity_type": "Action"
4241                    },
4242                    "resource": {
4243                        "op": "All"
4244                    },
4245                    "conditions": []
4246                }
4247            );
4248            assert_panics!(
4249                serde_json::from_value::<Policy>(bad).unwrap(),
4250                includes("unknown variant `is`, expected one of `All`, `all`, `==`, `in`"),
4251            );
4252        }
4253
4254        #[test]
4255        fn link() {
4256            let template = r#"
4257            permit(
4258                principal is User in ?principal,
4259                action,
4260                resource is Doc in ?resource
4261            );
4262        "#;
4263            let cst = parser::text_to_cst::parse_policy(template)
4264                .unwrap()
4265                .node
4266                .unwrap();
4267            let est: Policy = cst.try_into().unwrap();
4268            let err = est.clone().link(&HashMap::from_iter([]));
4269            assert_matches!(
4270                err,
4271                Err(e) => {
4272                    expect_err(
4273                        "",
4274                        &miette::Report::new(e),
4275                        &ExpectedErrorMessageBuilder::error("failed to link template: no value provided for `?principal`")
4276                            .build()
4277                    );
4278                }
4279            );
4280            let err = est.clone().link(&HashMap::from_iter([(
4281                ast::SlotId::principal(),
4282                EntityUidJson::new("User", "alice"),
4283            )]));
4284            assert_matches!(
4285                err,
4286                Err(e) => {
4287                    expect_err(
4288                        "",
4289                        &miette::Report::new(e),
4290                        &ExpectedErrorMessageBuilder::error("failed to link template: no value provided for `?resource`")
4291                            .build()
4292                    );
4293                }
4294            );
4295            let linked = est
4296                .link(&HashMap::from_iter([
4297                    (
4298                        ast::SlotId::principal(),
4299                        EntityUidJson::new("User", "alice"),
4300                    ),
4301                    (ast::SlotId::resource(), EntityUidJson::new("Folder", "abc")),
4302                ]))
4303                .expect("did fill all the slots");
4304            let expected_json = json!(
4305                {
4306                    "effect": "permit",
4307                    "principal": {
4308                        "op": "is",
4309                        "entity_type": "User",
4310                        "in": { "entity": { "type": "User", "id": "alice" } }
4311                    },
4312                    "action": {
4313                        "op": "All"
4314                    },
4315                    "resource": {
4316                        "op": "is",
4317                        "entity_type": "Doc",
4318                        "in": { "entity": { "type": "Folder", "id": "abc" } }
4319                    },
4320                    "conditions": [ ],
4321                }
4322            );
4323            let linked_json = serde_json::to_value(linked).unwrap();
4324            assert_eq!(
4325                linked_json,
4326                expected_json,
4327                "\nExpected:\n{}\n\nActual:\n{}\n\n",
4328                serde_json::to_string_pretty(&expected_json).unwrap(),
4329                serde_json::to_string_pretty(&linked_json).unwrap(),
4330            );
4331        }
4332
4333        #[test]
4334        fn link_no_slot() {
4335            let template = r#"permit(principal is User, action, resource is Doc);"#;
4336            let cst = parser::text_to_cst::parse_policy(template)
4337                .unwrap()
4338                .node
4339                .unwrap();
4340            let est: Policy = cst.try_into().unwrap();
4341            let linked = est.link(&HashMap::new()).unwrap();
4342            let expected_json = json!(
4343                {
4344                    "effect": "permit",
4345                    "principal": {
4346                        "op": "is",
4347                        "entity_type": "User",
4348                    },
4349                    "action": {
4350                        "op": "All"
4351                    },
4352                    "resource": {
4353                        "op": "is",
4354                        "entity_type": "Doc",
4355                    },
4356                    "conditions": [ ],
4357                }
4358            );
4359            let linked_json = serde_json::to_value(linked).unwrap();
4360            assert_eq!(
4361                linked_json,
4362                expected_json,
4363                "\nExpected:\n{}\n\nActual:\n{}\n\n",
4364                serde_json::to_string_pretty(&expected_json).unwrap(),
4365                serde_json::to_string_pretty(&linked_json).unwrap(),
4366            );
4367        }
4368    }
4369
4370    mod reserved_names {
4371        use cool_asserts::assert_matches;
4372
4373        use crate::{entities::json::err::JsonDeserializationError, est::FromJsonError};
4374
4375        use super::Policy;
4376        #[test]
4377        fn entity_type() {
4378            let policy: Policy = serde_json::from_value(serde_json::json!(
4379                {
4380                    "effect": "permit",
4381                    "principal": {
4382                        "op": "is",
4383                        "entity_type": "__cedar",
4384                    },
4385                    "action": {
4386                        "op": "All"
4387                    },
4388                    "resource": {
4389                        "op": "All",
4390                    },
4391                    "conditions": [ ],
4392                }
4393            ))
4394            .unwrap();
4395            assert_matches!(
4396                policy.try_into_ast_policy(None),
4397                Err(FromJsonError::InvalidEntityType(_))
4398            );
4399
4400            let policy: Policy = serde_json::from_value(serde_json::json!(
4401                {
4402                    "effect": "permit",
4403                    "principal": {
4404                        "op": "All",
4405                    },
4406                    "action": {
4407                        "op": "All"
4408                    },
4409                    "resource": {
4410                        "op": "All",
4411                    },
4412                    "conditions": [ {
4413                        "kind": "when",
4414                        "body": {
4415                            "is": {
4416                                "left": { "Var": "principal" },
4417                                "entity_type": "__cedar",
4418                            }
4419                        }
4420                    } ],
4421                }
4422            ))
4423            .unwrap();
4424            assert_matches!(
4425                policy.try_into_ast_policy(None),
4426                Err(FromJsonError::InvalidEntityType(_))
4427            );
4428        }
4429        #[test]
4430        fn entities() {
4431            let policy: Policy = serde_json::from_value(serde_json::json!(
4432                {
4433                    "effect": "permit",
4434                    "principal": {
4435                        "op": "All"
4436                    },
4437                    "action": {
4438                        "op": "All"
4439                    },
4440                    "resource": {
4441                        "op": "All",
4442                    },
4443                    "conditions": [
4444                        {
4445                            "kind": "when",
4446                            "body": {
4447                                "==": {
4448                                    "left": {
4449                                        "Var": "principal"
4450                                    },
4451                                    "right": {
4452                                        "Value": {
4453                                            "__entity": { "type": "__cedar", "id": "" }
4454                                        }
4455                                    }
4456                                }
4457                            }
4458                        }
4459                    ],
4460                }
4461            ))
4462            .unwrap();
4463            assert_matches!(
4464                policy.try_into_ast_policy(None),
4465                Err(FromJsonError::JsonDeserializationError(
4466                    JsonDeserializationError::ParseEscape(_)
4467                ))
4468            );
4469            let policy: Policy = serde_json::from_value(serde_json::json!(
4470                {
4471                    "effect": "permit",
4472                    "principal": {
4473                        "op": "==",
4474                        "entity": { "type": "__cedar", "id": "12UA45" }
4475                    },
4476                    "action": {
4477                        "op": "All"
4478                    },
4479                    "resource": {
4480                        "op": "All",
4481                    },
4482                    "conditions": [
4483                    ],
4484                }
4485            ))
4486            .unwrap();
4487            assert_matches!(
4488                policy.try_into_ast_policy(None),
4489                Err(FromJsonError::JsonDeserializationError(
4490                    JsonDeserializationError::ParseEscape(_)
4491                ))
4492            );
4493
4494            let policy: Policy = serde_json::from_value(serde_json::json!(
4495                {
4496                    "effect": "permit",
4497                    "principal": {
4498                        "op": "All"
4499                    },
4500                    "action": {
4501                        "op": "All"
4502                    },
4503                    "resource": {
4504                        "op": "==",
4505                        "entity": { "type": "__cedar", "id": "12UA45" }
4506                    },
4507                    "conditions": [
4508                    ],
4509                }
4510            ))
4511            .unwrap();
4512            assert_matches!(
4513                policy.try_into_ast_policy(None),
4514                Err(FromJsonError::JsonDeserializationError(
4515                    JsonDeserializationError::ParseEscape(_)
4516                ))
4517            );
4518
4519            let policy: Policy = serde_json::from_value(serde_json::json!(
4520                {
4521                    "effect": "permit",
4522                    "principal": {
4523                        "op": "All"
4524                    },
4525                    "action": {
4526                        "op": "==",
4527                        "entity": { "type": "__cedar::Action", "id": "12UA45" }
4528                    },
4529                    "resource": {
4530                        "op": "All"
4531                    },
4532                    "conditions": [
4533                    ],
4534                }
4535            ))
4536            .unwrap();
4537            assert_matches!(
4538                policy.try_into_ast_policy(None),
4539                Err(FromJsonError::JsonDeserializationError(
4540                    JsonDeserializationError::ParseEscape(_)
4541                ))
4542            );
4543        }
4544    }
4545
4546    #[test]
4547    fn extended_has() {
4548        let policy_text = r#"
4549        permit(principal, action, resource) when
4550        { principal has a.b.c };"#;
4551        let cst = parser::text_to_cst::parse_policy(policy_text).unwrap();
4552        let est: Policy = cst.node.unwrap().try_into().unwrap();
4553        assert_eq!(
4554            est,
4555            serde_json::from_value(json!({
4556               "effect": "permit",
4557                   "principal": { "op": "All" },
4558                   "action": { "op": "All" },
4559                   "resource": { "op": "All" },
4560                   "conditions": [
4561                       {
4562                           "kind": "when",
4563                           "body": {
4564                               "&&": {
4565                                   "left": {
4566                                       "&&": {
4567                                           "left": {
4568                                               "has": {
4569                                                   "left": {
4570                                                       "Var": "principal",
4571                                                   },
4572                                                   "attr": "a"
4573                                               }
4574                                           },
4575                                           "right": {
4576                                               "has": {
4577                                                   "left": {
4578                                                       ".": {
4579                                                           "left": {
4580                                                               "Var": "principal",
4581                                                           },
4582                                                           "attr": "a",
4583                                                       },
4584                                                   },
4585                                                   "attr": "b"
4586                                               }
4587                                           },
4588                                       }
4589                                   },
4590                                   "right": {
4591                                       "has": {
4592                                           "left": {
4593                                               ".": {
4594                                                   "left": {
4595                                                       ".": {
4596                                                           "left": {
4597                                                               "Var": "principal",
4598                                                           },
4599                                                           "attr": "a"
4600                                                       }
4601                                                   },
4602                                                   "attr": "b",
4603                                               }
4604                                           },
4605                                           "attr": "c",
4606                                       }
4607                                   },
4608                               },
4609                           },
4610                       }
4611                   ]
4612            }))
4613            .unwrap()
4614        );
4615    }
4616
4617    #[test]
4618    fn is_template_principal_slot() {
4619        let template = r#"
4620            permit(
4621                principal == ?principal,
4622                action == Action::"view",
4623                resource
4624            );
4625        "#;
4626        let cst = parser::text_to_cst::parse_policy(template)
4627            .unwrap()
4628            .node
4629            .unwrap();
4630        let est: Policy = cst.try_into().unwrap();
4631        assert!(
4632            est.is_template(),
4633            "Policy with principal slot not marked as template"
4634        );
4635    }
4636
4637    #[test]
4638    fn is_template_resource_slot() {
4639        let template = r#"
4640            permit(
4641                principal,
4642                action == Action::"view",
4643                resource in ?resource
4644            );
4645        "#;
4646        let cst = parser::text_to_cst::parse_policy(template)
4647            .unwrap()
4648            .node
4649            .unwrap();
4650        let est: Policy = cst.try_into().unwrap();
4651        assert!(
4652            est.is_template(),
4653            "Policy with resource slot not marked as template"
4654        );
4655    }
4656
4657    #[test]
4658    fn is_template_static_policy() {
4659        let template = r#"
4660            permit(
4661                principal,
4662                action == Action::"view",
4663                resource
4664            );
4665        "#;
4666        let cst = parser::text_to_cst::parse_policy(template)
4667            .unwrap()
4668            .node
4669            .unwrap();
4670        let est: Policy = cst.try_into().unwrap();
4671        assert!(!est.is_template(), "Static policy marked as template");
4672    }
4673
4674    #[test]
4675    fn is_template_static_policy_with_condition() {
4676        let template = r#"
4677            permit(
4678                principal,
4679                action == Action::"view",
4680                resource
4681            ) when {
4682                principal in resource.owners
4683            };
4684        "#;
4685        let cst = parser::text_to_cst::parse_policy(template)
4686            .unwrap()
4687            .node
4688            .unwrap();
4689        let est: Policy = cst.try_into().unwrap();
4690        assert!(!est.is_template(), "Static policy marked as template");
4691    }
4692}
4693
4694#[cfg(test)]
4695mod issue_891 {
4696    use crate::est;
4697    use cool_asserts::assert_matches;
4698    use serde_json::json;
4699
4700    fn est_json_with_body(body: &serde_json::Value) -> serde_json::Value {
4701        json!(
4702            {
4703                "effect": "permit",
4704                "principal": { "op": "All" },
4705                "action": { "op": "All" },
4706                "resource": { "op": "All" },
4707                "conditions": [
4708                    {
4709                        "kind": "when",
4710                        "body": body,
4711                    }
4712                ]
4713            }
4714        )
4715    }
4716
4717    #[test]
4718    fn invalid_extension_func() {
4719        let src = est_json_with_body(&json!( { "ow4": [ { "Var": "principal" } ] }));
4720        assert_matches!(serde_json::from_value::<est::Policy>(src), Err(e) => {
4721            assert!(e.to_string().starts_with("unknown variant `ow4`, expected one of `Value`, `Var`, "), "e was: {e}");
4722        });
4723
4724        let src = est_json_with_body(&json!(
4725            {
4726                "==": {
4727                    "left": {"Var": "principal"},
4728                    "right": {
4729                        "ownerOrEqual": [
4730                            {"Var": "resource"},
4731                            {"decimal": [{ "Value": "0.75" }]}
4732                        ]
4733                    }
4734                }
4735            }
4736        ));
4737        assert_matches!(serde_json::from_value::<est::Policy>(src), Err(e) => {
4738            assert!(e.to_string().starts_with("unknown variant `ownerOrEqual`, expected one of `Value`, `Var`, "), "e was: {e}");
4739        });
4740
4741        let src = est_json_with_body(&json!(
4742            {
4743                "==": {
4744                    "left": {"Var": "principal"},
4745                    "right": {
4746                        "resorThanOrEqual": [
4747                            {"decimal": [{ "Value": "0.75" }]}
4748                        ]
4749                    }
4750                }
4751            }
4752        ));
4753        assert_matches!(serde_json::from_value::<est::Policy>(src), Err(e) => {
4754            assert!(e.to_string().starts_with("unknown variant `resorThanOrEqual`, expected one of `Value`, `Var`, "), "e was: {e}");
4755        });
4756    }
4757}
4758
4759#[cfg(test)]
4760mod issue_925 {
4761    use crate::{
4762        est,
4763        test_utils::{expect_err, ExpectedErrorMessageBuilder},
4764    };
4765    use cool_asserts::assert_matches;
4766    use serde_json::json;
4767
4768    #[test]
4769    fn invalid_action_type() {
4770        let src = json!(
4771            {
4772                "effect": "permit",
4773                "principal": {
4774                    "op": "All"
4775                },
4776                "action": {
4777                    "op": "==",
4778                    "entity": {
4779                        "type": "NotAction",
4780                        "id": "view",
4781                    }
4782                },
4783                "resource": {
4784                    "op": "All"
4785                },
4786                "conditions": []
4787            }
4788        );
4789        let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
4790        assert_matches!(
4791            est.try_into_ast_policy(None),
4792            Err(e) => {
4793                expect_err(
4794                    &src,
4795                    &miette::Report::new(e),
4796                    &ExpectedErrorMessageBuilder::error(r#"expected an entity uid with type `Action` but got `NotAction::"view"`"#)
4797                        .help("action entities must have type `Action`, optionally in a namespace")
4798                        .build()
4799                );
4800            }
4801        );
4802
4803        let src = json!(
4804            {
4805                "effect": "permit",
4806                "principal": {
4807                    "op": "All"
4808                },
4809                "action": {
4810                    "op": "in",
4811                    "entity": {
4812                        "type": "NotAction",
4813                        "id": "view",
4814                    }
4815                },
4816                "resource": {
4817                    "op": "All"
4818                },
4819                "conditions": []
4820            }
4821        );
4822        let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
4823        assert_matches!(
4824            est.try_into_ast_policy(None),
4825            Err(e) => {
4826                expect_err(
4827                    &src,
4828                    &miette::Report::new(e),
4829                    &ExpectedErrorMessageBuilder::error(r#"expected an entity uid with type `Action` but got `NotAction::"view"`"#)
4830                        .help("action entities must have type `Action`, optionally in a namespace")
4831                        .build()
4832                );
4833            }
4834        );
4835
4836        let src = json!(
4837            {
4838                "effect": "permit",
4839                "principal": {
4840                    "op": "All"
4841                },
4842                "action": {
4843                    "op": "in",
4844                    "entities": [
4845                        {
4846                            "type": "NotAction",
4847                            "id": "view",
4848                        },
4849                        {
4850                            "type": "Other",
4851                            "id": "edit",
4852                        }
4853                    ]
4854                },
4855                "resource": {
4856                    "op": "All"
4857                },
4858                "conditions": []
4859            }
4860        );
4861        let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
4862        assert_matches!(
4863            est.try_into_ast_policy(None),
4864            Err(e) => {
4865                expect_err(
4866                    &src,
4867                    &miette::Report::new(e),
4868                    &ExpectedErrorMessageBuilder::error(r#"expected entity uids with type `Action` but got `NotAction::"view"` and `Other::"edit"`"#)
4869                        .help("action entities must have type `Action`, optionally in a namespace")
4870                        .build()
4871                );
4872            }
4873        );
4874    }
4875}
4876
4877#[cfg(test)]
4878mod issue_994 {
4879    use crate::{
4880        entities::json::err::JsonDeserializationError,
4881        est,
4882        test_utils::{expect_err, ExpectedErrorMessageBuilder},
4883    };
4884    use cool_asserts::assert_matches;
4885    use serde_json::json;
4886
4887    #[test]
4888    fn empty_annotation() {
4889        let src = json!(
4890            {
4891                "annotations": {"": ""},
4892                "effect": "permit",
4893                "principal": { "op": "All" },
4894                "action": { "op": "All" },
4895                "resource": { "op": "All" },
4896                "conditions": []
4897            }
4898        );
4899        assert_matches!(
4900            serde_json::from_value::<est::Policy>(src.clone())
4901                .map_err(|e| JsonDeserializationError::Serde(e.into())),
4902            Err(e) => {
4903                expect_err(
4904                    &src,
4905                    &miette::Report::new(e),
4906                    &ExpectedErrorMessageBuilder::error(r#"invalid id ``: unexpected end of input"#)
4907                        .build()
4908                );
4909            }
4910        );
4911    }
4912
4913    #[test]
4914    fn annotation_with_space() {
4915        let src = json!(
4916            {
4917                "annotations": {"has a space": ""},
4918                "effect": "permit",
4919                "principal": { "op": "All" },
4920                "action": { "op": "All" },
4921                "resource": { "op": "All" },
4922                "conditions": []
4923            }
4924        );
4925        assert_matches!(
4926            serde_json::from_value::<est::Policy>(src.clone())
4927                .map_err(|e| JsonDeserializationError::Serde(e.into())),
4928            Err(e) => {
4929                expect_err(
4930                    &src,
4931                    &miette::Report::new(e),
4932                    &ExpectedErrorMessageBuilder::error(r#"invalid id `has a space`: unexpected token `a`"#)
4933                        .build()
4934                );
4935            }
4936        );
4937    }
4938
4939    #[test]
4940    fn special_char() {
4941        let src = json!(
4942            {
4943                "annotations": {"@": ""},
4944                "effect": "permit",
4945                "principal": { "op": "All" },
4946                "action": { "op": "All" },
4947                "resource": { "op": "All" },
4948                "conditions": []
4949            }
4950        );
4951        assert_matches!(
4952            serde_json::from_value::<est::Policy>(src.clone())
4953                .map_err(|e| JsonDeserializationError::Serde(e.into())),
4954            Err(e) => {
4955                expect_err(
4956                    &src,
4957                    &miette::Report::new(e),
4958                    &ExpectedErrorMessageBuilder::error(r#"invalid id `@`: unexpected token `@`"#)
4959                        .build()
4960                );
4961            }
4962        );
4963    }
4964}
4965
4966#[cfg(feature = "partial-eval")]
4967#[cfg(test)]
4968mod issue_1061 {
4969    use crate::{est, parser};
4970    use serde_json::json;
4971
4972    #[test]
4973    fn function_with_name_unknown() {
4974        let src = json!(
4975            {
4976                "effect": "permit",
4977                "principal": {
4978                    "op": "All"
4979                },
4980                "action": {
4981                    "op": "All"
4982                },
4983                "resource": {
4984                    "op": "All"
4985                },
4986                "conditions": [
4987                    {
4988                        "kind": "when",
4989                        "body": {
4990                            "unknown": [
4991                                {"Value": ""}
4992                            ]
4993                        }
4994                    }
4995                ]
4996            }
4997        );
4998        let est =
4999            serde_json::from_value::<est::Policy>(src).expect("Failed to deserialize policy JSON");
5000        let ast_from_est = est
5001            .try_into_ast_policy(None)
5002            .expect("Failed to convert EST to AST");
5003        let ast_from_cedar = parser::parse_policy_or_template(None, &ast_from_est.to_string())
5004            .expect("Failed to parse policy template");
5005
5006        assert!(ast_from_est
5007            .non_scope_constraints()
5008            .eq_shape(ast_from_cedar.non_scope_constraints()));
5009    }
5010}