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