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