cedar_policy_core/
est.rs

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