Skip to main content

cedar_policy_core/
est.rs

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