Skip to main content

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 scope_constraints;
24pub use scope_constraints::*;
25mod policy_set;
26pub use policy_set::*;
27
28use crate::ast;
29use crate::entities::EntityUidJson;
30use crate::parser::cst;
31use crate::parser::err::{parse_errors, ParseErrors, ToASTError, ToASTErrorKind};
32use serde::{Deserialize, Serialize};
33use serde_with::serde_as;
34use smol_str::SmolStr;
35use std::collections::{BTreeMap, HashMap};
36
37#[cfg(feature = "wasm")]
38extern crate tsify;
39
40/// Serde JSON structure for policies and templates in the EST format
41#[serde_as]
42#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
43#[serde(deny_unknown_fields)]
44#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
45#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
46pub struct Policy {
47    /// `Effect` of the policy or template
48    effect: ast::Effect,
49    /// Principal scope constraint
50    principal: PrincipalConstraint,
51    /// Action scope constraint
52    action: ActionConstraint,
53    /// Resource scope constraint
54    resource: ResourceConstraint,
55    /// `when` and/or `unless` clauses
56    conditions: Vec<Clause>,
57    /// annotations
58    #[serde(default)]
59    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
60    #[serde_as(as = "serde_with::MapPreventDuplicates<_,_>")]
61    #[cfg_attr(feature = "wasm", tsify(type = "Record<string, string>"))]
62    annotations: BTreeMap<ast::AnyId, SmolStr>,
63}
64
65/// Serde JSON structure for a `when` or `unless` clause in the EST format
66#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67#[serde(deny_unknown_fields)]
68#[serde(tag = "kind", content = "body")]
69#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
70#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
71pub enum Clause {
72    /// A `when` clause
73    #[serde(rename = "when")]
74    When(Expr),
75    /// An `unless` clause
76    #[serde(rename = "unless")]
77    Unless(Expr),
78}
79
80impl Policy {
81    /// Fill in any slots in the policy using the values in `vals`. Throws an
82    /// error if `vals` doesn't contain a necessary mapping, but does not throw
83    /// an error if `vals` contains unused mappings -- and in particular if
84    /// `self` is an inline policy (in which case it is returned unchanged).
85    pub fn link(
86        self,
87        vals: &HashMap<ast::SlotId, EntityUidJson>,
88    ) -> Result<Self, InstantiationError> {
89        Ok(Policy {
90            effect: self.effect,
91            principal: self.principal.instantiate(vals)?,
92            action: self.action.instantiate(vals)?,
93            resource: self.resource.instantiate(vals)?,
94            conditions: self
95                .conditions
96                .into_iter()
97                .map(|clause| clause.instantiate(vals))
98                .collect::<Result<Vec<_>, _>>()?,
99            annotations: self.annotations,
100        })
101    }
102}
103
104impl Clause {
105    /// Fill in any slots in the clause using the values in `vals`. Throws an
106    /// error if `vals` doesn't contain a necessary mapping, but does not throw
107    /// an error if `vals` contains unused mappings.
108    pub fn instantiate(
109        self,
110        _vals: &HashMap<ast::SlotId, EntityUidJson>,
111    ) -> Result<Self, InstantiationError> {
112        // currently, slots are not allowed in clauses
113        Ok(self)
114    }
115}
116
117impl TryFrom<cst::Policy> for Policy {
118    type Error = ParseErrors;
119    fn try_from(policy: cst::Policy) -> Result<Policy, ParseErrors> {
120        let mut errs = ParseErrors::new();
121        let effect = policy.effect.to_effect(&mut errs);
122        let (principal, action, resource) = policy.extract_scope(&mut errs);
123        let (annot_success, annotations) = policy.get_ast_annotations(&mut errs);
124        let conditions = match policy
125            .conds
126            .into_iter()
127            .map(|node| {
128                let (cond, loc) = node.into_inner();
129                let cond = cond.ok_or_else(|| {
130                    ParseErrors(vec![ToASTError::new(
131                        ToASTErrorKind::EmptyClause(None),
132                        loc,
133                    )
134                    .into()])
135                })?;
136                cond.try_into()
137            })
138            .collect::<Result<Vec<_>, ParseErrors>>()
139        {
140            Ok(conds) => Some(conds),
141            Err(e) => {
142                errs.extend(e);
143                None
144            }
145        };
146
147        match (
148            effect,
149            principal,
150            action,
151            resource,
152            conditions,
153            annot_success,
154            errs.is_empty(),
155        ) {
156            (
157                Some(effect),
158                Some(principal),
159                Some(action),
160                Some(resource),
161                Some(conditions),
162                true,
163                true,
164            ) => Ok(Policy {
165                effect,
166                principal: principal.into(),
167                action: action.into(),
168                resource: resource.into(),
169                conditions,
170                annotations: annotations.into_iter().map(|(k, v)| (k, v.val)).collect(),
171            }),
172            _ => Err(errs),
173        }
174    }
175}
176
177impl TryFrom<cst::Cond> for Clause {
178    type Error = ParseErrors;
179    fn try_from(cond: cst::Cond) -> Result<Clause, ParseErrors> {
180        let mut errs = ParseErrors::new();
181        let is_when = cond.cond.to_cond_is_when(&mut errs);
182        let expr: Result<Expr, ParseErrors> = match cond.expr {
183            None => {
184                let ident = is_when.map(|is_when| {
185                    cst::Ident::Ident(if is_when { "when" } else { "unless" }.into())
186                });
187                Err(cond
188                    .cond
189                    .to_ast_err(ToASTErrorKind::EmptyClause(ident))
190                    .into())
191            }
192            Some(ref e) => e.try_into(),
193        };
194        let expr = match expr {
195            Ok(expr) => Some(expr),
196            Err(expr_errs) => {
197                errs.extend(expr_errs.0);
198                None
199            }
200        };
201
202        if let (Some(expr), true) = (expr, errs.is_empty()) {
203            Ok(match is_when {
204                // PANIC SAFETY errs should be non empty if is_when is None
205                #[allow(clippy::unreachable)]
206                None => unreachable!("should have had an err in this case"),
207                Some(true) => Clause::When(expr),
208                Some(false) => Clause::Unless(expr),
209            })
210        } else {
211            Err(errs)
212        }
213    }
214}
215
216impl Policy {
217    /// Try to convert a [`Policy`] into a [`ast::Policy`].
218    ///
219    /// This process requires a policy ID. If not supplied, this method will
220    /// fill it in as "JSON policy".
221    pub fn try_into_ast_policy(
222        self,
223        id: Option<ast::PolicyID>,
224    ) -> Result<ast::Policy, FromJsonError> {
225        let template: ast::Template = self.try_into_ast_policy_or_template(id)?;
226        ast::StaticPolicy::try_from(template)
227            .map(Into::into)
228            .map_err(Into::into)
229    }
230
231    /// Try to convert a [`Policy`] into a [`ast::Template`]. Returns an error
232    /// if the input is a static policy.
233    ///
234    /// This process requires a policy ID. If not supplied, this method will
235    /// fill it in as "JSON policy".
236    pub fn try_into_ast_template(
237        self,
238        id: Option<ast::PolicyID>,
239    ) -> Result<ast::Template, FromJsonError> {
240        let template: ast::Template = self.try_into_ast_policy_or_template(id)?;
241        if template.slots().count() == 0 {
242            Err(FromJsonError::PolicyToTemplate(
243                parse_errors::ExpectedTemplate::new(),
244            ))
245        } else {
246            Ok(template)
247        }
248    }
249
250    /// Try to convert a [`Policy`] into a [`ast::Template`]. The `Template` may
251    /// represent a template or static policy (which is a template with zero slots).
252    ///
253    /// This process requires a policy ID. If not supplied, this method will
254    /// fill it in as "JSON policy".
255    pub fn try_into_ast_policy_or_template(
256        self,
257        id: Option<ast::PolicyID>,
258    ) -> Result<ast::Template, FromJsonError> {
259        let id = id.unwrap_or(ast::PolicyID::from_string("JSON policy"));
260        let conditions = match self.conditions.len() {
261            0 => ast::Expr::val(true),
262            _ => {
263                let mut conditions = self
264                    .conditions
265                    .into_iter()
266                    .map(|cond| cond.try_into_ast(id.clone()));
267                // PANIC SAFETY checked above that `conditions` has at least 1 element
268                #[allow(clippy::expect_used)]
269                let first = conditions
270                    .next()
271                    .expect("already checked there is at least 1")?;
272                ast::ExprBuilder::with_data(())
273                    .and_nary(first, conditions.collect::<Result<Vec<_>, _>>()?)
274            }
275        };
276        Ok(ast::Template::new(
277            id,
278            None,
279            self.annotations
280                .into_iter()
281                .map(|(key, val)| (key, ast::Annotation { val, loc: None }))
282                .collect(),
283            self.effect,
284            self.principal.try_into()?,
285            self.action.try_into()?,
286            self.resource.try_into()?,
287            conditions,
288        ))
289    }
290}
291
292impl Clause {
293    fn filter_slots(e: ast::Expr, is_when: bool) -> Result<ast::Expr, FromJsonError> {
294        let first_slot = e.slots().next();
295        if let Some(slot) = first_slot {
296            Err(FromJsonError::SlotsInConditionClause {
297                slot: slot.id,
298                clausetype: if is_when { "when" } else { "unless" },
299            })
300        } else {
301            Ok(e)
302        }
303    }
304    /// `id` is the ID of the policy the clause belongs to, used only for reporting errors
305    fn try_into_ast(self, id: ast::PolicyID) -> Result<ast::Expr, FromJsonError> {
306        match self {
307            Clause::When(expr) => Self::filter_slots(expr.try_into_ast(id)?, true),
308            Clause::Unless(expr) => {
309                Self::filter_slots(ast::Expr::not(expr.try_into_ast(id)?), false)
310            }
311        }
312    }
313}
314
315/// Convert AST to EST
316impl From<ast::Policy> for Policy {
317    fn from(ast: ast::Policy) -> Policy {
318        Policy {
319            effect: ast.effect(),
320            principal: ast.principal_constraint().into(),
321            action: ast.action_constraint().clone().into(),
322            resource: ast.resource_constraint().into(),
323            conditions: vec![ast.non_scope_constraints().clone().into()],
324            annotations: ast
325                .annotations()
326                .map(|(k, v)| (k.clone(), v.val.clone()))
327                .collect(),
328        }
329    }
330}
331
332/// Convert AST to EST
333impl From<ast::Template> for Policy {
334    fn from(ast: ast::Template) -> Policy {
335        Policy {
336            effect: ast.effect(),
337            principal: ast.principal_constraint().clone().into(),
338            action: ast.action_constraint().clone().into(),
339            resource: ast.resource_constraint().clone().into(),
340            conditions: vec![ast.non_scope_constraints().clone().into()],
341            annotations: ast
342                .annotations()
343                .map(|(k, v)| (k.clone(), v.val.clone()))
344                .collect(),
345        }
346    }
347}
348
349impl From<ast::Expr> for Clause {
350    fn from(expr: ast::Expr) -> Clause {
351        Clause::When(expr.into())
352    }
353}
354
355impl std::fmt::Display for Policy {
356    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
357        for (k, v) in self.annotations.iter() {
358            writeln!(f, "@{k}(\"{}\") ", v.escape_debug())?;
359        }
360        write!(
361            f,
362            "{}({}, {}, {})",
363            self.effect, self.principal, self.action, self.resource
364        )?;
365        for condition in &self.conditions {
366            write!(f, " {condition}")?;
367        }
368        write!(f, ";")
369    }
370}
371
372impl std::fmt::Display for Clause {
373    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
374        match self {
375            Self::When(expr) => write!(f, "when {{ {expr} }}"),
376            Self::Unless(expr) => write!(f, "unless {{ {expr} }}"),
377        }
378    }
379}
380
381// PANIC SAFETY: Unit Test Code
382#[allow(clippy::panic)]
383// PANIC SAFETY: Unit Test Code
384#[allow(clippy::indexing_slicing)]
385#[cfg(test)]
386mod test {
387    use super::*;
388    use crate::parser;
389    use crate::test_utils::ExpectedErrorMessageBuilder;
390    use cool_asserts::assert_matches;
391    use serde_json::json;
392
393    /// helper function to just do EST data structure --> JSON --> EST data structure.
394    /// This roundtrip should be lossless for all policies.
395    fn est_roundtrip(est: Policy) -> Policy {
396        let json = serde_json::to_value(est).expect("failed to serialize to JSON");
397        serde_json::from_value(json.clone()).unwrap_or_else(|e| {
398            panic!(
399                "failed to deserialize from JSON: {e}\n\nJSON was:\n{}",
400                serde_json::to_string_pretty(&json).expect("failed to convert JSON to string")
401            )
402        })
403    }
404
405    /// helper function to take EST-->text-->CST-->EST, which directly tests the Display impl for EST.
406    /// This roundtrip should be lossless for all policies.
407    fn text_roundtrip(est: &Policy) -> Policy {
408        let text = est.to_string();
409        let cst = parser::text_to_cst::parse_policy(&text)
410            .expect("Failed to convert to CST")
411            .node
412            .expect("Node should not be empty");
413        cst.try_into().expect("Failed to convert to EST")
414    }
415
416    /// helper function to take EST-->AST-->EST for inline policies.
417    /// This roundtrip is not always lossless, because EST-->AST can be lossy.
418    fn ast_roundtrip(est: Policy) -> Policy {
419        let ast = est
420            .try_into_ast_policy(None)
421            .expect("Failed to convert to AST");
422        ast.into()
423    }
424
425    /// helper function to take EST-->AST-->EST for templates.
426    /// This roundtrip is not always lossless, because EST-->AST can be lossy.
427    fn ast_roundtrip_template(est: Policy) -> Policy {
428        let ast = est
429            .try_into_ast_policy_or_template(None)
430            .expect("Failed to convert to AST");
431        ast.into()
432    }
433
434    /// helper function to take EST-->AST-->text-->CST-->EST for inline policies.
435    /// This roundtrip is not always lossless, because EST-->AST can be lossy.
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    fn circular_roundtrip_template(est: Policy) -> Policy {
451        let ast = est
452            .try_into_ast_policy_or_template(None)
453            .expect("Failed to convert to AST");
454        let text = ast.to_string();
455        let cst = parser::text_to_cst::parse_policy(&text)
456            .expect("Failed to convert to CST")
457            .node
458            .expect("Node should not be empty");
459        cst.try_into().expect("Failed to convert to EST")
460    }
461
462    #[test]
463    fn empty_policy() {
464        let policy = "permit(principal, action, resource);";
465        let cst = parser::text_to_cst::parse_policy(policy)
466            .unwrap()
467            .node
468            .unwrap();
469        let est: Policy = cst.try_into().unwrap();
470        let expected_json = json!(
471            {
472                "effect": "permit",
473                "principal": {
474                    "op": "All",
475                },
476                "action": {
477                    "op": "All",
478                },
479                "resource": {
480                    "op": "All",
481                },
482                "conditions": [],
483            }
484        );
485        assert_eq!(
486            serde_json::to_value(&est).unwrap(),
487            expected_json,
488            "\nExpected:\n{}\n\nActual:\n{}\n\n",
489            serde_json::to_string_pretty(&expected_json).unwrap(),
490            serde_json::to_string_pretty(&est).unwrap()
491        );
492        let old_est = est.clone();
493        let roundtripped = est_roundtrip(est);
494        assert_eq!(&old_est, &roundtripped);
495        let est = text_roundtrip(&old_est);
496        assert_eq!(&old_est, &est);
497
498        // during the lossy transform to AST, the only difference for this policy is that
499        // a `when { true }` is added
500        let expected_json_after_roundtrip = json!(
501            {
502                "effect": "permit",
503                "principal": {
504                    "op": "All",
505                },
506                "action": {
507                    "op": "All",
508                },
509                "resource": {
510                    "op": "All",
511                },
512                "conditions": [
513                    {
514                        "kind": "when",
515                        "body": {
516                            "Value": true
517                        }
518                    }
519                ],
520            }
521        );
522        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
523        assert_eq!(
524            roundtripped,
525            expected_json_after_roundtrip,
526            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
527            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
528            serde_json::to_string_pretty(&roundtripped).unwrap()
529        );
530        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
531        assert_eq!(
532            roundtripped,
533            expected_json_after_roundtrip,
534            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
535            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
536            serde_json::to_string_pretty(&roundtripped).unwrap()
537        );
538    }
539
540    #[test]
541    fn annotated_policy() {
542        let policy = r#"
543            @foo("bar")
544            @this1is2a3valid_identifier("any arbitrary ! string \" is @ allowed in 🦀 here_")
545            permit(principal, action, resource);
546        "#;
547        let cst = parser::text_to_cst::parse_policy(policy)
548            .unwrap()
549            .node
550            .unwrap();
551        let est: Policy = cst.try_into().unwrap();
552        let expected_json = json!(
553            {
554                "effect": "permit",
555                "principal": {
556                    "op": "All",
557                },
558                "action": {
559                    "op": "All",
560                },
561                "resource": {
562                    "op": "All",
563                },
564                "conditions": [],
565                "annotations": {
566                    "foo": "bar",
567                    "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
568                }
569            }
570        );
571        assert_eq!(
572            serde_json::to_value(&est).unwrap(),
573            expected_json,
574            "\nExpected:\n{}\n\nActual:\n{}\n\n",
575            serde_json::to_string_pretty(&expected_json).unwrap(),
576            serde_json::to_string_pretty(&est).unwrap()
577        );
578        let old_est = est.clone();
579        let roundtripped = est_roundtrip(est);
580        assert_eq!(&old_est, &roundtripped);
581        let est = text_roundtrip(&old_est);
582        assert_eq!(&old_est, &est);
583
584        // during the lossy transform to AST, the only difference for this policy is that
585        // a `when { true }` is added
586        let expected_json_after_roundtrip = json!(
587            {
588                "effect": "permit",
589                "principal": {
590                    "op": "All",
591                },
592                "action": {
593                    "op": "All",
594                },
595                "resource": {
596                    "op": "All",
597                },
598                "conditions": [
599                    {
600                        "kind": "when",
601                        "body": {
602                            "Value": true
603                        }
604                    }
605                ],
606                "annotations": {
607                    "foo": "bar",
608                    "this1is2a3valid_identifier": "any arbitrary ! string \" is @ allowed in 🦀 here_",
609                }
610            }
611        );
612        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
613        assert_eq!(
614            roundtripped,
615            expected_json_after_roundtrip,
616            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
617            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
618            serde_json::to_string_pretty(&roundtripped).unwrap()
619        );
620        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
621        assert_eq!(
622            roundtripped,
623            expected_json_after_roundtrip,
624            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
625            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
626            serde_json::to_string_pretty(&roundtripped).unwrap()
627        );
628    }
629
630    /// Test that we can use Cedar reserved words like `if` and `has` as annotation keys
631    #[test]
632    fn reserved_words_as_annotations() {
633        let policy = r#"
634            @if("this is the annotation for `if`")
635            @then("this is the annotation for `then`")
636            @else("this is the annotation for `else`")
637            @true("this is the annotation for `true`")
638            @false("this is the annotation for `false`")
639            @in("this is the annotation for `in`")
640            @is("this is the annotation for `is`")
641            @like("this is the annotation for `like`")
642            @has("this is the annotation for `has`")
643            @principal("this is the annotation for `principal`") // not reserved at time of this writing, but we test it anyway
644            permit(principal, action, resource) when { 2 == 2 };
645        "#;
646
647        let cst = parser::text_to_cst::parse_policy(policy)
648            .unwrap()
649            .node
650            .unwrap();
651        let est: Policy = cst.try_into().unwrap();
652        let expected_json = json!(
653            {
654                "effect": "permit",
655                "principal": {
656                    "op": "All",
657                },
658                "action": {
659                    "op": "All",
660                },
661                "resource": {
662                    "op": "All",
663                },
664                "conditions": [
665                    {
666                        "kind": "when",
667                        "body": {
668                            "==": {
669                                "left": { "Value": 2 },
670                                "right": { "Value": 2 },
671                            }
672                        }
673                    }
674                ],
675                "annotations": {
676                    "if": "this is the annotation for `if`",
677                    "then": "this is the annotation for `then`",
678                    "else": "this is the annotation for `else`",
679                    "true": "this is the annotation for `true`",
680                    "false": "this is the annotation for `false`",
681                    "in": "this is the annotation for `in`",
682                    "is": "this is the annotation for `is`",
683                    "like": "this is the annotation for `like`",
684                    "has": "this is the annotation for `has`",
685                    "principal": "this is the annotation for `principal`",
686                }
687            }
688        );
689        assert_eq!(
690            serde_json::to_value(&est).unwrap(),
691            expected_json,
692            "\nExpected:\n{}\n\nActual:\n{}\n\n",
693            serde_json::to_string_pretty(&expected_json).unwrap(),
694            serde_json::to_string_pretty(&est).unwrap()
695        );
696        let old_est = est.clone();
697        let roundtripped = est_roundtrip(est);
698        assert_eq!(&old_est, &roundtripped);
699        let est = text_roundtrip(&old_est);
700        assert_eq!(&old_est, &est);
701
702        assert_eq!(ast_roundtrip(est.clone()), est);
703        assert_eq!(circular_roundtrip(est.clone()), est);
704    }
705
706    #[test]
707    fn annotation_errors() {
708        let policy = r#"
709            @foo("1")
710            @foo("2")
711            permit(principal, action, resource);
712        "#;
713        let cst = parser::text_to_cst::parse_policy(policy)
714            .unwrap()
715            .node
716            .unwrap();
717        assert_matches!(Policy::try_from(cst), Err(e) => {
718            parser::test_utils::expect_exactly_one_error(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("2")"#).build());
719        });
720
721        let policy = r#"
722            @foo("1")
723            @foo("1")
724            permit(principal, action, resource);
725        "#;
726        let cst = parser::text_to_cst::parse_policy(policy)
727            .unwrap()
728            .node
729            .unwrap();
730        assert_matches!(Policy::try_from(cst), Err(e) => {
731            parser::test_utils::expect_exactly_one_error(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("1")"#).build());
732        });
733
734        let policy = r#"
735            @foo("1")
736            @bar("yellow")
737            @foo("abc")
738            @hello("goodbye")
739            @bar("123")
740            @foo("def")
741            permit(principal, action, resource);
742        "#;
743        let cst = parser::text_to_cst::parse_policy(policy)
744            .unwrap()
745            .node
746            .unwrap();
747        assert_matches!(Policy::try_from(cst), Err(e) => {
748            assert_eq!(e.len(), 3); // two errors for @foo and one for @bar
749            parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("abc")"#).build());
750            parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @foo").exactly_one_underline(r#"@foo("def")"#).build());
751            parser::test_utils::expect_some_error_matches(policy, &e, &ExpectedErrorMessageBuilder::error("duplicate annotation: @bar").exactly_one_underline(r#"@bar("123")"#).build());
752        });
753
754        // the above tests ensure that we give the correct errors for CSTs
755        // containing duplicate annotations.
756        // This test ensures that we give the correct errors for JSON text
757        // containing duplicate annotations.
758        // Note that we have to use a string here as input (and not
759        // serde_json::Value) because serde_json::Value would already remove
760        // duplicates
761        let est = r#"
762            {
763                "effect": "permit",
764                "principal": {
765                    "op": "All"
766                },
767                "action": {
768                    "op": "All"
769                },
770                "resource": {
771                    "op": "All"
772                },
773                "conditions": [],
774                "annotations": {
775                    "foo": "1",
776                    "foo": "2"
777                }
778            }
779        "#;
780        assert_matches!(serde_json::from_str::<Policy>(est), Err(e) => {
781            assert_eq!(e.to_string(), "invalid entry: found duplicate key at line 17 column 17");
782        });
783    }
784
785    #[test]
786    fn rbac_policy() {
787        let policy = r#"
788            permit(
789                principal == User::"12UA45",
790                action == Action::"view",
791                resource in Folder::"abc"
792            );
793        "#;
794        let cst = parser::text_to_cst::parse_policy(policy)
795            .unwrap()
796            .node
797            .unwrap();
798        let est: Policy = cst.try_into().unwrap();
799        let expected_json = json!(
800            {
801                "effect": "permit",
802                "principal": {
803                    "op": "==",
804                    "entity": { "type": "User", "id": "12UA45" },
805                },
806                "action": {
807                    "op": "==",
808                    "entity": { "type": "Action", "id": "view" },
809                },
810                "resource": {
811                    "op": "in",
812                    "entity": { "type": "Folder", "id": "abc" },
813                },
814                "conditions": []
815            }
816        );
817        assert_eq!(
818            serde_json::to_value(&est).unwrap(),
819            expected_json,
820            "\nExpected:\n{}\n\nActual:\n{}\n\n",
821            serde_json::to_string_pretty(&expected_json).unwrap(),
822            serde_json::to_string_pretty(&est).unwrap()
823        );
824        let old_est = est.clone();
825        let roundtripped = est_roundtrip(est);
826        assert_eq!(&old_est, &roundtripped);
827        let est = text_roundtrip(&old_est);
828        assert_eq!(&old_est, &est);
829
830        // during the lossy transform to AST, the only difference for this policy is that
831        // a `when { true }` is added
832        let expected_json_after_roundtrip = json!(
833            {
834                "effect": "permit",
835                "principal": {
836                    "op": "==",
837                    "entity": { "type": "User", "id": "12UA45" },
838                },
839                "action": {
840                    "op": "==",
841                    "entity": { "type": "Action", "id": "view" },
842                },
843                "resource": {
844                    "op": "in",
845                    "entity": { "type": "Folder", "id": "abc" },
846                },
847                "conditions": [
848                    {
849                        "kind": "when",
850                        "body": {
851                            "Value": true
852                        }
853                    }
854                ]
855            }
856        );
857        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
858        assert_eq!(
859            roundtripped,
860            expected_json_after_roundtrip,
861            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
862            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
863            serde_json::to_string_pretty(&roundtripped).unwrap()
864        );
865        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
866        assert_eq!(
867            roundtripped,
868            expected_json_after_roundtrip,
869            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
870            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
871            serde_json::to_string_pretty(&roundtripped).unwrap()
872        );
873    }
874
875    #[test]
876    fn rbac_template() {
877        let template = r#"
878            permit(
879                principal == ?principal,
880                action == Action::"view",
881                resource in ?resource
882            );
883        "#;
884        let cst = parser::text_to_cst::parse_policy(template)
885            .unwrap()
886            .node
887            .unwrap();
888        let est: Policy = cst.try_into().unwrap();
889        let expected_json = json!(
890            {
891                "effect": "permit",
892                "principal": {
893                    "op": "==",
894                    "slot": "?principal",
895                },
896                "action": {
897                    "op": "==",
898                    "entity": { "type": "Action", "id": "view" },
899                },
900                "resource": {
901                    "op": "in",
902                    "slot": "?resource",
903                },
904                "conditions": []
905            }
906        );
907        assert_eq!(
908            serde_json::to_value(&est).unwrap(),
909            expected_json,
910            "\nExpected:\n{}\n\nActual:\n{}\n\n",
911            serde_json::to_string_pretty(&expected_json).unwrap(),
912            serde_json::to_string_pretty(&est).unwrap()
913        );
914        let old_est = est.clone();
915        let roundtripped = est_roundtrip(est);
916        assert_eq!(&old_est, &roundtripped);
917        let est = text_roundtrip(&old_est);
918        assert_eq!(&old_est, &est);
919
920        // during the lossy transform to AST, the only difference for this policy is that
921        // a `when { true }` is added
922        let expected_json_after_roundtrip = json!(
923            {
924                "effect": "permit",
925                "principal": {
926                    "op": "==",
927                    "slot": "?principal",
928                },
929                "action": {
930                    "op": "==",
931                    "entity": { "type": "Action", "id": "view" },
932                },
933                "resource": {
934                    "op": "in",
935                    "slot": "?resource",
936                },
937                "conditions": [
938                    {
939                        "kind": "when",
940                        "body": {
941                            "Value": true
942                        }
943                    }
944                ]
945            }
946        );
947        let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
948        assert_eq!(
949            roundtripped,
950            expected_json_after_roundtrip,
951            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
952            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
953            serde_json::to_string_pretty(&roundtripped).unwrap()
954        );
955        let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
956        assert_eq!(
957            roundtripped,
958            expected_json_after_roundtrip,
959            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
960            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
961            serde_json::to_string_pretty(&roundtripped).unwrap()
962        );
963    }
964
965    #[test]
966    fn abac_policy() {
967        let policy = r#"
968            permit(
969                principal == User::"12UA45",
970                action == Action::"view",
971                resource in Folder::"abc"
972            ) when {
973                context.tls_version == "1.3"
974            };
975        "#;
976        let cst = parser::text_to_cst::parse_policy(policy)
977            .unwrap()
978            .node
979            .unwrap();
980        let est: Policy = cst.try_into().unwrap();
981        let expected_json = json!(
982            {
983                "effect": "permit",
984                "principal": {
985                    "op": "==",
986                    "entity": { "type": "User", "id": "12UA45" },
987                },
988                "action": {
989                    "op": "==",
990                    "entity": { "type": "Action", "id": "view" },
991                },
992                "resource": {
993                    "op": "in",
994                    "entity": { "type": "Folder", "id": "abc" },
995                },
996                "conditions": [
997                    {
998                        "kind": "when",
999                        "body": {
1000                            "==": {
1001                                "left": {
1002                                    ".": {
1003                                        "left": { "Var": "context" },
1004                                        "attr": "tls_version",
1005                                    },
1006                                },
1007                                "right": {
1008                                    "Value": "1.3"
1009                                }
1010                            }
1011                        }
1012                    }
1013                ]
1014            }
1015        );
1016        assert_eq!(
1017            serde_json::to_value(&est).unwrap(),
1018            expected_json,
1019            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1020            serde_json::to_string_pretty(&expected_json).unwrap(),
1021            serde_json::to_string_pretty(&est).unwrap()
1022        );
1023        let old_est = est.clone();
1024        let roundtripped = est_roundtrip(est);
1025        assert_eq!(&old_est, &roundtripped);
1026        let est = text_roundtrip(&old_est);
1027        assert_eq!(&old_est, &est);
1028
1029        assert_eq!(ast_roundtrip(est.clone()), est);
1030        assert_eq!(circular_roundtrip(est.clone()), est);
1031    }
1032
1033    #[test]
1034    fn action_list() {
1035        let policy = r#"
1036            permit(
1037                principal == User::"12UA45",
1038                action in [Action::"read", Action::"write"],
1039                resource
1040            );
1041        "#;
1042        let cst = parser::text_to_cst::parse_policy(policy)
1043            .unwrap()
1044            .node
1045            .unwrap();
1046        let est: Policy = cst.try_into().unwrap();
1047        let expected_json = json!(
1048            {
1049                "effect": "permit",
1050                "principal": {
1051                    "op": "==",
1052                    "entity": { "type": "User", "id": "12UA45" },
1053                },
1054                "action": {
1055                    "op": "in",
1056                    "entities": [
1057                        { "type": "Action", "id": "read" },
1058                        { "type": "Action", "id": "write" },
1059                    ]
1060                },
1061                "resource": {
1062                    "op": "All",
1063                },
1064                "conditions": []
1065            }
1066        );
1067        assert_eq!(
1068            serde_json::to_value(&est).unwrap(),
1069            expected_json,
1070            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1071            serde_json::to_string_pretty(&expected_json).unwrap(),
1072            serde_json::to_string_pretty(&est).unwrap()
1073        );
1074        let old_est = est.clone();
1075        let roundtripped = est_roundtrip(est);
1076        assert_eq!(&old_est, &roundtripped);
1077        let est = text_roundtrip(&old_est);
1078        assert_eq!(&old_est, &est);
1079
1080        // during the lossy transform to AST, the only difference for this policy is that
1081        // a `when { true }` is added
1082        let expected_json_after_roundtrip = json!(
1083            {
1084                "effect": "permit",
1085                "principal": {
1086                    "op": "==",
1087                    "entity": { "type": "User", "id": "12UA45" },
1088                },
1089                "action": {
1090                    "op": "in",
1091                    "entities": [
1092                        { "type": "Action", "id": "read" },
1093                        { "type": "Action", "id": "write" },
1094                    ]
1095                },
1096                "resource": {
1097                    "op": "All",
1098                },
1099                "conditions": [
1100                    {
1101                        "kind": "when",
1102                        "body": {
1103                            "Value": true
1104                        }
1105                    }
1106                ]
1107            }
1108        );
1109        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1110        assert_eq!(
1111            roundtripped,
1112            expected_json_after_roundtrip,
1113            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1114            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1115            serde_json::to_string_pretty(&roundtripped).unwrap()
1116        );
1117        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1118        assert_eq!(
1119            roundtripped,
1120            expected_json_after_roundtrip,
1121            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1122            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1123            serde_json::to_string_pretty(&roundtripped).unwrap()
1124        );
1125    }
1126
1127    #[test]
1128    fn num_literals() {
1129        let policy = r#"
1130            permit(principal, action, resource)
1131            when { 1 == 2 };
1132        "#;
1133        let cst = parser::text_to_cst::parse_policy(policy)
1134            .unwrap()
1135            .node
1136            .unwrap();
1137        let est: Policy = cst.try_into().unwrap();
1138        let expected_json = json!(
1139            {
1140                "effect": "permit",
1141                "principal": {
1142                    "op": "All",
1143                },
1144                "action": {
1145                    "op": "All",
1146                },
1147                "resource": {
1148                    "op": "All",
1149                },
1150                "conditions": [
1151                    {
1152                        "kind": "when",
1153                        "body": {
1154                            "==": {
1155                                "left": {
1156                                    "Value": 1
1157                                },
1158                                "right": {
1159                                    "Value": 2
1160                                }
1161                            }
1162                        }
1163                    }
1164                ]
1165            }
1166        );
1167        assert_eq!(
1168            serde_json::to_value(&est).unwrap(),
1169            expected_json,
1170            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1171            serde_json::to_string_pretty(&expected_json).unwrap(),
1172            serde_json::to_string_pretty(&est).unwrap()
1173        );
1174        let old_est = est.clone();
1175        let roundtripped = est_roundtrip(est);
1176        assert_eq!(&old_est, &roundtripped);
1177        let est = text_roundtrip(&old_est);
1178        assert_eq!(&old_est, &est);
1179
1180        assert_eq!(ast_roundtrip(est.clone()), est);
1181        assert_eq!(circular_roundtrip(est.clone()), est);
1182    }
1183
1184    #[test]
1185    fn entity_literals() {
1186        let policy = r#"
1187            permit(principal, action, resource)
1188            when { User::"alice" == Namespace::Type::"foo" };
1189        "#;
1190        let cst = parser::text_to_cst::parse_policy(policy)
1191            .unwrap()
1192            .node
1193            .unwrap();
1194        let est: Policy = cst.try_into().unwrap();
1195        let expected_json = json!(
1196            {
1197                "effect": "permit",
1198                "principal": {
1199                    "op": "All",
1200                },
1201                "action": {
1202                    "op": "All",
1203                },
1204                "resource": {
1205                    "op": "All",
1206                },
1207                "conditions": [
1208                    {
1209                        "kind": "when",
1210                        "body": {
1211                            "==": {
1212                                "left": {
1213                                    "Value": {
1214                                        "__entity": {
1215                                            "type": "User",
1216                                            "id": "alice"
1217                                        }
1218                                    }
1219                                },
1220                                "right": {
1221                                    "Value": {
1222                                        "__entity": {
1223                                            "type": "Namespace::Type",
1224                                            "id": "foo"
1225                                        }
1226                                    }
1227                                }
1228                            }
1229                        }
1230                    }
1231                ]
1232            }
1233        );
1234        assert_eq!(
1235            serde_json::to_value(&est).unwrap(),
1236            expected_json,
1237            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1238            serde_json::to_string_pretty(&expected_json).unwrap(),
1239            serde_json::to_string_pretty(&est).unwrap()
1240        );
1241        let old_est = est.clone();
1242        let roundtripped = est_roundtrip(est);
1243        assert_eq!(&old_est, &roundtripped);
1244        let est = text_roundtrip(&old_est);
1245        assert_eq!(&old_est, &est);
1246
1247        assert_eq!(ast_roundtrip(est.clone()), est);
1248        assert_eq!(circular_roundtrip(est.clone()), est);
1249    }
1250
1251    #[test]
1252    fn bool_literals() {
1253        let policy = r#"
1254            permit(principal, action, resource)
1255            when { false == true };
1256        "#;
1257        let cst = parser::text_to_cst::parse_policy(policy)
1258            .unwrap()
1259            .node
1260            .unwrap();
1261        let est: Policy = cst.try_into().unwrap();
1262        let expected_json = json!(
1263            {
1264                "effect": "permit",
1265                "principal": {
1266                    "op": "All",
1267                },
1268                "action": {
1269                    "op": "All",
1270                },
1271                "resource": {
1272                    "op": "All",
1273                },
1274                "conditions": [
1275                    {
1276                        "kind": "when",
1277                        "body": {
1278                            "==": {
1279                                "left": {
1280                                    "Value": false
1281                                },
1282                                "right": {
1283                                    "Value": true
1284                                }
1285                            }
1286                        }
1287                    }
1288                ]
1289            }
1290        );
1291        assert_eq!(
1292            serde_json::to_value(&est).unwrap(),
1293            expected_json,
1294            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1295            serde_json::to_string_pretty(&expected_json).unwrap(),
1296            serde_json::to_string_pretty(&est).unwrap()
1297        );
1298        let old_est = est.clone();
1299        let roundtripped = est_roundtrip(est);
1300        assert_eq!(&old_est, &roundtripped);
1301        let est = text_roundtrip(&old_est);
1302        assert_eq!(&old_est, &est);
1303
1304        assert_eq!(ast_roundtrip(est.clone()), est);
1305        assert_eq!(circular_roundtrip(est.clone()), est);
1306    }
1307
1308    #[test]
1309    fn string_literals() {
1310        let policy = r#"
1311            permit(principal, action, resource)
1312            when { "spam" == "eggs" };
1313        "#;
1314        let cst = parser::text_to_cst::parse_policy(policy)
1315            .unwrap()
1316            .node
1317            .unwrap();
1318        let est: Policy = cst.try_into().unwrap();
1319        let expected_json = json!(
1320            {
1321                "effect": "permit",
1322                "principal": {
1323                    "op": "All",
1324                },
1325                "action": {
1326                    "op": "All",
1327                },
1328                "resource": {
1329                    "op": "All",
1330                },
1331                "conditions": [
1332                    {
1333                        "kind": "when",
1334                        "body": {
1335                            "==": {
1336                                "left": {
1337                                    "Value": "spam"
1338                                },
1339                                "right": {
1340                                    "Value": "eggs"
1341                                }
1342                            }
1343                        }
1344                    }
1345                ]
1346            }
1347        );
1348        assert_eq!(
1349            serde_json::to_value(&est).unwrap(),
1350            expected_json,
1351            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1352            serde_json::to_string_pretty(&expected_json).unwrap(),
1353            serde_json::to_string_pretty(&est).unwrap()
1354        );
1355        let old_est = est.clone();
1356        let roundtripped = est_roundtrip(est);
1357        assert_eq!(&old_est, &roundtripped);
1358        let est = text_roundtrip(&old_est);
1359        assert_eq!(&old_est, &est);
1360
1361        assert_eq!(ast_roundtrip(est.clone()), est);
1362        assert_eq!(circular_roundtrip(est.clone()), est);
1363    }
1364
1365    #[test]
1366    fn set_literals() {
1367        let policy = r#"
1368            permit(principal, action, resource)
1369            when { [1, 2, "foo"] == [4, 5, "spam"] };
1370        "#;
1371        let cst = parser::text_to_cst::parse_policy(policy)
1372            .unwrap()
1373            .node
1374            .unwrap();
1375        let est: Policy = cst.try_into().unwrap();
1376        let expected_json = json!(
1377            {
1378                "effect": "permit",
1379                "principal": {
1380                    "op": "All",
1381                },
1382                "action": {
1383                    "op": "All",
1384                },
1385                "resource": {
1386                    "op": "All",
1387                },
1388                "conditions": [
1389                    {
1390                        "kind": "when",
1391                        "body": {
1392                            "==": {
1393                                "left": {
1394                                    "Set": [
1395                                        { "Value": 1 },
1396                                        { "Value": 2 },
1397                                        { "Value": "foo" },
1398                                    ]
1399                                },
1400                                "right": {
1401                                    "Set": [
1402                                        { "Value": 4 },
1403                                        { "Value": 5 },
1404                                        { "Value": "spam" },
1405                                    ]
1406                                }
1407                            }
1408                        }
1409                    }
1410                ]
1411            }
1412        );
1413        assert_eq!(
1414            serde_json::to_value(&est).unwrap(),
1415            expected_json,
1416            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1417            serde_json::to_string_pretty(&expected_json).unwrap(),
1418            serde_json::to_string_pretty(&est).unwrap()
1419        );
1420        let old_est = est.clone();
1421        let roundtripped = est_roundtrip(est);
1422        assert_eq!(&old_est, &roundtripped);
1423        let est = text_roundtrip(&old_est);
1424        assert_eq!(&old_est, &est);
1425
1426        assert_eq!(ast_roundtrip(est.clone()), est);
1427        assert_eq!(circular_roundtrip(est.clone()), est);
1428    }
1429
1430    #[test]
1431    fn record_literals() {
1432        let policy = r#"
1433            permit(principal, action, resource)
1434            when { {foo: "spam", bar: false} == {} };
1435        "#;
1436        let cst = parser::text_to_cst::parse_policy(policy)
1437            .unwrap()
1438            .node
1439            .unwrap();
1440        let est: Policy = cst.try_into().unwrap();
1441        let expected_json = json!(
1442            {
1443                "effect": "permit",
1444                "principal": {
1445                    "op": "All",
1446                },
1447                "action": {
1448                    "op": "All",
1449                },
1450                "resource": {
1451                    "op": "All",
1452                },
1453                "conditions": [
1454                    {
1455                        "kind": "when",
1456                        "body": {
1457                            "==": {
1458                                "left": {
1459                                    "Record": {
1460                                        "foo": { "Value": "spam" },
1461                                        "bar": { "Value": false },
1462                                    }
1463                                },
1464                                "right": {
1465                                    "Record": {}
1466                                }
1467                            }
1468                        }
1469                    }
1470                ]
1471            }
1472        );
1473        assert_eq!(
1474            serde_json::to_value(&est).unwrap(),
1475            expected_json,
1476            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1477            serde_json::to_string_pretty(&expected_json).unwrap(),
1478            serde_json::to_string_pretty(&est).unwrap()
1479        );
1480        let old_est = est.clone();
1481        let roundtripped = est_roundtrip(est);
1482        assert_eq!(&old_est, &roundtripped);
1483        let est = text_roundtrip(&old_est);
1484        assert_eq!(&old_est, &est);
1485
1486        assert_eq!(ast_roundtrip(est.clone()), est);
1487        assert_eq!(circular_roundtrip(est.clone()), est);
1488    }
1489
1490    #[test]
1491    fn policy_variables() {
1492        let policy = r#"
1493            permit(principal, action, resource)
1494            when { principal == action && resource == context };
1495        "#;
1496        let cst = parser::text_to_cst::parse_policy(policy)
1497            .unwrap()
1498            .node
1499            .unwrap();
1500        let est: Policy = cst.try_into().unwrap();
1501        let expected_json = json!(
1502            {
1503                "effect": "permit",
1504                "principal": {
1505                    "op": "All",
1506                },
1507                "action": {
1508                    "op": "All",
1509                },
1510                "resource": {
1511                    "op": "All",
1512                },
1513                "conditions": [
1514                    {
1515                        "kind": "when",
1516                        "body": {
1517                            "&&": {
1518                                "left": {
1519                                    "==": {
1520                                        "left": {
1521                                            "Var": "principal"
1522                                        },
1523                                        "right": {
1524                                            "Var": "action"
1525                                        }
1526                                    }
1527                                },
1528                                "right": {
1529                                    "==": {
1530                                        "left": {
1531                                            "Var": "resource"
1532                                        },
1533                                        "right": {
1534                                            "Var": "context"
1535                                        }
1536                                    }
1537                                }
1538                            }
1539                        }
1540                    }
1541                ]
1542            }
1543        );
1544        assert_eq!(
1545            serde_json::to_value(&est).unwrap(),
1546            expected_json,
1547            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1548            serde_json::to_string_pretty(&expected_json).unwrap(),
1549            serde_json::to_string_pretty(&est).unwrap()
1550        );
1551        let old_est = est.clone();
1552        let roundtripped = est_roundtrip(est);
1553        assert_eq!(&old_est, &roundtripped);
1554        let est = text_roundtrip(&old_est);
1555        assert_eq!(&old_est, &est);
1556
1557        assert_eq!(ast_roundtrip(est.clone()), est);
1558        assert_eq!(circular_roundtrip(est.clone()), est);
1559    }
1560
1561    #[test]
1562    fn not() {
1563        let policy = r#"
1564            permit(principal, action, resource)
1565            when { !context.foo && principal != context.bar };
1566        "#;
1567        let cst = parser::text_to_cst::parse_policy(policy)
1568            .unwrap()
1569            .node
1570            .unwrap();
1571        let est: Policy = cst.try_into().unwrap();
1572        let expected_json = json!(
1573            {
1574                "effect": "permit",
1575                "principal": {
1576                    "op": "All",
1577                },
1578                "action": {
1579                    "op": "All",
1580                },
1581                "resource": {
1582                    "op": "All",
1583                },
1584                "conditions": [
1585                    {
1586                        "kind": "when",
1587                        "body": {
1588                            "&&": {
1589                                "left": {
1590                                    "!": {
1591                                        "arg": {
1592                                            ".": {
1593                                                "left": {
1594                                                    "Var": "context"
1595                                                },
1596                                                "attr": "foo"
1597                                            }
1598                                        }
1599                                    }
1600                                },
1601                                "right": {
1602                                    "!=": {
1603                                        "left": {
1604                                            "Var": "principal"
1605                                        },
1606                                        "right": {
1607                                            ".": {
1608                                                "left": {
1609                                                    "Var": "context"
1610                                                },
1611                                                "attr": "bar"
1612                                            }
1613                                        }
1614                                    }
1615                                }
1616                            }
1617                        }
1618                    }
1619                ]
1620            }
1621        );
1622        assert_eq!(
1623            serde_json::to_value(&est).unwrap(),
1624            expected_json,
1625            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1626            serde_json::to_string_pretty(&expected_json).unwrap(),
1627            serde_json::to_string_pretty(&est).unwrap()
1628        );
1629        let old_est = est.clone();
1630        let roundtripped = est_roundtrip(est);
1631        assert_eq!(&old_est, &roundtripped);
1632        let est = text_roundtrip(&old_est);
1633        assert_eq!(&old_est, &est);
1634
1635        // during the lossy transform to AST, the only difference for this policy is that
1636        // `!=` is expanded to `!(==)`
1637        let expected_json_after_roundtrip = json!(
1638            {
1639                "effect": "permit",
1640                "principal": {
1641                    "op": "All",
1642                },
1643                "action": {
1644                    "op": "All",
1645                },
1646                "resource": {
1647                    "op": "All",
1648                },
1649                "conditions": [
1650                    {
1651                        "kind": "when",
1652                        "body": {
1653                            "&&": {
1654                                "left": {
1655                                    "!": {
1656                                        "arg": {
1657                                            ".": {
1658                                                "left": {
1659                                                    "Var": "context"
1660                                                },
1661                                                "attr": "foo"
1662                                            }
1663                                        }
1664                                    }
1665                                },
1666                                "right": {
1667                                    "!": {
1668                                        "arg": {
1669                                            "==": {
1670                                                "left": {
1671                                                    "Var": "principal"
1672                                                },
1673                                                "right": {
1674                                                    ".": {
1675                                                        "left": {
1676                                                            "Var": "context"
1677                                                        },
1678                                                        "attr": "bar"
1679                                                    }
1680                                                }
1681                                            }
1682                                        }
1683                                    }
1684                                }
1685                            }
1686                        }
1687                    }
1688                ]
1689            }
1690        );
1691        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
1692        assert_eq!(
1693            roundtripped,
1694            expected_json_after_roundtrip,
1695            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1696            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1697            serde_json::to_string_pretty(&roundtripped).unwrap()
1698        );
1699        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
1700        assert_eq!(
1701            roundtripped,
1702            expected_json_after_roundtrip,
1703            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
1704            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
1705            serde_json::to_string_pretty(&roundtripped).unwrap()
1706        );
1707    }
1708
1709    #[test]
1710    fn hierarchy_in() {
1711        let policy = r#"
1712            permit(principal, action, resource)
1713            when { resource in principal.department };
1714        "#;
1715        let cst = parser::text_to_cst::parse_policy(policy)
1716            .unwrap()
1717            .node
1718            .unwrap();
1719        let est: Policy = cst.try_into().unwrap();
1720        let expected_json = json!(
1721            {
1722                "effect": "permit",
1723                "principal": {
1724                    "op": "All",
1725                },
1726                "action": {
1727                    "op": "All",
1728                },
1729                "resource": {
1730                    "op": "All",
1731                },
1732                "conditions": [
1733                    {
1734                        "kind": "when",
1735                        "body": {
1736                            "in": {
1737                                "left": {
1738                                    "Var": "resource"
1739                                },
1740                                "right": {
1741                                    ".": {
1742                                        "left": {
1743                                            "Var": "principal"
1744                                        },
1745                                        "attr": "department"
1746                                    }
1747                                }
1748                            }
1749                        }
1750                    }
1751                ]
1752            }
1753        );
1754        assert_eq!(
1755            serde_json::to_value(&est).unwrap(),
1756            expected_json,
1757            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1758            serde_json::to_string_pretty(&expected_json).unwrap(),
1759            serde_json::to_string_pretty(&est).unwrap()
1760        );
1761        let old_est = est.clone();
1762        let roundtripped = est_roundtrip(est);
1763        assert_eq!(&old_est, &roundtripped);
1764        let est = text_roundtrip(&old_est);
1765        assert_eq!(&old_est, &est);
1766
1767        assert_eq!(ast_roundtrip(est.clone()), est);
1768        assert_eq!(circular_roundtrip(est.clone()), est);
1769    }
1770
1771    #[test]
1772    fn nested_records() {
1773        let policy = r#"
1774            permit(principal, action, resource)
1775            when { context.something1.something2.something3 };
1776        "#;
1777        let cst = parser::text_to_cst::parse_policy(policy)
1778            .unwrap()
1779            .node
1780            .unwrap();
1781        let est: Policy = cst.try_into().unwrap();
1782        let expected_json = json!(
1783            {
1784                "effect": "permit",
1785                "principal": {
1786                    "op": "All",
1787                },
1788                "action": {
1789                    "op": "All",
1790                },
1791                "resource": {
1792                    "op": "All",
1793                },
1794                "conditions": [
1795                    {
1796                        "kind": "when",
1797                        "body": {
1798                            ".": {
1799                                "left": {
1800                                    ".": {
1801                                        "left": {
1802                                            ".": {
1803                                                "left": {
1804                                                    "Var": "context"
1805                                                },
1806                                                "attr": "something1"
1807                                            }
1808                                        },
1809                                        "attr": "something2"
1810                                    }
1811                                },
1812                                "attr": "something3"
1813                            }
1814                        }
1815                    }
1816                ]
1817            }
1818        );
1819        assert_eq!(
1820            serde_json::to_value(&est).unwrap(),
1821            expected_json,
1822            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1823            serde_json::to_string_pretty(&expected_json).unwrap(),
1824            serde_json::to_string_pretty(&est).unwrap()
1825        );
1826        let old_est = est.clone();
1827        let roundtripped = est_roundtrip(est);
1828        assert_eq!(&old_est, &roundtripped);
1829        let est = text_roundtrip(&old_est);
1830        assert_eq!(&old_est, &est);
1831
1832        assert_eq!(ast_roundtrip(est.clone()), est);
1833        assert_eq!(circular_roundtrip(est.clone()), est);
1834    }
1835
1836    #[test]
1837    fn neg_less_and_greater() {
1838        let policy = r#"
1839            permit(principal, action, resource)
1840            when { -3 < 2 && 4 > -(23 - 1) || 0 <= 0 && 7 >= 1};
1841        "#;
1842        let cst = parser::text_to_cst::parse_policy(policy)
1843            .unwrap()
1844            .node
1845            .unwrap();
1846        let est: Policy = cst.try_into().unwrap();
1847        let expected_json = json!(
1848            {
1849                "effect": "permit",
1850                "principal": {
1851                    "op": "All",
1852                },
1853                "action": {
1854                    "op": "All",
1855                },
1856                "resource": {
1857                    "op": "All",
1858                },
1859                "conditions": [
1860                    {
1861                        "kind": "when",
1862                        "body": {
1863                            "||": {
1864                                "left": {
1865                                    "&&": {
1866                                        "left": {
1867                                            "<": {
1868                                                "left": {
1869                                                    "Value": -3
1870                                                },
1871                                                "right": {
1872                                                    "Value": 2
1873                                                }
1874                                            }
1875                                        },
1876                                        "right": {
1877                                            ">": {
1878                                                "left": {
1879                                                    "Value": 4
1880                                                },
1881                                                "right": {
1882                                                    "neg": {
1883                                                        "arg": {
1884                                                            "-": {
1885                                                                "left": {
1886                                                                    "Value": 23
1887                                                                },
1888                                                                "right": {
1889                                                                    "Value": 1
1890                                                                }
1891                                                            }
1892                                                        }
1893                                                    }
1894                                                }
1895                                            }
1896                                        }
1897                                    }
1898                                },
1899                                "right": {
1900                                    "&&": {
1901                                        "left": {
1902                                            "<=": {
1903                                                "left": {
1904                                                    "Value": 0
1905                                                },
1906                                                "right": {
1907                                                    "Value": 0
1908                                                }
1909                                            }
1910                                        },
1911                                        "right": {
1912                                            ">=": {
1913                                                "left": {
1914                                                    "Value": 7
1915                                                },
1916                                                "right": {
1917                                                    "Value": 1
1918                                                }
1919                                            }
1920                                        }
1921                                    }
1922                                }
1923                            }
1924                        }
1925                    }
1926                ]
1927            }
1928        );
1929        assert_eq!(
1930            serde_json::to_value(&est).unwrap(),
1931            expected_json,
1932            "\nExpected:\n{}\n\nActual:\n{}\n\n",
1933            serde_json::to_string_pretty(&expected_json).unwrap(),
1934            serde_json::to_string_pretty(&est).unwrap()
1935        );
1936        let old_est = est.clone();
1937        let roundtripped = est_roundtrip(est);
1938        assert_eq!(&old_est, &roundtripped);
1939        let est = text_roundtrip(&old_est);
1940        assert_eq!(&old_est, &est);
1941
1942        // during the lossy transform to AST, the `>` and `>=` ops are desugared to `<` and
1943        // `<=` ops with the operands flipped
1944        let expected_json_after_roundtrip = json!(
1945            {
1946                "effect": "permit",
1947                "principal": {
1948                    "op": "All",
1949                },
1950                "action": {
1951                    "op": "All",
1952                },
1953                "resource": {
1954                    "op": "All",
1955                },
1956                "conditions": [
1957                    {
1958                        "kind": "when",
1959                        "body": {
1960                            "||": {
1961                                "left": {
1962                                    "&&": {
1963                                        "left": {
1964                                            "<": {
1965                                                "left": {
1966                                                    "Value": -3
1967                                                },
1968                                                "right": {
1969                                                    "Value": 2
1970                                                }
1971                                            }
1972                                        },
1973                                        "right": {
1974                                            "!": {
1975                                                "arg":{
1976                                                    "<=": {
1977                                                        "left": {
1978                                                            "Value": 4
1979                                                        },
1980                                                        "right": {
1981                                                            "neg": {
1982                                                                "arg": {
1983                                                                    "-": {
1984                                                                        "left": {
1985                                                                            "Value": 23
1986                                                                        },
1987                                                                        "right": {
1988                                                                            "Value": 1
1989                                                                        }
1990                                                                    }
1991                                                                }
1992                                                            }
1993                                                        }
1994                                                    }
1995                                                }
1996                                            }
1997                                        }
1998                                    }
1999                                },
2000                                "right": {
2001                                    "&&": {
2002                                        "left": {
2003                                            "<=": {
2004                                                "left": {
2005                                                    "Value": 0
2006                                                },
2007                                                "right": {
2008                                                    "Value": 0
2009                                                }
2010                                            }
2011                                        },
2012                                        "right": {
2013                                            "!": {
2014                                                "arg": {
2015                                                    "<": {
2016                                                        "left": {
2017                                                            "Value": 7
2018                                                        },
2019                                                        "right": {
2020                                                            "Value": 1
2021                                                        }
2022                                                    }
2023                                                }
2024                                            }
2025                                        }
2026                                    }
2027                                }
2028                            }
2029                        }
2030                    }
2031                ]
2032            }
2033        );
2034        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
2035        assert_eq!(
2036            roundtripped,
2037            expected_json_after_roundtrip,
2038            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2039            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2040            serde_json::to_string_pretty(&roundtripped).unwrap()
2041        );
2042        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
2043        assert_eq!(
2044            roundtripped,
2045            expected_json_after_roundtrip,
2046            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2047            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2048            serde_json::to_string_pretty(&roundtripped).unwrap()
2049        );
2050    }
2051
2052    #[test]
2053    fn add_sub_and_mul() {
2054        let policy = r#"
2055            permit(principal, action, resource)
2056            when { 2 + 3 - principal.numFoos * (-10) == 7 };
2057        "#;
2058        let cst = parser::text_to_cst::parse_policy(policy)
2059            .unwrap()
2060            .node
2061            .unwrap();
2062        let est: Policy = cst.try_into().unwrap();
2063        let expected_json = json!(
2064            {
2065                "effect": "permit",
2066                "principal": {
2067                    "op": "All",
2068                },
2069                "action": {
2070                    "op": "All",
2071                },
2072                "resource": {
2073                    "op": "All",
2074                },
2075                "conditions": [
2076                    {
2077                        "kind": "when",
2078                        "body": {
2079                            "==": {
2080                                "left": {
2081                                    "-": {
2082                                        "left": {
2083                                            "+": {
2084                                                "left": {
2085                                                    "Value": 2
2086                                                },
2087                                                "right": {
2088                                                    "Value": 3
2089                                                }
2090                                            }
2091                                        },
2092                                        "right": {
2093                                            "*": {
2094                                                "left": {
2095                                                    ".": {
2096                                                        "left": {
2097                                                            "Var": "principal"
2098                                                        },
2099                                                        "attr": "numFoos"
2100                                                    }
2101                                                },
2102                                                "right": {
2103                                                    "Value": -10
2104                                                }
2105                                            }
2106                                        }
2107                                    }
2108                                },
2109                                "right": {
2110                                    "Value": 7
2111                                }
2112                            }
2113                        }
2114                    }
2115                ]
2116            }
2117        );
2118        assert_eq!(
2119            serde_json::to_value(&est).unwrap(),
2120            expected_json,
2121            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2122            serde_json::to_string_pretty(&expected_json).unwrap(),
2123            serde_json::to_string_pretty(&est).unwrap()
2124        );
2125        let old_est = est.clone();
2126        let roundtripped = est_roundtrip(est);
2127        assert_eq!(&old_est, &roundtripped);
2128        let est = text_roundtrip(&old_est);
2129        assert_eq!(&old_est, &est);
2130
2131        assert_eq!(ast_roundtrip(est.clone()), est);
2132        assert_eq!(circular_roundtrip(est.clone()), est);
2133    }
2134
2135    #[test]
2136    fn contains_all_any() {
2137        let policy = r#"
2138            permit(principal, action, resource)
2139            when {
2140                principal.owners.contains("foo")
2141                && principal.owners.containsAny([1, Linux::Group::"sudoers"])
2142                && [2+3, "spam"].containsAll(resource.foos)
2143            };
2144        "#;
2145        let cst = parser::text_to_cst::parse_policy(policy)
2146            .unwrap()
2147            .node
2148            .unwrap();
2149        let est: Policy = cst.try_into().unwrap();
2150        let expected_json = json!(
2151            {
2152                "effect": "permit",
2153                "principal": {
2154                    "op": "All",
2155                },
2156                "action": {
2157                    "op": "All",
2158                },
2159                "resource": {
2160                    "op": "All",
2161                },
2162                "conditions": [
2163                    {
2164                        "kind": "when",
2165                        "body": {
2166                            "&&": {
2167                                "left": {
2168                                    "&&": {
2169                                        "left": {
2170                                            "contains": {
2171                                                "left": {
2172                                                    ".": {
2173                                                        "left": {
2174                                                            "Var": "principal"
2175                                                        },
2176                                                        "attr": "owners"
2177                                                    }
2178                                                },
2179                                                "right": {
2180                                                    "Value": "foo"
2181                                                }
2182                                            }
2183                                        },
2184                                        "right": {
2185                                            "containsAny": {
2186                                                "left": {
2187                                                    ".": {
2188                                                        "left": {
2189                                                            "Var": "principal"
2190                                                        },
2191                                                        "attr": "owners"
2192                                                    }
2193                                                },
2194                                                "right": {
2195                                                    "Set": [
2196                                                        { "Value": 1 },
2197                                                        { "Value": {
2198                                                            "__entity": {
2199                                                                "type": "Linux::Group",
2200                                                                "id": "sudoers"
2201                                                            }
2202                                                        } }
2203                                                    ]
2204                                                }
2205                                            }
2206                                        }
2207                                    }
2208                                },
2209                                "right": {
2210                                    "containsAll": {
2211                                        "left": {
2212                                            "Set": [
2213                                                { "+": {
2214                                                    "left": {
2215                                                        "Value": 2
2216                                                    },
2217                                                    "right": {
2218                                                        "Value": 3
2219                                                    }
2220                                                } },
2221                                                { "Value": "spam" },
2222                                            ]
2223                                        },
2224                                        "right": {
2225                                            ".": {
2226                                                "left": {
2227                                                    "Var": "resource"
2228                                                },
2229                                                "attr": "foos"
2230                                            }
2231                                        }
2232                                    }
2233                                }
2234                            }
2235                        }
2236                    }
2237                ]
2238            }
2239        );
2240        assert_eq!(
2241            serde_json::to_value(&est).unwrap(),
2242            expected_json,
2243            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2244            serde_json::to_string_pretty(&expected_json).unwrap(),
2245            serde_json::to_string_pretty(&est).unwrap()
2246        );
2247        let old_est = est.clone();
2248        let roundtripped = est_roundtrip(est);
2249        assert_eq!(&old_est, &roundtripped);
2250        let est = text_roundtrip(&old_est);
2251        assert_eq!(&old_est, &est);
2252
2253        assert_eq!(ast_roundtrip(est.clone()), est);
2254        assert_eq!(circular_roundtrip(est.clone()), est);
2255    }
2256
2257    #[test]
2258    fn has_like_and_if() {
2259        let policy = r#"
2260            permit(principal, action, resource)
2261            when {
2262                if context.foo
2263                then principal has "-78/%$!"
2264                else resource.email like "*@amazon.com"
2265            };
2266        "#;
2267        let cst = parser::text_to_cst::parse_policy(policy)
2268            .unwrap()
2269            .node
2270            .unwrap();
2271        let est: Policy = cst.try_into().unwrap();
2272        let expected_json = json!(
2273            {
2274                "effect": "permit",
2275                "principal": {
2276                    "op": "All",
2277                },
2278                "action": {
2279                    "op": "All",
2280                },
2281                "resource": {
2282                    "op": "All",
2283                },
2284                "conditions": [
2285                    {
2286                        "kind": "when",
2287                        "body": {
2288                            "if-then-else": {
2289                                "if": {
2290                                    ".": {
2291                                        "left": {
2292                                            "Var": "context"
2293                                        },
2294                                        "attr": "foo"
2295                                    }
2296                                },
2297                                "then": {
2298                                    "has": {
2299                                        "left": {
2300                                            "Var": "principal"
2301                                        },
2302                                        "attr": "-78/%$!"
2303                                    }
2304                                },
2305                                "else": {
2306                                    "like": {
2307                                        "left": {
2308                                            ".": {
2309                                                "left": {
2310                                                    "Var": "resource"
2311                                                },
2312                                                "attr": "email"
2313                                            }
2314                                        },
2315                                        "pattern": "*@amazon.com"
2316                                    }
2317                                }
2318                            }
2319                        }
2320                    }
2321                ]
2322            }
2323        );
2324        assert_eq!(
2325            serde_json::to_value(&est).unwrap(),
2326            expected_json,
2327            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2328            serde_json::to_string_pretty(&expected_json).unwrap(),
2329            serde_json::to_string_pretty(&est).unwrap()
2330        );
2331        let old_est = est.clone();
2332        let roundtripped = est_roundtrip(est);
2333        assert_eq!(&old_est, &roundtripped);
2334        let est = text_roundtrip(&old_est);
2335        assert_eq!(&old_est, &est);
2336
2337        assert_eq!(ast_roundtrip(est.clone()), est);
2338        assert_eq!(circular_roundtrip(est.clone()), est);
2339    }
2340
2341    #[test]
2342    fn decimal() {
2343        let policy = r#"
2344            permit(principal, action, resource)
2345            when {
2346                context.confidenceScore.greaterThan(decimal("10.0"))
2347            };
2348        "#;
2349        let cst = parser::text_to_cst::parse_policy(policy)
2350            .unwrap()
2351            .node
2352            .unwrap();
2353        let est: Policy = cst.try_into().unwrap();
2354        let expected_json = json!(
2355            {
2356                "effect": "permit",
2357                "principal": {
2358                    "op": "All",
2359                },
2360                "action": {
2361                    "op": "All",
2362                },
2363                "resource": {
2364                    "op": "All",
2365                },
2366                "conditions": [
2367                    {
2368                        "kind": "when",
2369                        "body": {
2370                            "greaterThan": [
2371                                {
2372                                    ".": {
2373                                        "left": {
2374                                            "Var": "context"
2375                                        },
2376                                        "attr": "confidenceScore"
2377                                    }
2378                                },
2379                                {
2380                                    "decimal": [
2381                                        {
2382                                            "Value": "10.0"
2383                                        }
2384                                    ]
2385                                }
2386                            ]
2387                        }
2388                    }
2389                ]
2390            }
2391        );
2392        assert_eq!(
2393            serde_json::to_value(&est).unwrap(),
2394            expected_json,
2395            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2396            serde_json::to_string_pretty(&expected_json).unwrap(),
2397            serde_json::to_string_pretty(&est).unwrap()
2398        );
2399        let old_est = est.clone();
2400        let roundtripped = est_roundtrip(est);
2401        assert_eq!(&old_est, &roundtripped);
2402        let est = text_roundtrip(&old_est);
2403        assert_eq!(&old_est, &est);
2404
2405        assert_eq!(ast_roundtrip(est.clone()), est);
2406        assert_eq!(circular_roundtrip(est.clone()), est);
2407    }
2408
2409    #[test]
2410    fn ip() {
2411        let policy = r#"
2412            permit(principal, action, resource)
2413            when {
2414                context.source_ip.isInRange(ip("222.222.222.0/24"))
2415            };
2416        "#;
2417        let cst = parser::text_to_cst::parse_policy(policy)
2418            .unwrap()
2419            .node
2420            .unwrap();
2421        let est: Policy = cst.try_into().unwrap();
2422        let expected_json = json!(
2423            {
2424                "effect": "permit",
2425                "principal": {
2426                    "op": "All",
2427                },
2428                "action": {
2429                    "op": "All",
2430                },
2431                "resource": {
2432                    "op": "All",
2433                },
2434                "conditions": [
2435                    {
2436                        "kind": "when",
2437                        "body": {
2438                            "isInRange": [
2439                                {
2440                                    ".": {
2441                                        "left": {
2442                                            "Var": "context"
2443                                        },
2444                                        "attr": "source_ip"
2445                                    }
2446                                },
2447                                {
2448                                    "ip": [
2449                                        {
2450                                            "Value": "222.222.222.0/24"
2451                                        }
2452                                    ]
2453                                }
2454                            ]
2455                        }
2456                    }
2457                ]
2458            }
2459        );
2460        assert_eq!(
2461            serde_json::to_value(&est).unwrap(),
2462            expected_json,
2463            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2464            serde_json::to_string_pretty(&expected_json).unwrap(),
2465            serde_json::to_string_pretty(&est).unwrap()
2466        );
2467        let old_est = est.clone();
2468        let roundtripped = est_roundtrip(est);
2469        assert_eq!(&old_est, &roundtripped);
2470        let est = text_roundtrip(&old_est);
2471        assert_eq!(&old_est, &est);
2472
2473        assert_eq!(ast_roundtrip(est.clone()), est);
2474        assert_eq!(circular_roundtrip(est.clone()), est);
2475    }
2476
2477    #[test]
2478    fn multiple_clauses() {
2479        let policy = r#"
2480            permit(principal, action, resource)
2481            when { context.foo }
2482            unless { context.bar }
2483            when { principal.eggs };
2484        "#;
2485        let cst = parser::text_to_cst::parse_policy(policy)
2486            .unwrap()
2487            .node
2488            .unwrap();
2489        let est: Policy = cst.try_into().unwrap();
2490        let expected_json = json!(
2491            {
2492                "effect": "permit",
2493                "principal": {
2494                    "op": "All",
2495                },
2496                "action": {
2497                    "op": "All",
2498                },
2499                "resource": {
2500                    "op": "All",
2501                },
2502                "conditions": [
2503                    {
2504                        "kind": "when",
2505                        "body": {
2506                            ".": {
2507                                "left": {
2508                                    "Var": "context"
2509                                },
2510                                "attr": "foo"
2511                            }
2512                        }
2513                    },
2514                    {
2515                        "kind": "unless",
2516                        "body": {
2517                            ".": {
2518                                "left": {
2519                                    "Var": "context"
2520                                },
2521                                "attr": "bar"
2522                            }
2523                        }
2524                    },
2525                    {
2526                        "kind": "when",
2527                        "body": {
2528                            ".": {
2529                                "left": {
2530                                    "Var": "principal"
2531                                },
2532                                "attr": "eggs"
2533                            }
2534                        }
2535                    }
2536                ]
2537            }
2538        );
2539        assert_eq!(
2540            serde_json::to_value(&est).unwrap(),
2541            expected_json,
2542            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2543            serde_json::to_string_pretty(&expected_json).unwrap(),
2544            serde_json::to_string_pretty(&est).unwrap()
2545        );
2546        let old_est = est.clone();
2547        let roundtripped = est_roundtrip(est);
2548        assert_eq!(&old_est, &roundtripped);
2549        let est = text_roundtrip(&old_est);
2550        assert_eq!(&old_est, &est);
2551
2552        // during the lossy transform to AST, the multiple clauses on this policy are
2553        // combined into a single `when` clause
2554        let expected_json_after_roundtrip = json!(
2555            {
2556                "effect": "permit",
2557                "principal": {
2558                    "op": "All",
2559                },
2560                "action": {
2561                    "op": "All",
2562                },
2563                "resource": {
2564                    "op": "All",
2565                },
2566                "conditions": [
2567                    {
2568                        "kind": "when",
2569                        "body": {
2570                            "&&": {
2571                                "left": {
2572                                    "&&": {
2573                                        "left": {
2574                                            ".": {
2575                                                "left": {
2576                                                    "Var": "context"
2577                                                },
2578                                                "attr": "foo"
2579                                            }
2580                                        },
2581                                        "right": {
2582                                            "!": {
2583                                                "arg": {
2584                                                    ".": {
2585                                                        "left": {
2586                                                            "Var": "context"
2587                                                        },
2588                                                        "attr": "bar"
2589                                                    }
2590                                                }
2591                                            }
2592                                        }
2593                                    }
2594                                },
2595                                "right": {
2596                                    ".": {
2597                                        "left": {
2598                                            "Var": "principal"
2599                                        },
2600                                        "attr": "eggs"
2601                                    }
2602                                }
2603                            }
2604                        }
2605                    }
2606                ]
2607            }
2608        );
2609        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
2610        assert_eq!(
2611            roundtripped,
2612            expected_json_after_roundtrip,
2613            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2614            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2615            serde_json::to_string_pretty(&roundtripped).unwrap()
2616        );
2617        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
2618        assert_eq!(
2619            roundtripped,
2620            expected_json_after_roundtrip,
2621            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2622            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2623            serde_json::to_string_pretty(&roundtripped).unwrap()
2624        );
2625    }
2626
2627    #[test]
2628    fn instantiate() {
2629        let template = r#"
2630            permit(
2631                principal == ?principal,
2632                action == Action::"view",
2633                resource in ?resource
2634            ) when {
2635                principal in resource.owners
2636            };
2637        "#;
2638        let cst = parser::text_to_cst::parse_policy(template)
2639            .unwrap()
2640            .node
2641            .unwrap();
2642        let est: Policy = cst.try_into().unwrap();
2643        let err = est
2644            .clone()
2645            .link(&HashMap::from_iter([]))
2646            .expect_err("didn't fill all the slots");
2647        assert_eq!(
2648            err,
2649            InstantiationError::MissedSlot {
2650                slot: ast::SlotId::principal()
2651            }
2652        );
2653        let err = est
2654            .clone()
2655            .link(&HashMap::from_iter([(
2656                ast::SlotId::principal(),
2657                EntityUidJson::new("XYZCorp::User", "12UA45"),
2658            )]))
2659            .expect_err("didn't fill all the slots");
2660        assert_eq!(
2661            err,
2662            InstantiationError::MissedSlot {
2663                slot: ast::SlotId::resource()
2664            }
2665        );
2666        let linked = est
2667            .link(&HashMap::from_iter([
2668                (
2669                    ast::SlotId::principal(),
2670                    EntityUidJson::new("XYZCorp::User", "12UA45"),
2671                ),
2672                (ast::SlotId::resource(), EntityUidJson::new("Folder", "abc")),
2673            ]))
2674            .expect("did fill all the slots");
2675        let expected_json = json!(
2676            {
2677                "effect": "permit",
2678                "principal": {
2679                    "op": "==",
2680                    "entity": { "type": "XYZCorp::User", "id": "12UA45" },
2681                },
2682                "action": {
2683                    "op": "==",
2684                    "entity": { "type": "Action", "id": "view" },
2685                },
2686                "resource": {
2687                    "op": "in",
2688                    "entity": { "type": "Folder", "id": "abc" },
2689                },
2690                "conditions": [
2691                    {
2692                        "kind": "when",
2693                        "body": {
2694                            "in": {
2695                                "left": {
2696                                    "Var": "principal"
2697                                },
2698                                "right": {
2699                                    ".": {
2700                                        "left": {
2701                                            "Var": "resource"
2702                                        },
2703                                        "attr": "owners"
2704                                    }
2705                                }
2706                            }
2707                        }
2708                    }
2709                ],
2710            }
2711        );
2712        let linked_json = serde_json::to_value(linked).unwrap();
2713        assert_eq!(
2714            linked_json,
2715            expected_json,
2716            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2717            serde_json::to_string_pretty(&expected_json).unwrap(),
2718            serde_json::to_string_pretty(&linked_json).unwrap(),
2719        );
2720    }
2721
2722    #[test]
2723    fn eid_with_nulls() {
2724        let policy = r#"
2725            permit(
2726                principal == a::"\0\0\0J",
2727                action == Action::"view",
2728                resource
2729            );
2730        "#;
2731        let cst = parser::text_to_cst::parse_policy(policy)
2732            .unwrap()
2733            .node
2734            .unwrap();
2735        let est: Policy = cst.try_into().unwrap();
2736        let expected_json = json!(
2737            {
2738                "effect": "permit",
2739                "principal": {
2740                    "op": "==",
2741                    "entity": {
2742                        "type": "a",
2743                        "id": "\0\0\0J",
2744                    }
2745                },
2746                "action": {
2747                    "op": "==",
2748                    "entity": {
2749                        "type": "Action",
2750                        "id": "view",
2751                    }
2752                },
2753                "resource": {
2754                    "op": "All"
2755                },
2756                "conditions": []
2757            }
2758        );
2759        assert_eq!(
2760            serde_json::to_value(&est).unwrap(),
2761            expected_json,
2762            "\nExpected:\n{}\n\nActual:\n{}\n\n",
2763            serde_json::to_string_pretty(&expected_json).unwrap(),
2764            serde_json::to_string_pretty(&est).unwrap()
2765        );
2766        let old_est = est.clone();
2767        let roundtripped = est_roundtrip(est);
2768        assert_eq!(&old_est, &roundtripped);
2769        let est = text_roundtrip(&old_est);
2770        assert_eq!(&old_est, &est);
2771
2772        // during the lossy transform to AST, the only difference for this policy is that
2773        // a `when { true }` is added
2774        let expected_json_after_roundtrip = json!(
2775            {
2776                "effect": "permit",
2777                "principal": {
2778                    "op": "==",
2779                    "entity": {
2780                        "type": "a",
2781                        "id": "\0\0\0J",
2782                    }
2783                },
2784                "action": {
2785                    "op": "==",
2786                    "entity": {
2787                        "type": "Action",
2788                        "id": "view",
2789                    }
2790                },
2791                "resource": {
2792                    "op": "All"
2793                },
2794                "conditions": [
2795                    {
2796                        "kind": "when",
2797                        "body": {
2798                            "Value": true
2799                        }
2800                    }
2801                ]
2802            }
2803        );
2804        let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
2805        assert_eq!(
2806            roundtripped,
2807            expected_json_after_roundtrip,
2808            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2809            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2810            serde_json::to_string_pretty(&roundtripped).unwrap()
2811        );
2812        let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
2813        assert_eq!(
2814            roundtripped,
2815            expected_json_after_roundtrip,
2816            "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
2817            serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
2818            serde_json::to_string_pretty(&roundtripped).unwrap()
2819        );
2820    }
2821
2822    #[test]
2823    fn invalid_json_ests() {
2824        let bad = json!(
2825            {
2826                "effect": "Permit",
2827                "principal": {
2828                    "op": "All"
2829                },
2830                "action": {
2831                    "op": "All"
2832                },
2833                "resource": {
2834                    "op": "All"
2835                },
2836                "conditions": []
2837            }
2838        );
2839        let est: Result<Policy, _> = serde_json::from_value(bad);
2840        assert_matches!(est, Err(_)); // `Permit` cannot be capitalized
2841
2842        let bad = json!(
2843            {
2844                "effect": "permit",
2845                "principal": {
2846                    "op": "All"
2847                },
2848                "action": {
2849                    "op": "All"
2850                },
2851                "resource": {
2852                    "op": "All"
2853                },
2854                "conditions": [
2855                    {
2856                        "kind": "when",
2857                        "body": {}
2858                    }
2859                ]
2860            }
2861        );
2862        let est: Policy = serde_json::from_value(bad).unwrap();
2863        let ast: Result<ast::Policy, _> = est.try_into_ast_policy(None);
2864        assert_matches!(ast, Err(FromJsonError::MissingOperator));
2865
2866        let bad = json!(
2867            {
2868                "effect": "permit",
2869                "principal": {
2870                    "op": "All"
2871                },
2872                "action": {
2873                    "op": "All"
2874                },
2875                "resource": {
2876                    "op": "All"
2877                },
2878                "conditions": [
2879                    {
2880                        "kind": "when",
2881                        "body": {
2882                            "+": {
2883                                "left": {
2884                                    "Value": 3
2885                                },
2886                                "right": {
2887                                    "Value": 4
2888                                }
2889                            },
2890                            "-": {
2891                                "left": {
2892                                    "Value": 2
2893                                },
2894                                "right": {
2895                                    "Value": 8
2896                                }
2897                            }
2898                        }
2899                    }
2900                ]
2901            }
2902        );
2903        let est: Result<Policy, _> = serde_json::from_value(bad);
2904        assert_matches!(est, Err(_)); // two expressions in body, not connected
2905
2906        let template = json!(
2907            {
2908                "effect": "permit",
2909                "principal": {
2910                    "op": "==",
2911                    "slot": "?principal",
2912                },
2913                "action": {
2914                    "op": "All"
2915                },
2916                "resource": {
2917                    "op": "All"
2918                },
2919                "conditions": []
2920            }
2921        );
2922        let est: Policy = serde_json::from_value(template).unwrap();
2923        let ast: Result<ast::Policy, _> = est.try_into_ast_policy(None);
2924        assert_matches!(
2925            ast,
2926            Err(FromJsonError::TemplateToPolicy(
2927                parse_errors::ExpectedStaticPolicy { slot }
2928            )) => assert_eq!(slot.id, ast::SlotId::principal())
2929        );
2930    }
2931
2932    #[test]
2933    fn record_duplicate_key() {
2934        let bad = r#"
2935            {
2936                "effect": "permit",
2937                "principal": { "op": "All" },
2938                "action": { "op": "All" },
2939                "resource": { "op": "All" },
2940                "conditions": [
2941                    {
2942                        "kind": "when",
2943                        "body": {
2944                            "Record": {
2945                                "foo": {"Value": 0},
2946                                "foo": {"Value": 1}
2947                            }
2948                        }
2949                    }
2950                ]
2951            }
2952        "#;
2953        let est: Result<Policy, _> = serde_json::from_str(bad);
2954        assert_matches!(est, Err(_));
2955    }
2956
2957    #[test]
2958    fn value_record_duplicate_key() {
2959        let bad = r#"
2960            {
2961                "effect": "permit",
2962                "principal": { "op": "All" },
2963                "action": { "op": "All" },
2964                "resource": { "op": "All" },
2965                "conditions": [
2966                    {
2967                        "kind": "when",
2968                        "body": {
2969                            "Value": {
2970                                "foo": 0,
2971                                "foo": 1
2972                            }
2973                        }
2974                    }
2975                ]
2976            }
2977        "#;
2978        let est: Result<Policy, _> = serde_json::from_str(bad);
2979        assert_matches!(est, Err(_));
2980    }
2981
2982    #[test]
2983    fn duplicate_annotations() {
2984        let bad = r#"
2985            {
2986                "effect": "permit",
2987                "principal": { "op": "All" },
2988                "action": { "op": "All" },
2989                "resource": { "op": "All" },
2990                "conditions": [],
2991                "annotations": {
2992                    "foo": "bar",
2993                    "foo": "baz"
2994                }
2995            }
2996        "#;
2997        let est: Result<Policy, _> = serde_json::from_str(bad);
2998        assert_matches!(est, Err(_));
2999    }
3000
3001    #[test]
3002    fn extension_duplicate_keys() {
3003        let bad = r#"
3004            {
3005                "effect": "permit",
3006                "principal": { "op": "All" },
3007                "action": { "op": "All" },
3008                "resource": { "op": "All" },
3009                "conditions": [
3010                    {
3011                        "kind": "when",
3012                        "body": {
3013                            "ip": [
3014                                {
3015                                    "Value": "222.222.222.0/24"
3016                                }
3017                            ],
3018                            "ip": [
3019                                {
3020                                    "Value": "111.111.111.0/24"
3021                                }
3022                            ]
3023                        }
3024                    }
3025                ]
3026            }
3027        "#;
3028        let est: Result<Policy, _> = serde_json::from_str(bad);
3029        assert_matches!(est, Err(_));
3030    }
3031
3032    mod is_type {
3033        use cool_asserts::assert_panics;
3034
3035        use super::*;
3036
3037        #[test]
3038        fn principal() {
3039            let policy = r"permit(principal is User, action, resource);";
3040            let cst = parser::text_to_cst::parse_policy(policy)
3041                .unwrap()
3042                .node
3043                .unwrap();
3044            let est: Policy = cst.try_into().unwrap();
3045            let expected_json = json!(
3046                {
3047                    "effect": "permit",
3048                    "principal": {
3049                        "op": "is",
3050                        "entity_type": "User"
3051                    },
3052                    "action": {
3053                        "op": "All",
3054                    },
3055                    "resource": {
3056                        "op": "All",
3057                    },
3058                    "conditions": [ ]
3059                }
3060            );
3061            assert_eq!(
3062                serde_json::to_value(&est).unwrap(),
3063                expected_json,
3064                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3065                serde_json::to_string_pretty(&expected_json).unwrap(),
3066                serde_json::to_string_pretty(&est).unwrap()
3067            );
3068            let old_est = est.clone();
3069            let roundtripped = est_roundtrip(est);
3070            assert_eq!(&old_est, &roundtripped);
3071            let est = text_roundtrip(&old_est);
3072            assert_eq!(&old_est, &est);
3073
3074            let expected_json_after_roundtrip = json!(
3075                {
3076                    "effect": "permit",
3077                    "principal": {
3078                        "op": "is",
3079                        "entity_type": "User"
3080                    },
3081                    "action": {
3082                        "op": "All",
3083                    },
3084                    "resource": {
3085                        "op": "All",
3086                    },
3087                    "conditions": [
3088                        {
3089                            "kind": "when",
3090                            "body": {
3091                                "Value": true
3092                            }
3093                        }
3094                    ],
3095                }
3096            );
3097            let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3098            assert_eq!(
3099                roundtripped,
3100                expected_json_after_roundtrip,
3101                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3102                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3103                serde_json::to_string_pretty(&roundtripped).unwrap()
3104            );
3105            let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3106            assert_eq!(
3107                roundtripped,
3108                expected_json_after_roundtrip,
3109                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3110                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3111                serde_json::to_string_pretty(&roundtripped).unwrap()
3112            );
3113        }
3114
3115        #[test]
3116        fn resource() {
3117            let policy = r"permit(principal, action, resource is Log);";
3118            let cst = parser::text_to_cst::parse_policy(policy)
3119                .unwrap()
3120                .node
3121                .unwrap();
3122            let est: Policy = cst.try_into().unwrap();
3123            let expected_json = json!(
3124                {
3125                    "effect": "permit",
3126                    "principal": {
3127                        "op": "All",
3128                    },
3129                    "action": {
3130                        "op": "All",
3131                    },
3132                    "resource": {
3133                        "op": "is",
3134                        "entity_type": "Log"
3135                    },
3136                    "conditions": [ ]
3137                }
3138            );
3139            assert_eq!(
3140                serde_json::to_value(&est).unwrap(),
3141                expected_json,
3142                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3143                serde_json::to_string_pretty(&expected_json).unwrap(),
3144                serde_json::to_string_pretty(&est).unwrap()
3145            );
3146            let old_est = est.clone();
3147            let roundtripped = est_roundtrip(est);
3148            assert_eq!(&old_est, &roundtripped);
3149            let est = text_roundtrip(&old_est);
3150            assert_eq!(&old_est, &est);
3151
3152            let expected_json_after_roundtrip = json!(
3153                {
3154                    "effect": "permit",
3155                    "principal": {
3156                        "op": "All",
3157                    },
3158                    "action": {
3159                        "op": "All",
3160                    },
3161                    "resource": {
3162                        "op": "is",
3163                        "entity_type": "Log"
3164                    },
3165                    "conditions": [
3166                        {
3167                            "kind": "when",
3168                            "body": {
3169                                "Value": true
3170                            }
3171                        }
3172                    ],
3173                }
3174            );
3175            let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3176            assert_eq!(
3177                roundtripped,
3178                expected_json_after_roundtrip,
3179                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3180                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3181                serde_json::to_string_pretty(&roundtripped).unwrap()
3182            );
3183            let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3184            assert_eq!(
3185                roundtripped,
3186                expected_json_after_roundtrip,
3187                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3188                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3189                serde_json::to_string_pretty(&roundtripped).unwrap()
3190            );
3191        }
3192
3193        #[test]
3194        fn principal_in_entity() {
3195            let policy = r#"permit(principal is User in Group::"admin", action, resource);"#;
3196            let cst = parser::text_to_cst::parse_policy(policy)
3197                .unwrap()
3198                .node
3199                .unwrap();
3200            let est: Policy = cst.try_into().unwrap();
3201            let expected_json = json!(
3202                {
3203                    "effect": "permit",
3204                    "principal": {
3205                        "op": "is",
3206                        "entity_type": "User",
3207                        "in": { "entity": { "type": "Group", "id": "admin" } }
3208                    },
3209                    "action": {
3210                        "op": "All",
3211                    },
3212                    "resource": {
3213                        "op": "All",
3214                    },
3215                    "conditions": [ ]
3216                }
3217            );
3218            assert_eq!(
3219                serde_json::to_value(&est).unwrap(),
3220                expected_json,
3221                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3222                serde_json::to_string_pretty(&expected_json).unwrap(),
3223                serde_json::to_string_pretty(&est).unwrap()
3224            );
3225            let old_est = est.clone();
3226            let roundtripped = est_roundtrip(est);
3227            assert_eq!(&old_est, &roundtripped);
3228            let est = text_roundtrip(&old_est);
3229            assert_eq!(&old_est, &est);
3230
3231            let expected_json_after_roundtrip = json!(
3232                {
3233                    "effect": "permit",
3234                    "principal": {
3235                        "op": "is",
3236                        "entity_type": "User",
3237                        "in": { "entity": { "type": "Group", "id": "admin" } }
3238                    },
3239                    "action": {
3240                        "op": "All",
3241                    },
3242                    "resource": {
3243                        "op": "All",
3244                    },
3245                    "conditions": [
3246                        {
3247                            "kind": "when",
3248                            "body": {
3249                                "Value": true
3250                            }
3251                        }
3252                    ],
3253                }
3254            );
3255            let roundtripped = serde_json::to_value(ast_roundtrip(est.clone())).unwrap();
3256            assert_eq!(
3257                roundtripped,
3258                expected_json_after_roundtrip,
3259                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3260                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3261                serde_json::to_string_pretty(&roundtripped).unwrap()
3262            );
3263            let roundtripped = serde_json::to_value(circular_roundtrip(est)).unwrap();
3264            assert_eq!(
3265                roundtripped,
3266                expected_json_after_roundtrip,
3267                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3268                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3269                serde_json::to_string_pretty(&roundtripped).unwrap()
3270            );
3271        }
3272
3273        #[test]
3274        fn principal_in_slot() {
3275            let policy = r#"permit(principal is User in ?principal, action, resource);"#;
3276            let cst = parser::text_to_cst::parse_policy(policy)
3277                .unwrap()
3278                .node
3279                .unwrap();
3280            let est: Policy = cst.try_into().unwrap();
3281            let expected_json = json!(
3282                {
3283                    "effect": "permit",
3284                    "principal": {
3285                        "op": "is",
3286                        "entity_type": "User",
3287                        "in": { "slot": "?principal" }
3288                    },
3289                    "action": {
3290                        "op": "All",
3291                    },
3292                    "resource": {
3293                        "op": "All",
3294                    },
3295                    "conditions": [ ]
3296                }
3297            );
3298            assert_eq!(
3299                serde_json::to_value(&est).unwrap(),
3300                expected_json,
3301                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3302                serde_json::to_string_pretty(&expected_json).unwrap(),
3303                serde_json::to_string_pretty(&est).unwrap()
3304            );
3305            let old_est = est.clone();
3306            let roundtripped = est_roundtrip(est);
3307            assert_eq!(&old_est, &roundtripped);
3308            let est = text_roundtrip(&old_est);
3309            assert_eq!(&old_est, &est);
3310
3311            let expected_json_after_roundtrip = json!(
3312                {
3313                    "effect": "permit",
3314                    "principal": {
3315                        "op": "is",
3316                        "entity_type": "User",
3317                        "in": { "slot": "?principal" }
3318                    },
3319                    "action": {
3320                        "op": "All",
3321                    },
3322                    "resource": {
3323                        "op": "All",
3324                    },
3325                    "conditions": [
3326                        {
3327                            "kind": "when",
3328                            "body": {
3329                                "Value": true
3330                            }
3331                        }
3332                    ],
3333                }
3334            );
3335            let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
3336            assert_eq!(
3337                roundtripped,
3338                expected_json_after_roundtrip,
3339                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3340                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3341                serde_json::to_string_pretty(&roundtripped).unwrap()
3342            );
3343            let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
3344            assert_eq!(
3345                roundtripped,
3346                expected_json_after_roundtrip,
3347                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3348                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3349                serde_json::to_string_pretty(&roundtripped).unwrap()
3350            );
3351        }
3352
3353        #[test]
3354        fn condition() {
3355            let policy = r#"
3356            permit(principal, action, resource)
3357            when { principal is User };"#;
3358            let cst = parser::text_to_cst::parse_policy(policy)
3359                .unwrap()
3360                .node
3361                .unwrap();
3362            let est: Policy = cst.try_into().unwrap();
3363            let expected_json = json!(
3364                {
3365                    "effect": "permit",
3366                    "principal": {
3367                        "op": "All",
3368                    },
3369                    "action": {
3370                        "op": "All",
3371                    },
3372                    "resource": {
3373                        "op": "All",
3374                    },
3375                    "conditions": [
3376                        {
3377                            "kind": "when",
3378                            "body": {
3379                                "is": {
3380                                    "left": {
3381                                        "Var": "principal"
3382                                    },
3383                                    "entity_type": "User",
3384                                }
3385                            }
3386                        }
3387                    ]
3388                }
3389            );
3390            assert_eq!(
3391                serde_json::to_value(&est).unwrap(),
3392                expected_json,
3393                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3394                serde_json::to_string_pretty(&expected_json).unwrap(),
3395                serde_json::to_string_pretty(&est).unwrap()
3396            );
3397            let old_est = est.clone();
3398            let roundtripped = est_roundtrip(est);
3399            assert_eq!(&old_est, &roundtripped);
3400            let est = text_roundtrip(&old_est);
3401            assert_eq!(&old_est, &est);
3402
3403            assert_eq!(ast_roundtrip(est.clone()), est);
3404            assert_eq!(circular_roundtrip(est.clone()), est);
3405        }
3406
3407        #[test]
3408        fn condition_in() {
3409            let policy = r#"
3410            permit(principal, action, resource)
3411            when { principal is User in 1 };"#;
3412            let cst = parser::text_to_cst::parse_policy(policy)
3413                .unwrap()
3414                .node
3415                .unwrap();
3416            let est: Policy = cst.try_into().unwrap();
3417            let expected_json = json!(
3418                {
3419                    "effect": "permit",
3420                    "principal": {
3421                        "op": "All",
3422                    },
3423                    "action": {
3424                        "op": "All",
3425                    },
3426                    "resource": {
3427                        "op": "All",
3428                    },
3429                    "conditions": [
3430                        {
3431                            "kind": "when",
3432                            "body": {
3433                                "is": {
3434                                    "left": { "Var": "principal" },
3435                                    "entity_type": "User",
3436                                    "in": {"Value": 1}
3437                                }
3438                            }
3439                        }
3440                    ]
3441                }
3442            );
3443            assert_eq!(
3444                serde_json::to_value(&est).unwrap(),
3445                expected_json,
3446                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3447                serde_json::to_string_pretty(&expected_json).unwrap(),
3448                serde_json::to_string_pretty(&est).unwrap()
3449            );
3450            let old_est = est.clone();
3451            let roundtripped = est_roundtrip(est);
3452            assert_eq!(&old_est, &roundtripped);
3453            let est = text_roundtrip(&old_est);
3454            assert_eq!(&old_est, &est);
3455
3456            let expected_json_after_roundtrip = json!(
3457                {
3458                    "effect": "permit",
3459                    "principal": {
3460                        "op": "All",
3461                    },
3462                    "action": {
3463                        "op": "All",
3464                    },
3465                    "resource": {
3466                        "op": "All",
3467                    },
3468                    "conditions": [
3469                        {
3470                            "kind": "when",
3471                            "body": {
3472                                "&&": {
3473                                    "left": {
3474                                        "is": {
3475                                            "left": { "Var": "principal" },
3476                                            "entity_type": "User",
3477                                        }
3478                                    },
3479                                    "right": {
3480                                        "in": {
3481                                            "left": { "Var": "principal" },
3482                                            "right": { "Value": 1}
3483                                        }
3484                                    }
3485                                }
3486                            }
3487                        }
3488                    ],
3489                }
3490            );
3491            let roundtripped = serde_json::to_value(ast_roundtrip_template(est.clone())).unwrap();
3492            assert_eq!(
3493                roundtripped,
3494                expected_json_after_roundtrip,
3495                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3496                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3497                serde_json::to_string_pretty(&roundtripped).unwrap()
3498            );
3499            let roundtripped = serde_json::to_value(circular_roundtrip_template(est)).unwrap();
3500            assert_eq!(
3501                roundtripped,
3502                expected_json_after_roundtrip,
3503                "\nExpected after roundtrip:\n{}\n\nActual after roundtrip:\n{}\n\n",
3504                serde_json::to_string_pretty(&expected_json_after_roundtrip).unwrap(),
3505                serde_json::to_string_pretty(&roundtripped).unwrap()
3506            );
3507        }
3508
3509        #[test]
3510        fn invalid() {
3511            let bad = json!(
3512                {
3513                    "effect": "permit",
3514                    "principal": {
3515                        "op": "is"
3516                    },
3517                    "action": {
3518                        "op": "All"
3519                    },
3520                    "resource": {
3521                        "op": "All"
3522                    },
3523                    "conditions": []
3524                }
3525            );
3526            assert_panics!(
3527                serde_json::from_value::<Policy>(bad).unwrap(),
3528                includes("missing field `entity_type`"),
3529            );
3530
3531            let bad = json!(
3532                {
3533                    "effect": "permit",
3534                    "principal": {
3535                        "op": "is",
3536                        "entity_type": "!"
3537                    },
3538                    "action": {
3539                        "op": "All"
3540                    },
3541                    "resource": {
3542                        "op": "All"
3543                    },
3544                    "conditions": []
3545                }
3546            );
3547            assert_matches!(
3548                serde_json::from_value::<Policy>(bad)
3549                    .unwrap()
3550                    .try_into_ast_policy(None),
3551                Err(FromJsonError::InvalidEntityType(_)),
3552            );
3553
3554            let bad = json!(
3555                {
3556                    "effect": "permit",
3557                    "principal": {
3558                        "op": "is",
3559                        "entity_type": "User",
3560                        "==": {"entity": { "type": "User", "id": "alice"}}
3561                    },
3562                    "action": {
3563                        "op": "All"
3564                    },
3565                    "resource": {
3566                        "op": "All"
3567                    },
3568                    "conditions": []
3569                }
3570            );
3571            assert_panics!(
3572                serde_json::from_value::<Policy>(bad).unwrap(),
3573                includes("unknown field `==`, expected `entity_type` or `in`"),
3574            );
3575
3576            let bad = json!(
3577                {
3578                    "effect": "permit",
3579                    "principal": {
3580                        "op": "All",
3581                    },
3582                    "action": {
3583                        "op": "is",
3584                        "entity_type": "Action"
3585                    },
3586                    "resource": {
3587                        "op": "All"
3588                    },
3589                    "conditions": []
3590                }
3591            );
3592            assert_panics!(
3593                serde_json::from_value::<Policy>(bad).unwrap(),
3594                includes("unknown variant `is`, expected one of `All`, `==`, `in`"),
3595            );
3596        }
3597
3598        #[test]
3599        fn instantiate() {
3600            let template = r#"
3601            permit(
3602                principal is User in ?principal,
3603                action,
3604                resource is Doc in ?resource
3605            );
3606        "#;
3607            let cst = parser::text_to_cst::parse_policy(template)
3608                .unwrap()
3609                .node
3610                .unwrap();
3611            let est: Policy = cst.try_into().unwrap();
3612            let err = est.clone().link(&HashMap::from_iter([]));
3613            assert_eq!(
3614                err,
3615                Err(InstantiationError::MissedSlot {
3616                    slot: ast::SlotId::principal()
3617                })
3618            );
3619            let err = est.clone().link(&HashMap::from_iter([(
3620                ast::SlotId::principal(),
3621                EntityUidJson::new("User", "alice"),
3622            )]));
3623            assert_eq!(
3624                err,
3625                Err(InstantiationError::MissedSlot {
3626                    slot: ast::SlotId::resource()
3627                })
3628            );
3629            let linked = est
3630                .link(&HashMap::from_iter([
3631                    (
3632                        ast::SlotId::principal(),
3633                        EntityUidJson::new("User", "alice"),
3634                    ),
3635                    (ast::SlotId::resource(), EntityUidJson::new("Folder", "abc")),
3636                ]))
3637                .expect("did fill all the slots");
3638            let expected_json = json!(
3639                {
3640                    "effect": "permit",
3641                    "principal": {
3642                        "op": "is",
3643                        "entity_type": "User",
3644                        "in": { "entity": { "type": "User", "id": "alice" } }
3645                    },
3646                    "action": {
3647                        "op": "All"
3648                    },
3649                    "resource": {
3650                        "op": "is",
3651                        "entity_type": "Doc",
3652                        "in": { "entity": { "type": "Folder", "id": "abc" } }
3653                    },
3654                    "conditions": [ ],
3655                }
3656            );
3657            let linked_json = serde_json::to_value(linked).unwrap();
3658            assert_eq!(
3659                linked_json,
3660                expected_json,
3661                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3662                serde_json::to_string_pretty(&expected_json).unwrap(),
3663                serde_json::to_string_pretty(&linked_json).unwrap(),
3664            );
3665        }
3666
3667        #[test]
3668        fn instantiate_no_slot() {
3669            let template = r#"permit(principal is User, action, resource is Doc);"#;
3670            let cst = parser::text_to_cst::parse_policy(template)
3671                .unwrap()
3672                .node
3673                .unwrap();
3674            let est: Policy = cst.try_into().unwrap();
3675            let linked = est.link(&HashMap::new()).unwrap();
3676            let expected_json = json!(
3677                {
3678                    "effect": "permit",
3679                    "principal": {
3680                        "op": "is",
3681                        "entity_type": "User",
3682                    },
3683                    "action": {
3684                        "op": "All"
3685                    },
3686                    "resource": {
3687                        "op": "is",
3688                        "entity_type": "Doc",
3689                    },
3690                    "conditions": [ ],
3691                }
3692            );
3693            let linked_json = serde_json::to_value(linked).unwrap();
3694            assert_eq!(
3695                linked_json,
3696                expected_json,
3697                "\nExpected:\n{}\n\nActual:\n{}\n\n",
3698                serde_json::to_string_pretty(&expected_json).unwrap(),
3699                serde_json::to_string_pretty(&linked_json).unwrap(),
3700            );
3701        }
3702    }
3703}
3704
3705#[cfg(test)]
3706mod issue_994 {
3707    use crate::{
3708        entities::JsonDeserializationError,
3709        est,
3710        test_utils::{expect_err, ExpectedErrorMessageBuilder},
3711    };
3712    use cool_asserts::assert_matches;
3713    use serde_json::json;
3714
3715    #[test]
3716    fn empty_annotation() {
3717        let src = json!(
3718            {
3719                "annotations": {"": ""},
3720                "effect": "permit",
3721                "principal": { "op": "All" },
3722                "action": { "op": "All" },
3723                "resource": { "op": "All" },
3724                "conditions": []
3725            }
3726        );
3727        assert_matches!(
3728            serde_json::from_value::<est::Policy>(src.clone())
3729                .map_err(|e| JsonDeserializationError::Serde(e.into())),
3730            Err(e) => {
3731                expect_err(
3732                    &src,
3733                    &miette::Report::new(e),
3734                    &ExpectedErrorMessageBuilder::error(r#"invalid id ``: unexpected end of input"#)
3735                        .build()
3736                );
3737            }
3738        );
3739    }
3740
3741    #[test]
3742    fn annotation_with_space() {
3743        let src = json!(
3744            {
3745                "annotations": {"has a space": ""},
3746                "effect": "permit",
3747                "principal": { "op": "All" },
3748                "action": { "op": "All" },
3749                "resource": { "op": "All" },
3750                "conditions": []
3751            }
3752        );
3753        assert_matches!(
3754            serde_json::from_value::<est::Policy>(src.clone())
3755                .map_err(|e| JsonDeserializationError::Serde(e.into())),
3756            Err(e) => {
3757                expect_err(
3758                    &src,
3759                    &miette::Report::new(e),
3760                    &ExpectedErrorMessageBuilder::error(r#"invalid id `has a space`: unexpected token `a`"#)
3761                        .build()
3762                );
3763            }
3764        );
3765    }
3766
3767    #[test]
3768    fn special_char() {
3769        let src = json!(
3770            {
3771                "annotations": {"@": ""},
3772                "effect": "permit",
3773                "principal": { "op": "All" },
3774                "action": { "op": "All" },
3775                "resource": { "op": "All" },
3776                "conditions": []
3777            }
3778        );
3779        assert_matches!(
3780            serde_json::from_value::<est::Policy>(src.clone())
3781                .map_err(|e| JsonDeserializationError::Serde(e.into())),
3782            Err(e) => {
3783                expect_err(
3784                    &src,
3785                    &miette::Report::new(e),
3786                    &ExpectedErrorMessageBuilder::error(r#"invalid id `@`: unexpected token `@`"#)
3787                        .build()
3788                );
3789            }
3790        );
3791    }
3792}
3793
3794#[cfg(test)]
3795mod issue_925 {
3796    use crate::{
3797        est,
3798        test_utils::{expect_err, ExpectedErrorMessageBuilder},
3799    };
3800    use cool_asserts::assert_matches;
3801    use serde_json::json;
3802
3803    #[test]
3804    fn invalid_action_type() {
3805        let src = json!(
3806            {
3807                "effect": "permit",
3808                "principal": {
3809                    "op": "All"
3810                },
3811                "action": {
3812                    "op": "==",
3813                    "entity": {
3814                        "type": "NotAction",
3815                        "id": "view",
3816                    }
3817                },
3818                "resource": {
3819                    "op": "All"
3820                },
3821                "conditions": []
3822            }
3823        );
3824        let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
3825        assert_matches!(
3826            est.try_into_ast_policy(None),
3827            Err(e) => {
3828                expect_err(
3829                    &src,
3830                    &miette::Report::new(e),
3831                    &ExpectedErrorMessageBuilder::error(r#"expected that action entity uids would have the type `Action` but got `NotAction::"view"`"#)
3832                        .help("action entities must have type `Action`, optionally in a namespace")
3833                        .build()
3834                );
3835            }
3836        );
3837
3838        let src = json!(
3839            {
3840                "effect": "permit",
3841                "principal": {
3842                    "op": "All"
3843                },
3844                "action": {
3845                    "op": "in",
3846                    "entity": {
3847                        "type": "NotAction",
3848                        "id": "view",
3849                    }
3850                },
3851                "resource": {
3852                    "op": "All"
3853                },
3854                "conditions": []
3855            }
3856        );
3857        let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
3858        assert_matches!(
3859            est.try_into_ast_policy(None),
3860            Err(e) => {
3861                expect_err(
3862                    &src,
3863                    &miette::Report::new(e),
3864                    &ExpectedErrorMessageBuilder::error(r#"expected that action entity uids would have the type `Action` but got `NotAction::"view"`"#)
3865                        .help("action entities must have type `Action`, optionally in a namespace")
3866                        .build()
3867                );
3868            }
3869        );
3870
3871        let src = json!(
3872            {
3873                "effect": "permit",
3874                "principal": {
3875                    "op": "All"
3876                },
3877                "action": {
3878                    "op": "in",
3879                    "entities": [
3880                        {
3881                            "type": "NotAction",
3882                            "id": "view",
3883                        },
3884                        {
3885                            "type": "Other",
3886                            "id": "edit",
3887                        }
3888                    ]
3889                },
3890                "resource": {
3891                    "op": "All"
3892                },
3893                "conditions": []
3894            }
3895        );
3896        let est: est::Policy = serde_json::from_value(src.clone()).unwrap();
3897        assert_matches!(
3898            est.try_into_ast_policy(None),
3899            Err(e) => {
3900                expect_err(
3901                    &src,
3902                    &miette::Report::new(e),
3903                    &ExpectedErrorMessageBuilder::error(r#"expected that action entity uids would have the type `Action` but got `NotAction::"view"` and `Other::"edit"`"#)
3904                        .help("action entities must have type `Action`, optionally in a namespace")
3905                        .build()
3906                );
3907            }
3908        );
3909    }
3910}