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