Skip to main content

cedar_policy_core/validator/
entity_manifest.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//! Entity Manifest definition and static analysis.
18
19use std::collections::hash_map::Entry;
20use std::collections::HashMap;
21use std::fmt::{Display, Formatter};
22
23use crate::ast::{
24    BinaryOp, EntityUID, Expr, ExprKind, Literal, PolicySet, RequestType, UnaryOp, Var,
25};
26use crate::entities::err::EntitiesError;
27use miette::Diagnostic;
28use serde::{Deserialize, Serialize};
29use serde_with::serde_as;
30use smol_str::SmolStr;
31use thiserror::Error;
32
33mod analysis;
34mod loader;
35pub mod slicing;
36mod type_annotations;
37
38use crate::validator::entity_manifest::analysis::{
39    EntityManifestAnalysisResult, WrappedAccessPaths,
40};
41use crate::validator::{
42    typecheck::{PolicyCheck, Typechecker},
43    types::Type,
44    ValidationMode, ValidatorSchema,
45};
46use crate::validator::{ValidationResult, Validator};
47
48/// Data structure storing what data is needed based on the the [`RequestType`].
49///
50/// For each request type, the [`EntityManifest`] stores
51/// a [`RootAccessTrie`] of data to retrieve.
52///
53/// `T` represents an optional type annotation on each
54/// node in the [`AccessTrie`].
55// CAUTION: this type is publicly exported in `cedar-policy`.
56// Don't make fields `pub`, don't make breaking changes, and use caution
57// when adding public methods.
58#[doc = include_str!("../../experimental_warning.md")]
59#[serde_as]
60#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "camelCase")]
62pub struct EntityManifest {
63    /// A map from request types to [`RootAccessTrie`]s.
64    #[serde_as(as = "Vec<(_, _)>")]
65    pub(crate) per_action: HashMap<RequestType, RootAccessTrie>,
66}
67
68/// A map of data fields to [`AccessTrie`]s.
69/// The keys to this map form the edges in the access trie,
70/// pointing to sub-tries.
71// CAUTION: this type is publicly exported in `cedar-policy`.
72// Don't make fields `pub`, don't make breaking changes, and use caution
73// when adding public methods.
74#[doc = include_str!("../../experimental_warning.md")]
75pub type Fields = HashMap<SmolStr, Box<AccessTrie>>;
76
77/// The root of a data path or [`RootAccessTrie`].
78// CAUTION: this type is publicly exported in `cedar-policy`.
79// Don't make fields `pub`, don't make breaking changes, and use caution
80// when adding public methods.
81#[doc = include_str!("../../experimental_warning.md")]
82#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Hash)]
83#[serde(rename_all = "camelCase")]
84pub enum EntityRoot {
85    /// Literal entity ids
86    Literal(EntityUID),
87    /// A Cedar variable
88    Var(Var),
89}
90
91impl Display for EntityRoot {
92    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
93        match self {
94            EntityRoot::Literal(l) => write!(f, "{l}"),
95            EntityRoot::Var(v) => write!(f, "{v}"),
96        }
97    }
98}
99
100/// A trie describing a set of data paths to retrieve.
101///
102/// Each edge in the trie is either a record or entity dereference.
103///
104/// If an entity or record field does not exist in the backing store,
105/// it is safe to stop loading data at that point.
106///
107/// `T` represents an optional type annotation on each
108/// node in the [`AccessTrie`].
109// CAUTION: this type is publicly exported in `cedar-policy`.
110// Don't make fields `pub`, don't make breaking changes, and use caution
111// when adding public methods.
112#[doc = include_str!("../../experimental_warning.md")]
113#[serde_as]
114#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
115#[serde(rename_all = "camelCase")]
116pub struct RootAccessTrie {
117    /// The data that needs to be loaded, organized by root.
118    #[serde_as(as = "Vec<(_, _)>")]
119    pub(crate) trie: HashMap<EntityRoot, AccessTrie>,
120}
121
122/// A Trie representing a set of data paths to load,
123/// starting implicitly from a Cedar value.
124///
125/// `T` represents an optional type annotation on each
126/// node in the [`AccessTrie`].
127///
128// CAUTION: this type is publicly exported in `cedar-policy`.
129// Don't make fields `pub`, don't make breaking changes, and use caution
130// when adding public methods.
131#[doc = include_str!("../../experimental_warning.md")]
132#[serde_as]
133#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
134#[serde(rename_all = "camelCase")]
135pub struct AccessTrie {
136    /// Child data of this entity slice.
137    /// The keys are edges in the trie pointing to sub-trie values.
138    #[serde_as(as = "Vec<(_, _)>")]
139    pub(crate) children: Fields,
140    /// `ancestors_trie` is another [`RootAccessTrie`] representing
141    /// all of the ancestors of this entity that are required.
142    /// The ancestors trie is a subset of the original [`RootAccessTrie`].
143    /// See the [`RootAccessTrie::is_ancestor`] annotation.
144    pub(crate) ancestors_trie: RootAccessTrie,
145    /// When ancestors are required, each node marked `is_ancestor`
146    /// represents an ancestor or set of ancestors that are required.
147    /// An ancestor trie can be thought of as a set of pointers to
148    /// nodes in the original trie, one `is_ancestor`-marked node per pointer.
149    pub(crate) is_ancestor: bool,
150    /// The type of this node in the [`AccessTrie`].
151    /// From the public API, this field should always be `Some`.
152    /// It is `None` after deserialization or after first being constructed, but it is type annotated right away.
153    #[serde(skip_serializing)]
154    #[serde(skip_deserializing)]
155    pub(crate) node_type: Option<Type>,
156}
157
158/// An access path represents path of fields, starting with an [`EntityRoot`].
159/// Fields may be record fields or entity fields.
160/// If an access path ends with an entity type, it may also require the ancestors of the entity.
161#[derive(Debug, Clone, PartialEq, Eq, Hash)]
162pub(crate) struct AccessPath {
163    /// The root variable that begins the data path
164    pub root: EntityRoot,
165    /// The path of fields of entities or structs
166    pub path: Vec<SmolStr>,
167}
168
169/// Error when expressions are partial during entity
170/// manifest computation
171// CAUTION: this type is publicly exported in `cedar-policy`.
172// Don't make fields `pub`, don't make breaking changes, and use caution
173// when adding public methods.
174#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
175#[error("entity slicing requires fully concrete policies. Got a policy with an unknown expression")]
176pub struct PartialExpressionError {}
177
178impl Diagnostic for PartialExpressionError {}
179
180/// Error when the request is partial during entity
181/// manifest computation
182// CAUTION: this type is publicly exported in `cedar-policy`.
183// Don't make fields `pub`, don't make breaking changes, and use caution
184// when adding public methods.
185#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
186#[error("entity slicing requires a fully concrete request. Got a partial request")]
187pub struct PartialRequestError {}
188impl Diagnostic for PartialRequestError {}
189
190/// An error generated by entity slicing.
191#[derive(Debug, Error)]
192pub enum EntityManifestError {
193    /// A validation error was encountered
194    // TODO (#1158) impl Error for ValidationResult (it already is implemented for api::ValidationResult)
195    #[error("a validation error occurred")]
196    Validation(ValidationResult),
197    /// A entities error was encountered
198    #[error(transparent)]
199    Entities(#[from] EntitiesError),
200
201    /// The request was partial
202    #[error(transparent)]
203    PartialRequest(#[from] PartialRequestError),
204    /// A policy was partial
205    #[error(transparent)]
206    PartialExpression(#[from] PartialExpressionError),
207    /// Unsupported feature
208    #[error(transparent)]
209    UnsupportedCedarFeature(#[from] UnsupportedCedarFeatureError),
210}
211
212/// Error when entity manifest analysis cannot handle a Cedar feature
213// CAUTION: this type is publicly exported in `cedar-policy`.
214// Don't make fields `pub`, don't make breaking changes, and use caution
215// when adding public methods.
216#[derive(Debug, Clone, Error, Diagnostic)]
217#[error("entity manifest analysis currently doesn't support Cedar feature: {feature}")]
218pub struct UnsupportedCedarFeatureError {
219    pub(crate) feature: SmolStr,
220}
221
222/// Error when the manifest has an entity the schema lacks.
223// CAUTION: this type is publicly exported in `cedar-policy`.
224// Don't make fields `pub`, don't make breaking changes, and use caution
225// when adding public methods.
226#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
227#[error("entity manifest doesn't match schema. Schema is missing entity {entity}. Either you wrote an entity manifest by hand (not recommended) or you are using an out-of-date entity manifest with respect to the schema")]
228pub struct MismatchedMissingEntityError {
229    pub(crate) entity: EntityUID,
230}
231
232/// Error when the schema isn't valid in strict mode.
233// CAUTION: this type is publicly exported in `cedar-policy`.
234// Don't make fields `pub`, don't make breaking changes, and use caution
235// when adding public methods.
236#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
237#[error("entity manifests are only compatible with schemas that validate in strict mode. Tried to use an invalid schema with an entity manifest")]
238pub struct MismatchedNotStrictSchemaError {}
239
240/// An error generated by entity manifest parsing. These happen
241/// when the entity manifest doesn't conform to the schema.
242/// Either the user wrote an entity manifest by hand (not reccomended)
243/// or they used an out-of-date entity manifest (after updating the schema).
244/// Warning: This error is not guaranteed to happen, even when an entity
245/// manifest is out-of-date with respect to a schema! Users must ensure
246/// that entity manifests are in-sync with the schema and policies.
247// CAUTION: this type is publicly exported in `cedar-policy`.
248// Don't make fields `pub`, don't make breaking changes, and use caution
249// when adding public methods.
250#[derive(Debug, Clone, Error, Hash, Eq, PartialEq)]
251pub enum MismatchedEntityManifestError {
252    /// Mismatch between entity in manifest and schema
253    #[error(transparent)]
254    MismatchedMissingEntity(#[from] MismatchedMissingEntityError),
255    /// Found a schema that isn't valid in strict mode
256    #[error(transparent)]
257    MismatchedNotStrictSchema(#[from] MismatchedNotStrictSchemaError),
258}
259
260/// An error generated when parsing entity manifests from json
261#[derive(Debug, Error)]
262pub enum EntityManifestFromJsonError {
263    /// A Serde error happened
264    #[error(transparent)]
265    SerdeJsonParseError(#[from] serde_json::Error),
266    /// A mismatched entity manifest error
267    #[error(transparent)]
268    MismatchedEntityManifest(#[from] MismatchedEntityManifestError),
269}
270
271impl EntityManifest {
272    /// Get the contents of the entity manifest
273    /// indexed by the type of the request.
274    pub fn per_action(&self) -> &HashMap<RequestType, RootAccessTrie> {
275        &self.per_action
276    }
277
278    /// Convert a json string to an [`EntityManifest`].
279    /// Requires the schema in order to add type annotations.
280    pub fn from_json_str(
281        json: &str,
282        schema: &ValidatorSchema,
283    ) -> Result<Self, EntityManifestFromJsonError> {
284        match serde_json::from_str::<EntityManifest>(json) {
285            Ok(manifest) => manifest.to_typed(schema).map_err(|e| e.into()),
286            Err(e) => Err(e.into()),
287        }
288    }
289
290    /// Convert a json value to an [`EntityManifest`].
291    /// Requires the schema in order to add type annotations.
292    pub fn from_json_value(
293        value: serde_json::Value,
294        schema: &ValidatorSchema,
295    ) -> Result<Self, EntityManifestFromJsonError> {
296        match serde_json::from_value::<EntityManifest>(value) {
297            Ok(manifest) => manifest.to_typed(schema).map_err(|e| e.into()),
298            Err(e) => Err(e.into()),
299        }
300    }
301}
302
303/// Union two tries by combining the fields.
304fn union_fields_mut(first: &mut Fields, second: Fields) {
305    for (key, value) in second {
306        match first.entry(key) {
307            Entry::Occupied(mut occupied) => {
308                occupied.get_mut().union_mut(*value);
309            }
310            Entry::Vacant(vacant) => {
311                vacant.insert(value);
312            }
313        }
314    }
315}
316
317impl AccessPath {
318    /// Convert a [`AccessPath`] into corresponding [`RootAccessTrie`].
319    pub fn to_root_access_trie(&self) -> RootAccessTrie {
320        self.to_root_access_trie_with_leaf(AccessTrie::default())
321    }
322
323    /// Convert an [`AccessPath`] to a [`RootAccessTrie`], and also
324    /// add a full trie as the leaf at the end.
325    pub(crate) fn to_root_access_trie_with_leaf(&self, leaf_trie: AccessTrie) -> RootAccessTrie {
326        let mut current = leaf_trie;
327
328        // reverse the path, visiting the last access first
329        for field in self.path.iter().rev() {
330            let mut fields = HashMap::new();
331            fields.insert(field.clone(), Box::new(current));
332
333            current = AccessTrie {
334                ancestors_trie: Default::default(),
335                is_ancestor: false,
336                children: fields,
337                node_type: None,
338            };
339        }
340
341        let mut primary_map = HashMap::new();
342
343        // special case: if the path is completely empty,
344        // no need to insert anything
345        if current != AccessTrie::new() {
346            primary_map.insert(self.root.clone(), current);
347        }
348        RootAccessTrie { trie: primary_map }
349    }
350}
351
352impl RootAccessTrie {
353    /// Get the trie as a hash map from [`EntityRoot`]
354    /// to sub-[`AccessTrie`]s.
355    pub fn trie(&self) -> &HashMap<EntityRoot, AccessTrie> {
356        &self.trie
357    }
358}
359
360impl RootAccessTrie {
361    /// Create an empty [`RootAccessTrie`] that requests nothing.
362    pub fn new() -> Self {
363        Self {
364            trie: Default::default(),
365        }
366    }
367}
368
369impl RootAccessTrie {
370    /// Union two [`RootAccessTrie`]s together.
371    /// The new trie requests the data from both of the original.
372    pub fn union(mut self, other: Self) -> Self {
373        self.union_mut(other);
374        self
375    }
376
377    /// Like [`RootAccessTrie::union`], but modifies the current trie.
378    pub fn union_mut(&mut self, other: Self) {
379        for (key, value) in other.trie {
380            match self.trie.entry(key) {
381                Entry::Occupied(mut occupied) => {
382                    occupied.get_mut().union_mut(value);
383                }
384                Entry::Vacant(vacant) => {
385                    vacant.insert(value);
386                }
387            }
388        }
389    }
390}
391
392impl Default for RootAccessTrie {
393    fn default() -> Self {
394        Self::new()
395    }
396}
397
398impl AccessTrie {
399    /// Union two [`AccessTrie`]s together.
400    /// The new trie requests the data from both of the original.
401    pub fn union(mut self, other: Self) -> Self {
402        self.union_mut(other);
403        self
404    }
405
406    /// Like [`AccessTrie::union`], but modifies the current trie.
407    pub fn union_mut(&mut self, other: Self) {
408        union_fields_mut(&mut self.children, other.children);
409        self.ancestors_trie.union_mut(other.ancestors_trie);
410        self.is_ancestor = self.is_ancestor || other.is_ancestor;
411    }
412
413    /// Get the children of this [`AccessTrie`].
414    pub fn children(&self) -> &Fields {
415        &self.children
416    }
417
418    /// Get a boolean which is true if this trie
419    /// requires all ancestors of the entity to be loaded.
420    pub fn ancestors_required(&self) -> &RootAccessTrie {
421        &self.ancestors_trie
422    }
423}
424
425impl AccessTrie {
426    /// A new trie that requests no data.
427    pub(crate) fn new() -> Self {
428        Self {
429            children: Default::default(),
430            ancestors_trie: Default::default(),
431            is_ancestor: false,
432            node_type: None,
433        }
434    }
435}
436
437impl Default for AccessTrie {
438    fn default() -> Self {
439        Self::new()
440    }
441}
442
443/// Computes an [`EntityManifest`] from the schema and policies.
444/// The policies must validate against the schema in strict mode,
445/// otherwise an error is returned.
446pub fn compute_entity_manifest(
447    validator: &Validator,
448    policies: &PolicySet,
449) -> Result<EntityManifest, EntityManifestError> {
450    // first, run strict validation to ensure there are no errors
451    let validation_res = validator.validate(policies, ValidationMode::Strict);
452    if !validation_res.validation_passed() {
453        return Err(EntityManifestError::Validation(validation_res));
454    }
455
456    let mut manifest: HashMap<RequestType, RootAccessTrie> = HashMap::new();
457
458    let typechecker = Typechecker::new(validator.schema(), ValidationMode::Strict);
459    // now, for each policy we add the data it requires to the manifest
460    for policy in policies.policies() {
461        // typecheck the policy and get all the request environments
462        let request_envs = typechecker.typecheck_by_request_env(policy.template());
463        for (request_env, policy_check) in request_envs {
464            let new_primary_slice = match policy_check {
465                PolicyCheck::Success(typechecked_expr) => {
466                    // compute the trie from the typechecked expr
467                    // using static analysis
468                    entity_manifest_from_expr(&typechecked_expr).map(|val| val.global_trie)
469                }
470                PolicyCheck::Irrelevant(_, _) => {
471                    // this policy is irrelevant, so we need no data
472                    Ok(RootAccessTrie::new())
473                }
474
475                #[expect(
476                    clippy::panic,
477                    reason = "policy check should not fail after full strict validation above"
478                )]
479                PolicyCheck::Fail(_errors) => {
480                    panic!("Policy check failed after validation succeeded")
481                }
482            }?;
483
484            let request_type = request_env
485                .to_request_type()
486                .ok_or(PartialRequestError {})?;
487            match manifest.entry(request_type) {
488                Entry::Occupied(mut occupied) => {
489                    occupied.get_mut().union_mut(new_primary_slice);
490                }
491                Entry::Vacant(vacant) => {
492                    vacant.insert(new_primary_slice);
493                }
494            }
495        }
496    }
497
498    #[expect(
499        clippy::unwrap_used,
500        reason = "entity manifest cannot be out of date, since it was computed from the schema given"
501    )]
502    Ok(EntityManifest {
503        per_action: manifest,
504    }
505    .to_typed(validator.schema())
506    .unwrap())
507}
508
509/// A static analysis on type-annotated cedar expressions.
510/// Computes the [`RootAccessTrie`] representing all the data required
511/// to evaluate the expression.
512fn entity_manifest_from_expr(
513    expr: &Expr<Option<Type>>,
514) -> Result<EntityManifestAnalysisResult, EntityManifestError> {
515    match expr.expr_kind() {
516        ExprKind::Slot(slot_id) => {
517            if slot_id.is_principal() {
518                Ok(EntityManifestAnalysisResult::from_root(EntityRoot::Var(
519                    Var::Principal,
520                )))
521            } else {
522                assert!(slot_id.is_resource());
523                Ok(EntityManifestAnalysisResult::from_root(EntityRoot::Var(
524                    Var::Resource,
525                )))
526            }
527        }
528        ExprKind::Var(var) => Ok(EntityManifestAnalysisResult::from_root(EntityRoot::Var(
529            *var,
530        ))),
531        ExprKind::Lit(Literal::EntityUID(literal)) => Ok(EntityManifestAnalysisResult::from_root(
532            EntityRoot::Literal((**literal).clone()),
533        )),
534        ExprKind::Unknown(_) => Err(PartialExpressionError {})?,
535
536        // Non-entity literals need no fields to be loaded.
537        ExprKind::Lit(_) => Ok(EntityManifestAnalysisResult::default()),
538        ExprKind::If {
539            test_expr,
540            then_expr,
541            else_expr,
542        } => Ok(entity_manifest_from_expr(test_expr)?
543            .empty_paths()
544            .union(entity_manifest_from_expr(then_expr)?)
545            .union(entity_manifest_from_expr(else_expr)?)),
546        ExprKind::And { left, right }
547        | ExprKind::Or { left, right }
548        | ExprKind::BinaryApp {
549            op: BinaryOp::Less | BinaryOp::LessEq | BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul,
550            arg1: left,
551            arg2: right,
552        } => Ok(entity_manifest_from_expr(left)?
553            .empty_paths()
554            .union(entity_manifest_from_expr(right)?.empty_paths())),
555        ExprKind::UnaryApp { op, arg } => {
556            match op {
557                // these unary ops are on primitive types, so they are simple
558                UnaryOp::Not | UnaryOp::Neg => Ok(entity_manifest_from_expr(arg)?.empty_paths()),
559                UnaryOp::IsEmpty => {
560                    #[expect(
561                        clippy::expect_used,
562                        reason = "Typechecking succeeded, so type annotations are present"
563                    )]
564                    let ty = arg
565                        .data()
566                        .as_ref()
567                        .expect("Expected annotated types after typechecking");
568                    Ok(entity_manifest_from_expr(arg)?
569                        .full_type_required(ty)
570                        .empty_paths())
571                }
572            }
573        }
574        ExprKind::BinaryApp {
575            op:
576                op @ (BinaryOp::Eq
577                | BinaryOp::In
578                | BinaryOp::Contains
579                | BinaryOp::ContainsAll
580                | BinaryOp::ContainsAny),
581            arg1,
582            arg2,
583        } => {
584            // First, find the data paths for each argument
585            let mut arg1_res = entity_manifest_from_expr(arg1)?;
586            let arg2_res = entity_manifest_from_expr(arg2)?;
587
588            #[expect(
589                clippy::expect_used,
590                reason = "Typechecking succeeded, so type annotations are present"
591            )]
592            let ty1 = arg1
593                .data()
594                .as_ref()
595                .expect("Expected annotated types after typechecking");
596            #[expect(
597                clippy::expect_used,
598                reason = "Typechecking succeeded, so type annotations are present"
599            )]
600            let ty2 = arg2
601                .data()
602                .as_ref()
603                .expect("Expected annotated types after typechecking");
604
605            // For the `in` operator, we need the ancestors of entities.
606            if matches!(op, BinaryOp::In) {
607                arg1_res = arg1_res
608                    .with_ancestors_required(&arg2_res.resulting_paths.to_ancestor_access_trie());
609            }
610
611            // Load all fields using `full_type_required`, since
612            // these operations do equality checks.
613            Ok(arg1_res
614                .full_type_required(ty1)
615                .union(arg2_res.full_type_required(ty2))
616                .empty_paths())
617        }
618        ExprKind::BinaryApp {
619            op: BinaryOp::GetTag | BinaryOp::HasTag,
620            arg1: _,
621            arg2: _,
622        } => Err(UnsupportedCedarFeatureError {
623            feature: "entity tags".into(),
624        }
625        .into()),
626        ExprKind::ExtensionFunctionApp { fn_name: _, args } => {
627            // WARNING: this code assumes that extension functions
628            // all take primitives as inputs and produce
629            // primitives as outputs.
630            // If not, we would need to use logic similar to the Eq binary operator.
631
632            let mut res = EntityManifestAnalysisResult::default();
633
634            for arg in args.iter() {
635                res = res.union(entity_manifest_from_expr(arg)?);
636            }
637            Ok(res)
638        }
639        ExprKind::Like { expr, pattern: _ }
640        | ExprKind::Is {
641            expr,
642            entity_type: _,
643        } => {
644            // drop paths since boolean returned
645            Ok(entity_manifest_from_expr(expr)?.empty_paths())
646        }
647        ExprKind::Set(contents) => {
648            let mut res = EntityManifestAnalysisResult::default();
649
650            // take union of all of the contents
651            for expr in &**contents {
652                let content = entity_manifest_from_expr(expr)?;
653
654                res = res.union(content);
655            }
656
657            // now, wrap result in a set
658            res.resulting_paths = WrappedAccessPaths::SetLiteral(Box::new(res.resulting_paths));
659
660            Ok(res)
661        }
662        ExprKind::Record(content) => {
663            let mut record_contents = HashMap::new();
664            let mut global_trie = RootAccessTrie::default();
665
666            for (key, child_expr) in content.iter() {
667                let res = entity_manifest_from_expr(child_expr)?;
668                record_contents.insert(key.clone(), Box::new(res.resulting_paths));
669
670                global_trie = global_trie.union(res.global_trie);
671            }
672
673            Ok(EntityManifestAnalysisResult {
674                resulting_paths: WrappedAccessPaths::RecordLiteral(record_contents),
675                global_trie,
676            })
677        }
678        ExprKind::GetAttr { expr, attr } => {
679            Ok(entity_manifest_from_expr(expr)?.get_or_has_attr(attr))
680        }
681        ExprKind::HasAttr { expr, attr } => Ok(entity_manifest_from_expr(expr)?
682            .get_or_has_attr(attr)
683            .empty_paths()),
684        #[cfg(feature = "tolerant-ast")]
685        ExprKind::Error { .. } => Err(EntityManifestError::UnsupportedCedarFeature(
686            UnsupportedCedarFeatureError {
687                feature: "No support for AST error nodes".into(),
688            },
689        )),
690    }
691}
692
693#[cfg(test)]
694mod entity_slice_tests {
695    use crate::{ast::PolicyID, extensions::Extensions, parser::parse_policy};
696    use similar_asserts::assert_eq;
697
698    use super::*;
699
700    // Schema for testing in this module
701    fn schema() -> ValidatorSchema {
702        ValidatorSchema::from_cedarschema_str(
703            "
704entity User = {
705  name: String,
706};
707
708entity Document;
709
710action Read appliesTo {
711  principal: [User],
712  resource: [Document]
713};
714    ",
715            Extensions::all_available(),
716        )
717        .unwrap()
718        .0
719    }
720
721    fn document_fields_schema() -> ValidatorSchema {
722        ValidatorSchema::from_cedarschema_str(
723            "
724entity User = {
725name: String,
726};
727
728entity Document = {
729owner: User,
730viewer: User,
731};
732
733action Read appliesTo {
734principal: [User],
735resource: [Document]
736};
737",
738            Extensions::all_available(),
739        )
740        .unwrap()
741        .0
742    }
743
744    #[test]
745    fn test_simple_entity_manifest() {
746        let mut pset = PolicySet::new();
747        let policy = parse_policy(
748            None,
749            r#"permit(principal, action, resource)
750when {
751    principal.name == "John"
752};"#,
753        )
754        .expect("should succeed");
755        pset.add(policy.into()).expect("should succeed");
756
757        let validator = Validator::new(schema());
758
759        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
760        let expected_rust = EntityManifest {
761            per_action: HashMap::from([(
762                RequestType {
763                    principal: "User".parse().unwrap(),
764                    resource: "Document".parse().unwrap(),
765                    action: r#"Action::"Read""#.parse().unwrap(),
766                },
767                RootAccessTrie {
768                    trie: HashMap::from([(
769                        EntityRoot::Var(Var::Principal),
770                        AccessTrie {
771                            children: HashMap::from([(
772                                SmolStr::new_static("name"),
773                                Box::new(AccessTrie {
774                                    children: HashMap::new(),
775                                    ancestors_trie: RootAccessTrie::new(),
776                                    is_ancestor: false,
777                                    node_type: Some(Type::primitive_string()),
778                                }),
779                            )]),
780                            ancestors_trie: RootAccessTrie::new(),
781                            is_ancestor: false,
782                            node_type: Some(Type::named_entity_reference("User".parse().unwrap())),
783                        },
784                    )]),
785                },
786            )]),
787        };
788        let expected = serde_json::json! ({
789          "perAction": [
790            [
791              {
792                "principal": "User",
793                "action": {
794                  "ty": "Action",
795                  "eid": "Read"
796                },
797                "resource": "Document"
798              },
799              {
800                "trie": [
801                  [
802                    {
803                      "var": "principal"
804                    },
805                    {
806                      "children": [
807                        [
808                          "name",
809                          {
810                            "children": [],
811                            "ancestorsTrie": { "trie": []},
812                            "isAncestor": false
813                          }
814                        ]
815                      ],
816                      "ancestorsTrie": { "trie": []},
817                      "isAncestor": false
818                    }
819                  ]
820                ]
821              }
822            ]
823          ]
824        });
825        let expected_manifest =
826            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
827        assert_eq!(entity_manifest, expected_manifest);
828        assert_eq!(entity_manifest, expected_rust);
829    }
830
831    #[test]
832    fn test_empty_entity_manifest() {
833        let mut pset = PolicySet::new();
834        let policy =
835            parse_policy(None, "permit(principal, action, resource);").expect("should succeed");
836        pset.add(policy.into()).expect("should succeed");
837
838        let validator = Validator::new(schema());
839
840        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
841        let expected = serde_json::json!(
842        {
843          "perAction": [
844            [
845              {
846                "principal": "User",
847                "action": {
848                  "ty": "Action",
849                  "eid": "Read"
850                },
851                "resource": "Document"
852              },
853              {
854                "trie": [
855                ]
856              }
857            ]
858          ]
859        });
860        let expected_manifest =
861            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
862        assert_eq!(entity_manifest, expected_manifest);
863    }
864
865    #[test]
866    fn test_entity_manifest_ancestors_required() {
867        let mut pset = PolicySet::new();
868        let policy = parse_policy(
869            None,
870            "permit(principal, action, resource)
871when {
872    principal in resource || principal.manager in resource
873};",
874        )
875        .expect("should succeed");
876        pset.add(policy.into()).expect("should succeed");
877
878        let schema = ValidatorSchema::from_cedarschema_str(
879            "
880entity User in [Document] = {
881  name: String,
882  manager: User
883};
884entity Document;
885action Read appliesTo {
886  principal: [User],
887  resource: [Document]
888};
889  ",
890            Extensions::all_available(),
891        )
892        .unwrap()
893        .0;
894        let validator = Validator::new(schema);
895
896        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
897        let expected = serde_json::json!(
898        {
899          "perAction": [
900            [
901              {
902                "principal": "User",
903                "action": {
904                  "ty": "Action",
905                  "eid": "Read"
906                },
907                "resource": "Document"
908              },
909              {
910                "trie": [
911                  [
912                    {
913                      "var": "principal"
914                    },
915                    {
916                      "children": [
917                        [
918                          "manager",
919                          {
920                            "children": [],
921                            "ancestorsTrie": {
922                              "trie": [
923                                [
924                                  {
925                                    "var": "resource",
926                                  },
927                                  {
928                                    "children": [],
929                                    "isAncestor": true,
930                                    "ancestorsTrie": { "trie": [] }
931                                  }
932                                ]
933                              ]
934                            },
935                            "isAncestor": false
936                          }
937                        ]
938                      ],
939                      "ancestorsTrie": {
940                              "trie": [
941                                [
942                                  {
943                                    "var": "resource",
944                                  },
945                                  {
946                                    "children": [],
947                                    "isAncestor": true,
948                                    "ancestorsTrie": { "trie": [] }
949                                  }
950                                ]
951                              ]
952                            },
953                      "isAncestor": false
954                    }
955                  ]
956                ]
957              }
958            ]
959          ]
960        });
961        let expected_manifest =
962            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
963        assert_eq!(entity_manifest, expected_manifest);
964    }
965
966    #[test]
967    fn test_entity_manifest_multiple_types() {
968        let mut pset = PolicySet::new();
969        let policy = parse_policy(
970            None,
971            r#"permit(principal, action, resource)
972when {
973    principal.name == "John"
974};"#,
975        )
976        .expect("should succeed");
977        pset.add(policy.into()).expect("should succeed");
978
979        let schema = ValidatorSchema::from_cedarschema_str(
980            "
981entity User = {
982  name: String,
983};
984
985entity OtherUserType = {
986  name: String,
987  irrelevant: String,
988};
989
990entity Document;
991
992action Read appliesTo {
993  principal: [User, OtherUserType],
994  resource: [Document]
995};
996        ",
997            Extensions::all_available(),
998        )
999        .unwrap()
1000        .0;
1001        let validator = Validator::new(schema);
1002
1003        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1004        let expected = serde_json::json!(
1005        {
1006          "perAction": [
1007            [
1008              {
1009                "principal": "User",
1010                "action": {
1011                  "ty": "Action",
1012                  "eid": "Read"
1013                },
1014                "resource": "Document"
1015              },
1016              {
1017                "trie": [
1018                  [
1019                    {
1020                      "var": "principal"
1021                    },
1022                    {
1023                      "children": [
1024                        [
1025                          "name",
1026                          {
1027                            "children": [],
1028                            "ancestorsTrie": { "trie": []},
1029                            "isAncestor": false
1030                          }
1031                        ]
1032                      ],
1033                      "ancestorsTrie": { "trie": []},
1034                      "isAncestor": false
1035                    }
1036                  ]
1037                ]
1038              }
1039            ],
1040            [
1041              {
1042                "principal": "OtherUserType",
1043                "action": {
1044                  "ty": "Action",
1045                  "eid": "Read"
1046                },
1047                "resource": "Document"
1048              },
1049              {
1050                "trie": [
1051                  [
1052                    {
1053                      "var": "principal"
1054                    },
1055                    {
1056                      "children": [
1057                        [
1058                          "name",
1059                          {
1060                            "children": [],
1061                            "ancestorsTrie": { "trie": []},
1062                            "isAncestor": false
1063                          }
1064                        ]
1065                      ],
1066                      "ancestorsTrie": { "trie": []},
1067                      "isAncestor": false
1068                    }
1069                  ]
1070                ]
1071              }
1072            ]
1073          ]
1074            });
1075        let expected_manifest =
1076            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1077        assert_eq!(entity_manifest, expected_manifest);
1078    }
1079
1080    #[test]
1081    fn test_entity_manifest_multiple_branches() {
1082        let mut pset = PolicySet::new();
1083        let policy1 = parse_policy(
1084            None,
1085            r#"
1086permit(
1087  principal,
1088  action == Action::"Read",
1089  resource
1090)
1091when
1092{
1093  resource.readers.contains(principal)
1094};"#,
1095        )
1096        .unwrap();
1097        let policy2 = parse_policy(
1098            Some(PolicyID::from_string("Policy2")),
1099            r#"permit(
1100  principal,
1101  action == Action::"Read",
1102  resource
1103)
1104when
1105{
1106  resource.metadata.owner == principal
1107};"#,
1108        )
1109        .unwrap();
1110        pset.add(policy1.into()).expect("should succeed");
1111        pset.add(policy2.into()).expect("should succeed");
1112
1113        let schema = ValidatorSchema::from_cedarschema_str(
1114            "
1115entity User;
1116
1117entity Metadata = {
1118   owner: User,
1119   time: String,
1120};
1121
1122entity Document = {
1123  metadata: Metadata,
1124  readers: Set<User>,
1125};
1126
1127action Read appliesTo {
1128  principal: [User],
1129  resource: [Document]
1130};
1131        ",
1132            Extensions::all_available(),
1133        )
1134        .unwrap()
1135        .0;
1136        let validator = Validator::new(schema);
1137
1138        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1139        let expected = serde_json::json!(
1140        {
1141          "perAction": [
1142            [
1143              {
1144                "principal": "User",
1145                "action": {
1146                  "ty": "Action",
1147                  "eid": "Read"
1148                },
1149                "resource": "Document"
1150              },
1151              {
1152                "trie": [
1153                  [
1154                    {
1155                      "var": "resource"
1156                    },
1157                    {
1158                      "children": [
1159                        [
1160                          "metadata",
1161                          {
1162                            "children": [
1163                              [
1164                                "owner",
1165                                {
1166                                  "children": [],
1167                                  "ancestorsTrie": { "trie": []},
1168                                  "isAncestor": false
1169                                }
1170                              ]
1171                            ],
1172                            "ancestorsTrie": { "trie": []},
1173                            "isAncestor": false
1174                          }
1175                        ],
1176                        [
1177                          "readers",
1178                          {
1179                            "children": [],
1180                            "ancestorsTrie": { "trie": []},
1181                            "isAncestor": false
1182                          }
1183                        ]
1184                      ],
1185                      "ancestorsTrie": { "trie": []},
1186                      "isAncestor": false
1187                    }
1188                  ],
1189                ]
1190              }
1191            ]
1192          ]
1193        });
1194        let expected_manifest =
1195            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1196        assert_eq!(entity_manifest, expected_manifest);
1197    }
1198
1199    #[test]
1200    fn test_entity_manifest_struct_equality() {
1201        let mut pset = PolicySet::new();
1202        // we need to load all of the metadata, not just nickname
1203        // no need to load actual name
1204        let policy = parse_policy(
1205            None,
1206            r#"permit(principal, action, resource)
1207when {
1208    principal.metadata.nickname == "timmy" && principal.metadata == {
1209        "friends": [ "oliver" ],
1210        "nickname": "timmy"
1211    }
1212};"#,
1213        )
1214        .expect("should succeed");
1215        pset.add(policy.into()).expect("should succeed");
1216
1217        let schema = ValidatorSchema::from_cedarschema_str(
1218            "
1219entity User = {
1220  name: String,
1221  metadata: {
1222    friends: Set<String>,
1223    nickname: String,
1224  },
1225};
1226
1227entity Document;
1228
1229action BeSad appliesTo {
1230  principal: [User],
1231  resource: [Document]
1232};
1233        ",
1234            Extensions::all_available(),
1235        )
1236        .unwrap()
1237        .0;
1238        let validator = Validator::new(schema);
1239
1240        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1241        let expected = serde_json::json!(
1242        {
1243          "perAction": [
1244            [
1245              {
1246                "principal": "User",
1247                "action": {
1248                  "ty": "Action",
1249                  "eid": "BeSad"
1250                },
1251                "resource": "Document"
1252              },
1253              {
1254                "trie": [
1255                  [
1256                    {
1257                      "var": "principal"
1258                    },
1259                    {
1260                      "children": [
1261                        [
1262                          "metadata",
1263                          {
1264                            "children": [
1265                              [
1266                                "nickname",
1267                                {
1268                                  "children": [],
1269                                  "ancestorsTrie": { "trie": []},
1270                                  "isAncestor": false
1271                                }
1272                              ],
1273                              [
1274                                "friends",
1275                                {
1276                                  "children": [],
1277                                  "ancestorsTrie": { "trie": []},
1278                                  "isAncestor": false
1279                                }
1280                              ]
1281                            ],
1282                            "ancestorsTrie": { "trie": []},
1283                            "isAncestor": false
1284                          }
1285                        ]
1286                      ],
1287                      "ancestorsTrie": { "trie": []},
1288                      "isAncestor": false
1289                    }
1290                  ]
1291                ]
1292              }
1293            ]
1294          ]
1295        });
1296        let expected_manifest =
1297            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1298        assert_eq!(entity_manifest, expected_manifest);
1299    }
1300
1301    #[test]
1302    fn test_entity_manifest_struct_equality_left_right_different() {
1303        let mut pset = PolicySet::new();
1304        // we need to load all of the metadata, not just nickname
1305        // no need to load actual name
1306        let policy = parse_policy(
1307            None,
1308            r#"permit(principal, action, resource)
1309when {
1310    principal.metadata == resource.metadata
1311};"#,
1312        )
1313        .expect("should succeed");
1314        pset.add(policy.into()).expect("should succeed");
1315
1316        let schema = ValidatorSchema::from_cedarschema_str(
1317            "
1318entity User = {
1319  name: String,
1320  metadata: {
1321    friends: Set<String>,
1322    nickname: String,
1323  },
1324};
1325
1326entity Document;
1327
1328action Hello appliesTo {
1329  principal: [User],
1330  resource: [User]
1331};
1332        ",
1333            Extensions::all_available(),
1334        )
1335        .unwrap()
1336        .0;
1337        let validator = Validator::new(schema);
1338
1339        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1340        let expected = serde_json::json!(
1341        {
1342          "perAction": [
1343            [
1344              {
1345                "principal": "User",
1346                "action": {
1347                  "ty": "Action",
1348                  "eid": "Hello"
1349                },
1350                "resource": "User"
1351              },
1352              {
1353                "trie": [
1354                  [
1355                    {
1356                      "var": "resource"
1357                    },
1358                    {
1359                      "children": [
1360                        [
1361                          "metadata",
1362                          {
1363                            "children": [
1364                              [
1365                                "friends",
1366                                {
1367                                  "children": [],
1368                                  "ancestorsTrie": { "trie": []},
1369                                  "isAncestor": false
1370                                }
1371                              ],
1372                              [
1373                                "nickname",
1374                                {
1375                                  "children": [],
1376                                  "ancestorsTrie": { "trie": []},
1377                                  "isAncestor": false
1378                                }
1379                              ]
1380                            ],
1381                            "ancestorsTrie": { "trie": []},
1382                            "isAncestor": false
1383                          }
1384                        ]
1385                      ],
1386                      "ancestorsTrie": { "trie": []},
1387                            "isAncestor": false
1388                    }
1389                  ],
1390                  [
1391                    {
1392                      "var": "principal"
1393                    },
1394                    {
1395                      "children": [
1396                        [
1397                          "metadata",
1398                          {
1399                            "children": [
1400                              [
1401                                "nickname",
1402                                {
1403                                  "children": [],
1404                                  "ancestorsTrie": { "trie": []},
1405                                  "isAncestor": false
1406                                }
1407                              ],
1408                              [
1409                                "friends",
1410                                {
1411                                  "children": [],
1412                                  "ancestorsTrie": { "trie": []},
1413                                  "isAncestor": false
1414                                }
1415                              ]
1416                            ],
1417                            "ancestorsTrie": { "trie": []},
1418                            "isAncestor": false
1419                          }
1420                        ]
1421                      ],
1422                      "ancestorsTrie": { "trie": []},
1423                      "isAncestor": false
1424                    }
1425                  ]
1426                ]
1427              }
1428            ]
1429          ]
1430        });
1431        let expected_manifest =
1432            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1433        assert_eq!(entity_manifest, expected_manifest);
1434    }
1435
1436    #[test]
1437    fn test_entity_manifest_with_if() {
1438        let mut pset = PolicySet::new();
1439
1440        let validator = Validator::new(document_fields_schema());
1441
1442        let policy = parse_policy(
1443            None,
1444            r#"permit(principal, action, resource)
1445when {
1446    if principal.name == "John"
1447    then resource.owner.name == User::"oliver".name
1448    else resource.viewer == User::"oliver"
1449};"#,
1450        )
1451        .expect("should succeed");
1452        pset.add(policy.into()).expect("should succeed");
1453
1454        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1455        let expected = serde_json::json! ( {
1456          "perAction": [
1457            [
1458              {
1459                "principal": "User",
1460                "action": {
1461                  "ty": "Action",
1462                  "eid": "Read"
1463                },
1464                "resource": "Document"
1465              },
1466              {
1467                "trie": [
1468                  [
1469                    {
1470                      "var": "principal"
1471                    },
1472                    {
1473                      "children": [
1474                        [
1475                          "name",
1476                          {
1477                            "children": [],
1478                            "ancestorsTrie": { "trie": []},
1479                            "isAncestor": false
1480                          }
1481                        ]
1482                      ],
1483                      "ancestorsTrie": { "trie": []},
1484                            "isAncestor": false
1485                    }
1486                  ],
1487                  [
1488                    {
1489                      "literal": {
1490                        "ty": "User",
1491                        "eid": "oliver"
1492                      }
1493                    },
1494                    {
1495                      "children": [
1496                        [
1497                          "name",
1498                          {
1499                            "children": [],
1500                            "ancestorsTrie": { "trie": []},
1501                            "isAncestor": false
1502                          }
1503                        ]
1504                      ],
1505                      "ancestorsTrie": { "trie": []},
1506                            "isAncestor": false
1507                    }
1508                  ],
1509                  [
1510                    {
1511                      "var": "resource"
1512                    },
1513                    {
1514                      "children": [
1515                        [
1516                          "viewer",
1517                          {
1518                            "children": [],
1519                            "ancestorsTrie": { "trie": []},
1520                            "isAncestor": false
1521                          }
1522                        ],
1523                        [
1524                          "owner",
1525                          {
1526                            "children": [
1527                              [
1528                                "name",
1529                                {
1530                                  "children": [],
1531                                  "ancestorsTrie": { "trie": []},
1532                            "isAncestor": false
1533                                }
1534                              ]
1535                            ],
1536                            "ancestorsTrie": { "trie": []},
1537                            "isAncestor": false
1538                          }
1539                        ]
1540                      ],
1541                      "ancestorsTrie": { "trie": []},
1542                            "isAncestor": false
1543                    }
1544                  ]
1545                ]
1546              }
1547            ]
1548          ]
1549        }
1550        );
1551        let expected_manifest =
1552            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1553        assert_eq!(entity_manifest, expected_manifest);
1554    }
1555
1556    #[test]
1557    fn test_entity_manifest_if_literal_record() {
1558        let mut pset = PolicySet::new();
1559
1560        let validator = Validator::new(document_fields_schema());
1561
1562        let policy = parse_policy(
1563            None,
1564            r#"permit(principal, action, resource)
1565when {
1566    {
1567      "myfield":
1568          {
1569            "secondfield":
1570            if principal.name == "yihong"
1571            then principal
1572            else resource.owner,
1573            "ignored but still important due to errors":
1574            resource.viewer
1575          }
1576    }["myfield"]["secondfield"].name == "pavel"
1577};"#,
1578        )
1579        .expect("should succeed");
1580        pset.add(policy.into()).expect("should succeed");
1581
1582        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1583        let expected = serde_json::json! ( {
1584          "perAction": [
1585            [
1586              {
1587                "principal": "User",
1588                "action": {
1589                  "ty": "Action",
1590                  "eid": "Read"
1591                },
1592                "resource": "Document"
1593              },
1594              {
1595                "trie": [
1596                  [
1597                    {
1598                      "var": "principal"
1599                    },
1600                    {
1601                      "children": [
1602                        [
1603                          "name",
1604                          {
1605                            "children": [],
1606                            "ancestorsTrie": { "trie": []},
1607                            "isAncestor": false
1608                          }
1609                        ]
1610                      ],
1611                      "ancestorsTrie": { "trie": []},
1612                            "isAncestor": false
1613                    }
1614                  ],
1615                  [
1616                    {
1617                      "var": "resource"
1618                    },
1619                    {
1620                      "children": [
1621                        [
1622                          "viewer",
1623                          {
1624                            "children": [],
1625                            "ancestorsTrie": { "trie": []},
1626                            "isAncestor": false
1627                          }
1628                        ],
1629                        [
1630                          "owner",
1631                          {
1632                            "children": [
1633                              [
1634                                "name",
1635                                {
1636                                  "children": [],
1637                                  "ancestorsTrie": { "trie": []},
1638                                  "isAncestor": false
1639                                }
1640                              ]
1641                            ],
1642                            "ancestorsTrie": { "trie": []},
1643                            "isAncestor": false
1644                          }
1645                        ]
1646                      ],
1647                      "ancestorsTrie": { "trie": []},
1648                      "isAncestor": false
1649                    }
1650                  ]
1651                ]
1652              }
1653            ]
1654          ]
1655        }
1656        );
1657        let expected_manifest =
1658            EntityManifest::from_json_value(expected, validator.schema()).unwrap();
1659        assert_eq!(entity_manifest, expected_manifest);
1660    }
1661
1662    #[test]
1663    fn test_entity_manifest_action_in() {
1664        let mut pset = PolicySet::new();
1665        let policy = parse_policy(
1666            None,
1667            r#"permit(principal, action in Action::"parent", resource);"#,
1668        )
1669        .unwrap();
1670        pset.add(policy.into()).unwrap();
1671
1672        let schema = ValidatorSchema::from_cedarschema_str(
1673            "entity e; action action in parent appliesTo { principal: e, resource: e}; action parent;",
1674            Extensions::all_available(),
1675        )
1676        .unwrap()
1677        .0;
1678        let validator = Validator::new(schema);
1679
1680        let entity_manifest = compute_entity_manifest(&validator, &pset).expect("Should succeed");
1681        let expected = EntityManifest::from_json_value(
1682            serde_json::json!({
1683              "perAction": [
1684                [
1685                  {
1686                    "principal": "e",
1687                    "action": {
1688                      "ty": "Action",
1689                      "eid": "action"
1690                    },
1691                    "resource": "e"
1692                  },
1693                  {
1694                    "trie": [
1695                      [
1696                        {
1697                          "var": "action"
1698                        },
1699                        {
1700                          "children": [ ],
1701                          "ancestorsTrie": {
1702                              "trie": [
1703                                [
1704                                  { "literal": {"ty": "Action", "eid": "parent"} },
1705                                  {
1706                                    "children": [],
1707                                    "isAncestor": true,
1708                                    "ancestorsTrie": { "trie": [] }
1709                                  }
1710                                ]
1711                              ]
1712                          },
1713                          "isAncestor": false
1714                        }
1715                      ],
1716                    ]
1717                  }
1718                ]
1719              ]
1720            }),
1721            validator.schema(),
1722        )
1723        .unwrap();
1724        assert_eq!(entity_manifest, expected);
1725    }
1726}