cedar_policy_validator/
schema.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//! Defines structures for entity type and action id information used by the
18//! validator. The contents of these structures should be populated from and schema
19//! with a few transformations applied to the data. Specifically, the
20//! `member_of` relation from the schema is reversed and the transitive closure is
21//! computed to obtain a `descendants` relation.
22
23use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
24
25use cedar_policy_core::{
26    ast::{Entity, EntityType, EntityUID, Name},
27    entities::{Entities, EntitiesError, TCComputation},
28    extensions::Extensions,
29    transitive_closure::compute_tc,
30};
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33
34use super::NamespaceDefinition;
35use crate::{
36    err::*,
37    human_schema::SchemaWarning,
38    types::{Attributes, EntityRecordKind, OpenTag, Type},
39    SchemaFragment, SchemaType, SchemaTypeVariant, TypeOfAttribute,
40};
41
42mod action;
43pub use action::ValidatorActionId;
44pub(crate) use action::ValidatorApplySpec;
45mod entity_type;
46pub use entity_type::ValidatorEntityType;
47mod namespace_def;
48pub(crate) use namespace_def::is_action_entity_type;
49pub use namespace_def::ValidatorNamespaceDef;
50#[cfg(test)]
51pub(crate) use namespace_def::ACTION_ENTITY_TYPE;
52
53// We do not have a formal model for action attributes, so we disable them by default.
54#[derive(Eq, PartialEq, Copy, Clone, Default)]
55pub enum ActionBehavior {
56    /// Action entities cannot have attributes. Attempting to declare attributes
57    /// will result in a error when constructing the schema.
58    #[default]
59    ProhibitAttributes,
60    /// Action entities may have attributes.
61    PermitAttributes,
62}
63
64#[derive(Debug)]
65pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
66
67impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
68    type Error = SchemaError;
69
70    fn try_into(self) -> Result<ValidatorSchemaFragment> {
71        ValidatorSchemaFragment::from_schema_fragment(
72            self,
73            ActionBehavior::default(),
74            Extensions::all_available(),
75        )
76    }
77}
78
79impl ValidatorSchemaFragment {
80    pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
81        Self(namespaces.into_iter().collect())
82    }
83
84    pub fn from_schema_fragment(
85        fragment: SchemaFragment,
86        action_behavior: ActionBehavior,
87        extensions: Extensions<'_>,
88    ) -> Result<Self> {
89        Ok(Self(
90            fragment
91                .0
92                .into_iter()
93                .map(|(fragment_ns, ns_def)| {
94                    ValidatorNamespaceDef::from_namespace_definition(
95                        fragment_ns,
96                        ns_def,
97                        action_behavior,
98                        extensions,
99                    )
100                })
101                .collect::<Result<Vec<_>>>()?,
102        ))
103    }
104
105    /// Access the `Name`s for the namespaces in this fragment.
106    pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
107        self.0.iter().map(|d| d.namespace())
108    }
109}
110
111#[serde_as]
112#[derive(Clone, Debug, Serialize)]
113pub struct ValidatorSchema {
114    /// Map from entity type names to the ValidatorEntityType object.
115    #[serde(rename = "entityTypes")]
116    #[serde_as(as = "Vec<(_, _)>")]
117    entity_types: HashMap<Name, ValidatorEntityType>,
118
119    /// Map from action id names to the ValidatorActionId object.
120    #[serde(rename = "actionIds")]
121    #[serde_as(as = "Vec<(_, _)>")]
122    action_ids: HashMap<EntityUID, ValidatorActionId>,
123}
124
125impl std::str::FromStr for ValidatorSchema {
126    type Err = SchemaError;
127
128    fn from_str(s: &str) -> Result<Self> {
129        serde_json::from_str::<SchemaFragment>(s)?.try_into()
130    }
131}
132
133impl TryFrom<NamespaceDefinition> for ValidatorSchema {
134    type Error = SchemaError;
135
136    fn try_from(nsd: NamespaceDefinition) -> Result<ValidatorSchema> {
137        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
138            nsd.try_into()?
139        ])])
140    }
141}
142
143impl TryFrom<SchemaFragment> for ValidatorSchema {
144    type Error = SchemaError;
145
146    fn try_from(frag: SchemaFragment) -> Result<ValidatorSchema> {
147        ValidatorSchema::from_schema_fragments([frag.try_into()?])
148    }
149}
150
151impl ValidatorSchema {
152    /// Returns an iterator over every entity type that can be a principal for any action in this schema
153    pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
154        self.action_ids
155            .values()
156            .flat_map(ValidatorActionId::principals)
157    }
158
159    /// Returns an iterator over every entity type that can be a resource for any action in this schema
160    pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
161        self.action_ids
162            .values()
163            .flat_map(ValidatorActionId::resources)
164    }
165
166    /// Returns an iterator over every entity type that can be a principal for `action` in this schema
167    ///
168    /// # Errors
169    ///
170    /// Returns [`None`] if `action` is not found in the schema
171    pub fn principals_for_action(
172        &self,
173        action: &EntityUID,
174    ) -> Option<impl Iterator<Item = &EntityType>> {
175        self.action_ids
176            .get(action)
177            .map(ValidatorActionId::principals)
178    }
179
180    /// Returns an iterator over every entity type that can be a resource for `action` in this schema
181    ///
182    /// # Errors
183    ///
184    /// Returns [`None`] if `action` is not found in the schema
185    pub fn resources_for_action(
186        &self,
187        action: &EntityUID,
188    ) -> Option<impl Iterator<Item = &EntityType>> {
189        self.action_ids
190            .get(action)
191            .map(ValidatorActionId::resources)
192    }
193
194    /// Returns an iterator over all the entity types that can be a parent of `ty`
195    ///
196    /// # Errors
197    ///
198    /// Returns [`None`] if the `ty` is not found in the schema
199    pub fn ancestors<'a>(&'a self, ty: &'a Name) -> Option<impl Iterator<Item = &'a Name> + 'a> {
200        if self.entity_types.contains_key(ty) {
201            Some(self.entity_types.values().filter_map(|ety| {
202                if ety.descendants.contains(ty) {
203                    Some(&ety.name)
204                } else {
205                    None
206                }
207            }))
208        } else {
209            None
210        }
211    }
212
213    /// Returns an iterator over all the action groups defined in this schema
214    pub fn action_groups(&self) -> impl Iterator<Item = &EntityUID> {
215        self.action_ids.values().filter_map(|action| {
216            if action.descendants.is_empty() {
217                None
218            } else {
219                Some(&action.name)
220            }
221        })
222    }
223
224    /// Returns an iterator over all actions defined in this schema
225    pub fn actions(&self) -> impl Iterator<Item = &EntityUID> {
226        self.action_ids.keys()
227    }
228
229    /// Create a [`ValidatorSchema`] without any definitions (of entity types,
230    /// common types, or actions).
231    pub fn empty() -> ValidatorSchema {
232        Self {
233            entity_types: HashMap::new(),
234            action_ids: HashMap::new(),
235        }
236    }
237
238    /// Construct a `ValidatorSchema` from a JSON value (which should be an
239    /// object matching the `SchemaFileFormat` shape).
240    pub fn from_json_value(json: serde_json::Value, extensions: Extensions<'_>) -> Result<Self> {
241        Self::from_schema_file(
242            SchemaFragment::from_json_value(json)?,
243            ActionBehavior::default(),
244            extensions,
245        )
246    }
247
248    /// Construct a `ValidatorSchema` directly from a file.
249    pub fn from_file(file: impl std::io::Read, extensions: Extensions<'_>) -> Result<Self> {
250        Self::from_schema_file(
251            SchemaFragment::from_file(file)?,
252            ActionBehavior::default(),
253            extensions,
254        )
255    }
256
257    pub fn from_file_natural(
258        r: impl std::io::Read,
259        extensions: Extensions<'_>,
260    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
261        let (fragment, warnings) = SchemaFragment::from_file_natural(r)?;
262        let schema_and_warnings =
263            Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
264                .map(|schema| (schema, warnings))?;
265        Ok(schema_and_warnings)
266    }
267
268    pub fn from_str_natural(
269        src: &str,
270        extensions: Extensions<'_>,
271    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning>), HumanSchemaError> {
272        let (fragment, warnings) = SchemaFragment::from_str_natural(src)?;
273        let schema_and_warnings =
274            Self::from_schema_file(fragment, ActionBehavior::default(), extensions)
275                .map(|schema| (schema, warnings))?;
276        Ok(schema_and_warnings)
277    }
278
279    pub fn from_schema_file(
280        schema_file: SchemaFragment,
281        action_behavior: ActionBehavior,
282        extensions: Extensions<'_>,
283    ) -> Result<ValidatorSchema> {
284        Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
285            schema_file,
286            action_behavior,
287            extensions,
288        )?])
289    }
290
291    /// Construct a new `ValidatorSchema` from some number of schema fragments.
292    pub fn from_schema_fragments(
293        fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
294    ) -> Result<ValidatorSchema> {
295        let mut type_defs = HashMap::new();
296        let mut entity_type_fragments = HashMap::new();
297        let mut action_fragments = HashMap::new();
298
299        for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
300            // Build aggregate maps for the declared typedefs, entity types, and
301            // actions, checking that nothing is defined twice.  Namespaces were
302            // already added by the `ValidatorNamespaceDef`, so the same base
303            // type name may appear multiple times so long as the namespaces are
304            // different.
305            for (name, ty) in ns_def.type_defs.type_defs {
306                match type_defs.entry(name) {
307                    Entry::Vacant(v) => v.insert(ty),
308                    Entry::Occupied(o) => {
309                        return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
310                    }
311                };
312            }
313
314            for (name, entity_type) in ns_def.entity_types.entity_types {
315                match entity_type_fragments.entry(name) {
316                    Entry::Vacant(v) => v.insert(entity_type),
317                    Entry::Occupied(o) => {
318                        return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
319                    }
320                };
321            }
322
323            for (action_euid, action) in ns_def.actions.actions {
324                match action_fragments.entry(action_euid) {
325                    Entry::Vacant(v) => v.insert(action),
326                    Entry::Occupied(o) => {
327                        return Err(SchemaError::DuplicateAction(o.key().to_string()))
328                    }
329                };
330            }
331        }
332
333        let resolver = CommonTypeResolver::new(&type_defs);
334        let type_defs = resolver.resolve()?;
335
336        // Invert the `parents` relation defined by entities and action so far
337        // to get a `children` relation.
338        let mut entity_children = HashMap::new();
339        for (name, entity_type) in entity_type_fragments.iter() {
340            for parent in entity_type.parents.iter() {
341                entity_children
342                    .entry(parent.clone())
343                    .or_insert_with(HashSet::new)
344                    .insert(name.clone());
345            }
346        }
347
348        let mut entity_types = entity_type_fragments
349            .into_iter()
350            .map(|(name, entity_type)| -> Result<_> {
351                // Keys of the `entity_children` map were values of an
352                // `memberOfTypes` list, so they might not have been declared in
353                // their fragment.  By removing entries from `entity_children`
354                // where the key is a declared name, we will be left with a map
355                // where the keys are undeclared. These keys are used to report
356                // an error when undeclared entity types are referenced inside a
357                // `memberOfTypes` list. The error is reported alongside the
358                // error for any other undeclared entity types by
359                // `check_for_undeclared`.
360                let descendants = entity_children.remove(&name).unwrap_or_default();
361                let (attributes, open_attributes) = Self::record_attributes_or_none(
362                    entity_type.attributes.resolve_type_defs(&type_defs)?,
363                )
364                .ok_or(SchemaError::ContextOrShapeNotRecord(
365                    ContextOrShape::EntityTypeShape(name.clone()),
366                ))?;
367                Ok((
368                    name.clone(),
369                    ValidatorEntityType {
370                        name,
371                        descendants,
372                        attributes,
373                        open_attributes,
374                    },
375                ))
376            })
377            .collect::<Result<HashMap<_, _>>>()?;
378
379        let mut action_children = HashMap::new();
380        for (euid, action) in action_fragments.iter() {
381            for parent in action.parents.iter() {
382                action_children
383                    .entry(parent.clone())
384                    .or_insert_with(HashSet::new)
385                    .insert(euid.clone());
386            }
387        }
388        let mut action_ids = action_fragments
389            .into_iter()
390            .map(|(name, action)| -> Result<_> {
391                let descendants = action_children.remove(&name).unwrap_or_default();
392                let (context, open_context_attributes) =
393                    Self::record_attributes_or_none(action.context.resolve_type_defs(&type_defs)?)
394                        .ok_or(SchemaError::ContextOrShapeNotRecord(
395                            ContextOrShape::ActionContext(name.clone()),
396                        ))?;
397                Ok((
398                    name.clone(),
399                    ValidatorActionId {
400                        name,
401                        applies_to: action.applies_to,
402                        descendants,
403                        context: Type::record_with_attributes(
404                            context.attrs,
405                            open_context_attributes,
406                        ),
407                        attribute_types: action.attribute_types,
408                        attributes: action.attributes,
409                    },
410                ))
411            })
412            .collect::<Result<HashMap<_, _>>>()?;
413
414        // We constructed entity types and actions with child maps, but we need
415        // transitively closed descendants.
416        compute_tc(&mut entity_types, false)?;
417        // Pass `true` here so that we also check that the action hierarchy does
418        // not contain cycles.
419        compute_tc(&mut action_ids, true)?;
420
421        // Return with an error if there is an undeclared entity or action
422        // referenced in any fragment. `{entity,action}_children` are provided
423        // for the `undeclared_parent_{entities,actions}` arguments because
424        // removed keys from these maps as we encountered declarations for the
425        // entity types or actions. Any keys left in the map are therefore
426        // undeclared.
427        Self::check_for_undeclared(
428            &entity_types,
429            entity_children.into_keys(),
430            &action_ids,
431            action_children.into_keys(),
432        )?;
433
434        Ok(ValidatorSchema {
435            entity_types,
436            action_ids,
437        })
438    }
439
440    /// Check that all entity types and actions referenced in the schema are in
441    /// the set of declared entity type or action names. Point of caution: this
442    /// function assumes that all entity types are fully qualified. This is
443    /// handled by the `SchemaFragment` constructor.
444    fn check_for_undeclared(
445        entity_types: &HashMap<Name, ValidatorEntityType>,
446        undeclared_parent_entities: impl IntoIterator<Item = Name>,
447        action_ids: &HashMap<EntityUID, ValidatorActionId>,
448        undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
449    ) -> Result<()> {
450        // When we constructed `entity_types`, we removed entity types from  the
451        // `entity_children` map as we encountered a declaration for that type.
452        // Any entity types left in the map are therefore undeclared. These are
453        // any undeclared entity types which appeared in a `memberOf` list.
454        let mut undeclared_e = undeclared_parent_entities
455            .into_iter()
456            .map(|n| n.to_string())
457            .collect::<HashSet<_>>();
458        // Looking at entity types, we need to check entity references in
459        // attribute types. We already know that all elements of the
460        // `descendants` list were declared because the list is a result of
461        // inverting the `memberOf` relationship which mapped declared entity
462        // types to their parent entity types.
463        for entity_type in entity_types.values() {
464            for (_, attr_typ) in entity_type.attributes() {
465                Self::check_undeclared_in_type(
466                    &attr_typ.attr_type,
467                    entity_types,
468                    &mut undeclared_e,
469                );
470            }
471        }
472
473        // Undeclared actions in a `memberOf` list.
474        let undeclared_a = undeclared_parent_actions
475            .into_iter()
476            .map(|n| n.to_string())
477            .collect::<HashSet<_>>();
478        // For actions, we check entity references in the context attribute
479        // types and `appliesTo` lists. See the `entity_types` loop for why the
480        // `descendants` list is not checked.
481        for action in action_ids.values() {
482            Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
483
484            for p_entity in action.applies_to.applicable_principal_types() {
485                match p_entity {
486                    EntityType::Specified(p_entity) => {
487                        if !entity_types.contains_key(&p_entity) {
488                            undeclared_e.insert(p_entity.to_string());
489                        }
490                    }
491                    EntityType::Unspecified => (),
492                }
493            }
494
495            for r_entity in action.applies_to.applicable_resource_types() {
496                match r_entity {
497                    EntityType::Specified(r_entity) => {
498                        if !entity_types.contains_key(&r_entity) {
499                            undeclared_e.insert(r_entity.to_string());
500                        }
501                    }
502                    EntityType::Unspecified => (),
503                }
504            }
505        }
506        if !undeclared_e.is_empty() {
507            return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
508        }
509        if !undeclared_a.is_empty() {
510            return Err(SchemaError::UndeclaredActions(undeclared_a));
511        }
512
513        Ok(())
514    }
515
516    fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
517        match ty {
518            Type::EntityOrRecord(EntityRecordKind::Record {
519                attrs,
520                open_attributes,
521            }) => Some((attrs, open_attributes)),
522            _ => None,
523        }
524    }
525
526    // Check that all entity types appearing inside a type are in the set of
527    // declared entity types, adding any undeclared entity types to the
528    // `undeclared_types` set.
529    fn check_undeclared_in_type(
530        ty: &Type,
531        entity_types: &HashMap<Name, ValidatorEntityType>,
532        undeclared_types: &mut HashSet<String>,
533    ) {
534        match ty {
535            Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
536                for name in lub.iter() {
537                    if !entity_types.contains_key(name) {
538                        undeclared_types.insert(name.to_string());
539                    }
540                }
541            }
542
543            Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
544                for (_, attr_ty) in attrs.iter() {
545                    Self::check_undeclared_in_type(
546                        &attr_ty.attr_type,
547                        entity_types,
548                        undeclared_types,
549                    );
550                }
551            }
552
553            Type::Set {
554                element_type: Some(element_type),
555            } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
556
557            _ => (),
558        }
559    }
560
561    /// Lookup the ValidatorActionId object in the schema with the given name.
562    pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
563        self.action_ids.get(action_id)
564    }
565
566    /// Lookup the ValidatorEntityType object in the schema with the given name.
567    pub fn get_entity_type<'a>(&'a self, entity_type_id: &Name) -> Option<&'a ValidatorEntityType> {
568        self.entity_types.get(entity_type_id)
569    }
570
571    /// Return true when the entity_type_id corresponds to a valid entity type.
572    pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
573        self.action_ids.contains_key(action_id)
574    }
575
576    /// Return true when the entity_type_id corresponds to a valid entity type.
577    pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
578        is_action_entity_type(entity_type) || self.entity_types.contains_key(entity_type)
579    }
580
581    /// Return true when `euid` has an entity type declared by the schema. We
582    /// treat an Unspecified as "known" because it is always possible to declare
583    /// an action using an unspecified principal/resource type without first
584    /// declaring unspecified as an entity type in the entity types list.
585    pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
586        match euid.entity_type() {
587            EntityType::Specified(ety) => self.is_known_entity_type(ety),
588            EntityType::Unspecified => true,
589        }
590    }
591
592    /// An iterator over the action ids in the schema.
593    pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
594        self.action_ids.keys()
595    }
596
597    /// An iterator over the entity type names in the schema.
598    pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
599        self.entity_types.keys()
600    }
601
602    /// An iterator matching the entity Types to their Validator Types
603    pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
604        self.entity_types.iter()
605    }
606
607    /// Get all entity types in the schema where an `{entity0} in {entity}` can
608    /// evaluate to `true` for some `entity0` with that entity type. This
609    /// includes all entity types that are descendants of the type of `entity`
610    /// according  to the schema, and the type of `entity` itself because
611    /// `entity in entity` evaluates to `true`.
612    pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&'a Name> {
613        match entity.entity_type() {
614            EntityType::Specified(ety) => {
615                let mut descendants = self
616                    .get_entity_type(ety)
617                    .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
618                    .unwrap_or_default();
619                descendants.push(ety);
620                descendants
621            }
622            EntityType::Unspecified => Vec::new(),
623        }
624    }
625
626    /// Get all entity types in the schema where an `{entity0} in {euids}` can
627    /// evaluate to `true` for some `entity0` with that entity type. See comment
628    /// on `get_entity_types_in`.
629    pub(crate) fn get_entity_types_in_set<'a>(
630        &'a self,
631        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
632    ) -> impl Iterator<Item = &'a Name> {
633        euids.into_iter().flat_map(|e| self.get_entity_types_in(e))
634    }
635
636    /// Get all action entities in the schema where `action in euids` evaluates
637    /// to `true`. This includes all actions which are descendants of some
638    /// element of `euids`, and all elements of `euids`.
639    pub(crate) fn get_actions_in_set<'a>(
640        &'a self,
641        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
642    ) -> Option<Vec<&'a EntityUID>> {
643        euids
644            .into_iter()
645            .map(|e| {
646                self.get_action_id(e).map(|action| {
647                    action
648                        .descendants
649                        .iter()
650                        .chain(std::iter::once(&action.name))
651                })
652            })
653            .collect::<Option<Vec<_>>>()
654            .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
655    }
656
657    /// Get the `Type` of context expected for the given `action`.
658    /// This always reutrns a closed record type.
659    ///
660    /// Returns `None` if the action is not in the schema.
661    pub fn context_type(&self, action: &EntityUID) -> Option<Type> {
662        // INVARIANT: `ValidatorActionId::context_type` always returns a closed
663        // record type
664        self.get_action_id(action)
665            .map(ValidatorActionId::context_type)
666    }
667
668    /// Invert the action hierarchy to get the ancestor relation expected for
669    /// the `Entity` datatype instead of descendants as stored by the schema.
670    pub(crate) fn action_entities_iter(
671        &self,
672    ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
673        // We could store the un-inverted `memberOf` relation for each action,
674        // but I [john-h-kastner-aws] judge that the current implementation is
675        // actually less error prone, as it minimizes the threading of data
676        // structures through some complicated bits of schema construction code,
677        // and avoids computing the TC twice.
678        let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
679        for (action_euid, action_def) in &self.action_ids {
680            for descendant in &action_def.descendants {
681                action_ancestors
682                    .entry(descendant)
683                    .or_default()
684                    .insert(action_euid.clone());
685            }
686        }
687        self.action_ids.iter().map(move |(action_id, action)| {
688            Entity::new_with_attr_partial_value_serialized_as_expr(
689                action_id.clone(),
690                action.attributes.clone(),
691                action_ancestors.remove(action_id).unwrap_or_default(),
692            )
693        })
694    }
695
696    /// Construct an `Entity` object for each action in the schema
697    pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
698        let extensions = Extensions::all_available();
699        Entities::from_entities(
700            self.action_entities_iter(),
701            None::<&cedar_policy_core::entities::NoEntitiesSchema>, // we don't want to tell `Entities::from_entities()` to add the schema's action entities, that would infinitely recurse
702            TCComputation::AssumeAlreadyComputed,
703            extensions,
704        )
705        .map_err(Into::into)
706    }
707}
708
709/// Used to write a schema implicitly overriding the default handling of action
710/// groups.
711#[derive(Debug, Clone, Deserialize)]
712#[serde(transparent)]
713#[allow(dead_code)]
714pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
715
716impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
717    type Error = SchemaError;
718
719    fn try_into(self) -> Result<ValidatorSchema> {
720        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
721            ValidatorNamespaceDef::from_namespace_definition(
722                None,
723                self.0,
724                crate::ActionBehavior::PermitAttributes,
725                Extensions::all_available(),
726            )?,
727        ])])
728    }
729}
730
731/// A common type reference resolver
732#[derive(Debug)]
733struct CommonTypeResolver<'a> {
734    /// Common type declarations to resolve
735    type_defs: &'a HashMap<Name, SchemaType>,
736    /// The dependency graph among common type names
737    /// The graph contains a vertex for each `Name` and `graph.get(u)` gives the set of vertices `v` for which `(u,v)` is a directed edge in the graph
738    /// A common type name is prefixed with the namespace id where it's declared
739    graph: HashMap<Name, HashSet<Name>>,
740}
741
742impl<'a> CommonTypeResolver<'a> {
743    /// Construct a the resolver
744    fn new(type_defs: &'a HashMap<Name, SchemaType>) -> Self {
745        let mut graph = HashMap::new();
746        for (name, ty) in type_defs {
747            graph.insert(
748                name.clone(),
749                HashSet::from_iter(ty.common_type_references()),
750            );
751        }
752        Self { type_defs, graph }
753    }
754
755    /// Perform topological sort on the dependency graph
756    /// Let A -> B denote the RHS of type `A` refers to type `B` (i.e., `A`
757    /// depends on `B`)
758    /// topo_sort(A -> B -> C) produces [C, B, A]
759    /// If there is a cycle, a type name involving in this cycle is the error
760    /// It implements a variant of Kahn's algorithm
761    fn topo_sort(&self) -> std::result::Result<Vec<Name>, Name> {
762        // The in-degree map
763        // Note that the keys of this map may be a superset of all common type
764        // names
765        let mut indegrees: HashMap<&Name, usize> = HashMap::new();
766        for (ty_name, deps) in self.graph.iter() {
767            // Ensure that declared common types have values in `indegrees`
768            indegrees.entry(ty_name).or_insert(0);
769            for dep in deps {
770                match indegrees.entry(dep) {
771                    std::collections::hash_map::Entry::Occupied(mut o) => {
772                        o.insert(o.get() + 1);
773                    }
774                    std::collections::hash_map::Entry::Vacant(v) => {
775                        v.insert(1);
776                    }
777                }
778            }
779        }
780
781        // The set that contains type names with zero incoming edges
782        let mut work_set: HashSet<&Name> = HashSet::new();
783        let mut res: Vec<Name> = Vec::new();
784
785        // Find all type names with zero in coming edges
786        for (name, degree) in indegrees.iter() {
787            let name = *name;
788            if *degree == 0 {
789                work_set.insert(name);
790                // The result only contains *declared* type names
791                if self.graph.contains_key(name) {
792                    res.push(name.clone());
793                }
794            }
795        }
796
797        // Pop a node
798        while let Some(name) = work_set.iter().next().cloned() {
799            work_set.remove(name);
800            if let Some(deps) = self.graph.get(name) {
801                for dep in deps {
802                    if let Some(degree) = indegrees.get_mut(dep) {
803                        // There will not be any underflows here because
804                        // in order for the in-degree to underflow, `dep`'s
805                        // in-degree must be 0 at this point
806                        // The only possibility where a node's in-degree
807                        // becomes 0 is through the subtraction below, which
808                        // means it has been visited and hence has 0 in-degrees
809                        // In other words, all its in-coming edges have been
810                        // "removed" and hence contradicts with the fact that
811                        // one of them is being "removed"
812                        *degree -= 1;
813                        if *degree == 0 {
814                            work_set.insert(dep);
815                            if self.graph.contains_key(dep) {
816                                res.push(dep.clone());
817                            }
818                        }
819                    }
820                }
821            }
822        }
823
824        // The set of nodes that have not been added to the result
825        // i.e., there are still in-coming edges and hence exists a cycle
826        let mut set: HashSet<&Name> = HashSet::from_iter(self.graph.keys().clone());
827        for name in res.iter() {
828            set.remove(name);
829        }
830
831        if let Some(cycle) = set.into_iter().next() {
832            Err(cycle.clone())
833        } else {
834            // We need to reverse the result because, e.g.,
835            // `res` is now [A,B,C] for A -> B -> C because no one depends on A
836            res.reverse();
837            Ok(res)
838        }
839    }
840
841    // Substitute common type references in `ty` according to `resolve_table`
842    fn resolve_type(
843        resolve_table: &HashMap<&Name, SchemaType>,
844        ty: SchemaType,
845    ) -> Result<SchemaType> {
846        match ty {
847            SchemaType::TypeDef { type_name } => resolve_table
848                .get(&type_name)
849                .ok_or(SchemaError::UndeclaredCommonTypes(HashSet::from_iter(
850                    std::iter::once(type_name.to_string()),
851                )))
852                .cloned(),
853            SchemaType::Type(SchemaTypeVariant::Set { element }) => {
854                Ok(SchemaType::Type(SchemaTypeVariant::Set {
855                    element: Box::new(Self::resolve_type(resolve_table, *element)?),
856                }))
857            }
858            SchemaType::Type(SchemaTypeVariant::Record {
859                attributes,
860                additional_attributes,
861            }) => Ok(SchemaType::Type(SchemaTypeVariant::Record {
862                attributes: BTreeMap::from_iter(
863                    attributes
864                        .into_iter()
865                        .map(|(attr, attr_ty)| {
866                            Ok((
867                                attr,
868                                TypeOfAttribute {
869                                    required: attr_ty.required,
870                                    ty: Self::resolve_type(resolve_table, attr_ty.ty)?,
871                                },
872                            ))
873                        })
874                        .collect::<Result<Vec<(_, _)>>>()?,
875                ),
876                additional_attributes,
877            })),
878            _ => Ok(ty),
879        }
880    }
881
882    // Resolve common type references
883    fn resolve(&self) -> Result<HashMap<Name, Type>> {
884        let sorted_names = self
885            .topo_sort()
886            .map_err(SchemaError::CycleInCommonTypeReferences)?;
887
888        let mut resolve_table = HashMap::new();
889        let mut tys = HashMap::new();
890
891        for name in sorted_names.iter() {
892            let ns: Option<Name> = if name.is_unqualified() {
893                None
894            } else {
895                // PANIC SAFETY: The namespace of qualified names should be a valid name
896                #[allow(clippy::unwrap_used)]
897                Some(name.namespace().parse().unwrap())
898            };
899            // PANIC SAFETY: `name.basename()` should be an existing common type id
900            #[allow(clippy::unwrap_used)]
901            let ty = self.type_defs.get(name).unwrap();
902            let substituted_ty = Self::resolve_type(&resolve_table, ty.clone())?;
903            resolve_table.insert(name, substituted_ty.clone());
904            tys.insert(
905                name.clone(),
906                ValidatorNamespaceDef::try_schema_type_into_validator_type(
907                    ns.as_ref(),
908                    substituted_ty,
909                )?
910                .resolve_type_defs(&HashMap::new())?,
911            );
912        }
913
914        Ok(tys)
915    }
916}
917
918// PANIC SAFETY unit tests
919#[allow(clippy::panic)]
920// PANIC SAFETY unit tests
921#[allow(clippy::indexing_slicing)]
922#[cfg(test)]
923mod test {
924    use std::{collections::BTreeMap, str::FromStr};
925
926    use crate::types::Type;
927    use crate::{SchemaType, SchemaTypeVariant};
928
929    use cedar_policy_core::ast::RestrictedExpr;
930    use cool_asserts::assert_matches;
931    use serde_json::json;
932
933    use super::*;
934
935    // Well-formed schema
936    #[test]
937    fn test_from_schema_file() {
938        let src = json!(
939        {
940            "entityTypes": {
941                "User": {
942                    "memberOfTypes": [ "Group" ]
943                },
944                "Group": {
945                    "memberOfTypes": []
946                },
947                "Photo": {
948                    "memberOfTypes": [ "Album" ]
949                },
950                "Album": {
951                    "memberOfTypes": []
952                }
953            },
954            "actions": {
955                "view_photo": {
956                    "appliesTo": {
957                        "principalTypes": ["User", "Group"],
958                        "resourceTypes": ["Photo"]
959                    }
960                }
961            }
962        });
963        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
964        let schema: Result<ValidatorSchema> = schema_file.try_into();
965        assert!(schema.is_ok());
966    }
967
968    // Duplicate entity "Photo"
969    #[test]
970    fn test_from_schema_file_duplicate_entity() {
971        // Test written using `from_str` instead of `from_value` because the
972        // `json!` macro silently ignores duplicate map keys.
973        let src = r#"
974        {"": {
975            "entityTypes": {
976                "User": {
977                    "memberOfTypes": [ "Group" ]
978                },
979                "Group": {
980                    "memberOfTypes": []
981                },
982                "Photo": {
983                    "memberOfTypes": [ "Album" ]
984                },
985                "Photo": {
986                    "memberOfTypes": []
987                }
988            },
989            "actions": {
990                "view_photo": {
991                    "memberOf": [],
992                    "appliesTo": {
993                        "principalTypes": ["User", "Group"],
994                        "resourceTypes": ["Photo"]
995                    }
996                }
997            }
998        }}"#;
999
1000        match ValidatorSchema::from_str(src) {
1001            Err(SchemaError::Serde(_)) => (),
1002            _ => panic!("Expected serde error due to duplicate entity type."),
1003        }
1004    }
1005
1006    // Duplicate action "view_photo"
1007    #[test]
1008    fn test_from_schema_file_duplicate_action() {
1009        // Test written using `from_str` instead of `from_value` because the
1010        // `json!` macro silently ignores duplicate map keys.
1011        let src = r#"
1012        {"": {
1013            "entityTypes": {
1014                "User": {
1015                    "memberOfTypes": [ "Group" ]
1016                },
1017                "Group": {
1018                    "memberOfTypes": []
1019                },
1020                "Photo": {
1021                    "memberOfTypes": []
1022                }
1023            },
1024            "actions": {
1025                "view_photo": {
1026                    "memberOf": [],
1027                    "appliesTo": {
1028                        "principalTypes": ["User", "Group"],
1029                        "resourceTypes": ["Photo"]
1030                    }
1031                },
1032                "view_photo": { }
1033            }
1034        }"#;
1035        match ValidatorSchema::from_str(src) {
1036            Err(SchemaError::Serde(_)) => (),
1037            _ => panic!("Expected serde error due to duplicate action type."),
1038        }
1039    }
1040
1041    // Undefined entity types "Grop", "Usr", "Phoot"
1042    #[test]
1043    fn test_from_schema_file_undefined_entities() {
1044        let src = json!(
1045        {
1046            "entityTypes": {
1047                "User": {
1048                    "memberOfTypes": [ "Grop" ]
1049                },
1050                "Group": {
1051                    "memberOfTypes": []
1052                },
1053                "Photo": {
1054                    "memberOfTypes": []
1055                }
1056            },
1057            "actions": {
1058                "view_photo": {
1059                    "appliesTo": {
1060                        "principalTypes": ["Usr", "Group"],
1061                        "resourceTypes": ["Phoot"]
1062                    }
1063                }
1064            }
1065        });
1066        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1067        let schema: Result<ValidatorSchema> = schema_file.try_into();
1068        match schema {
1069            Ok(_) => panic!("from_schema_file should have failed"),
1070            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1071                assert_eq!(v.len(), 3)
1072            }
1073            _ => panic!("Unexpected error from from_schema_file"),
1074        }
1075    }
1076
1077    #[test]
1078    fn undefined_entity_namespace_member_of() {
1079        let src = json!(
1080        {"Foo": {
1081            "entityTypes": {
1082                "User": {
1083                    "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1084                },
1085                "Group": { }
1086            },
1087            "actions": {}
1088        }});
1089        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1090        let schema: Result<ValidatorSchema> = schema_file.try_into();
1091        match schema {
1092            Ok(_) => panic!("try_into should have failed"),
1093            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1094                assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1095            }
1096            _ => panic!("Unexpected error from try_into"),
1097        }
1098    }
1099
1100    #[test]
1101    fn undefined_entity_namespace_applies_to() {
1102        let src = json!(
1103        {"Foo": {
1104            "entityTypes": { "User": { }, "Photo": { } },
1105            "actions": {
1106                "view_photo": {
1107                    "appliesTo": {
1108                        "principalTypes": ["Foo::User", "Bar::User"],
1109                        "resourceTypes": ["Photo", "Bar::Photo"],
1110                    }
1111                }
1112            }
1113        }});
1114        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1115        let schema: Result<ValidatorSchema> = schema_file.try_into();
1116        match schema {
1117            Ok(_) => panic!("try_into should have failed"),
1118            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1119                assert_eq!(
1120                    v,
1121                    HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1122                )
1123            }
1124            _ => panic!("Unexpected error from try_into"),
1125        }
1126    }
1127
1128    // Undefined action "photo_actions"
1129    #[test]
1130    fn test_from_schema_file_undefined_action() {
1131        let src = json!(
1132        {
1133            "entityTypes": {
1134                "User": {
1135                    "memberOfTypes": [ "Group" ]
1136                },
1137                "Group": {
1138                    "memberOfTypes": []
1139                },
1140                "Photo": {
1141                    "memberOfTypes": []
1142                }
1143            },
1144            "actions": {
1145                "view_photo": {
1146                    "memberOf": [ {"id": "photo_action"} ],
1147                    "appliesTo": {
1148                        "principalTypes": ["User", "Group"],
1149                        "resourceTypes": ["Photo"]
1150                    }
1151                }
1152            }
1153        });
1154        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1155        let schema: Result<ValidatorSchema> = schema_file.try_into();
1156        match schema {
1157            Ok(_) => panic!("from_schema_file should have failed"),
1158            Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1159            _ => panic!("Unexpected error from from_schema_file"),
1160        }
1161    }
1162
1163    // Trivial cycle in action hierarchy
1164    // view_photo -> view_photo
1165    #[test]
1166    fn test_from_schema_file_action_cycle1() {
1167        let src = json!(
1168        {
1169            "entityTypes": {},
1170            "actions": {
1171                "view_photo": {
1172                    "memberOf": [ {"id": "view_photo"} ]
1173                }
1174            }
1175        });
1176        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1177        let schema: Result<ValidatorSchema> = schema_file.try_into();
1178        assert_matches!(
1179            schema,
1180            Err(SchemaError::CycleInActionHierarchy(euid)) => {
1181                assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
1182            }
1183        )
1184    }
1185
1186    // Slightly more complex cycle in action hierarchy
1187    // view_photo -> edit_photo -> delete_photo -> view_photo
1188    #[test]
1189    fn test_from_schema_file_action_cycle2() {
1190        let src = json!(
1191        {
1192            "entityTypes": {},
1193            "actions": {
1194                "view_photo": {
1195                    "memberOf": [ {"id": "edit_photo"} ]
1196                },
1197                "edit_photo": {
1198                    "memberOf": [ {"id": "delete_photo"} ]
1199                },
1200                "delete_photo": {
1201                    "memberOf": [ {"id": "view_photo"} ]
1202                },
1203                "other_action": {
1204                    "memberOf": [ {"id": "edit_photo"} ]
1205                }
1206            }
1207        });
1208        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1209        let schema: Result<ValidatorSchema> = schema_file.try_into();
1210        assert_matches!(
1211            schema,
1212            // The exact action reported as being in the cycle isn't deterministic.
1213            Err(SchemaError::CycleInActionHierarchy(_)),
1214        )
1215    }
1216
1217    #[test]
1218    fn namespaced_schema() {
1219        let src = r#"
1220        { "N::S": {
1221            "entityTypes": {
1222                "User": {},
1223                "Photo": {}
1224            },
1225            "actions": {
1226                "view_photo": {
1227                    "appliesTo": {
1228                        "principalTypes": ["User"],
1229                        "resourceTypes": ["Photo"]
1230                    }
1231                }
1232            }
1233        } }
1234        "#;
1235        let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1236        let schema: ValidatorSchema = schema_file
1237            .try_into()
1238            .expect("Namespaced schema failed to convert.");
1239        dbg!(&schema);
1240        let user_entity_type = &"N::S::User"
1241            .parse()
1242            .expect("Namespaced entity type should have parsed");
1243        let photo_entity_type = &"N::S::Photo"
1244            .parse()
1245            .expect("Namespaced entity type should have parsed");
1246        assert!(
1247            schema.entity_types.contains_key(user_entity_type),
1248            "Expected and entity type User."
1249        );
1250        assert!(
1251            schema.entity_types.contains_key(photo_entity_type),
1252            "Expected an entity type Photo."
1253        );
1254        assert_eq!(
1255            schema.entity_types.len(),
1256            2,
1257            "Expected exactly 2 entity types."
1258        );
1259        assert!(
1260            schema.action_ids.contains_key(
1261                &"N::S::Action::\"view_photo\""
1262                    .parse()
1263                    .expect("Namespaced action should have parsed")
1264            ),
1265            "Expected an action \"view_photo\"."
1266        );
1267        assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1268
1269        let apply_spec = &schema
1270            .action_ids
1271            .values()
1272            .next()
1273            .expect("Expected Action")
1274            .applies_to;
1275        assert_eq!(
1276            apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1277            vec![&EntityType::Specified(user_entity_type.clone())]
1278        );
1279        assert_eq!(
1280            apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1281            vec![&EntityType::Specified(photo_entity_type.clone())]
1282        );
1283    }
1284
1285    #[test]
1286    fn cant_use_namespace_in_entity_type() {
1287        let src = r#"
1288        {
1289            "entityTypes": { "NS::User": {} },
1290            "actions": {}
1291        }
1292        "#;
1293        let schema_file: std::result::Result<NamespaceDefinition, _> = serde_json::from_str(src);
1294        assert!(schema_file.is_err());
1295    }
1296
1297    #[test]
1298    fn entity_attribute_entity_type_with_namespace() {
1299        let schema_json: SchemaFragment = serde_json::from_str(
1300            r#"
1301            {"A::B": {
1302                "entityTypes": {
1303                    "Foo": {
1304                        "shape": {
1305                            "type": "Record",
1306                            "attributes": {
1307                                "name": { "type": "Entity", "name": "C::D::Foo" }
1308                            }
1309                        }
1310                    }
1311                },
1312                "actions": {}
1313              }}
1314            "#,
1315        )
1316        .expect("Expected valid schema");
1317
1318        let schema: Result<ValidatorSchema> = schema_json.try_into();
1319        match schema {
1320            Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1321                assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1322            }
1323            _ => panic!("Schema construction should have failed due to undeclared entity type."),
1324        }
1325    }
1326
1327    #[test]
1328    fn entity_attribute_entity_type_with_declared_namespace() {
1329        let schema_json: SchemaFragment = serde_json::from_str(
1330            r#"
1331            {"A::B": {
1332                "entityTypes": {
1333                    "Foo": {
1334                        "shape": {
1335                            "type": "Record",
1336                            "attributes": {
1337                                "name": { "type": "Entity", "name": "A::B::Foo" }
1338                            }
1339                        }
1340                    }
1341                },
1342                "actions": {}
1343              }}
1344            "#,
1345        )
1346        .expect("Expected valid schema");
1347
1348        let schema: ValidatorSchema = schema_json
1349            .try_into()
1350            .expect("Expected schema to construct without error.");
1351
1352        let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1353        let foo_type = schema
1354            .entity_types
1355            .get(&foo_name)
1356            .expect("Expected to find entity");
1357        let name_type = foo_type
1358            .attr("name")
1359            .expect("Expected attribute name")
1360            .attr_type
1361            .clone();
1362        let expected_name_type = Type::named_entity_reference(foo_name);
1363        assert_eq!(name_type, expected_name_type);
1364    }
1365
1366    #[test]
1367    fn cannot_declare_action_type_when_prohibited() {
1368        let schema_json: NamespaceDefinition = serde_json::from_str(
1369            r#"
1370            {
1371                "entityTypes": { "Action": {} },
1372                "actions": {}
1373              }
1374            "#,
1375        )
1376        .expect("Expected valid schema");
1377
1378        let schema: Result<ValidatorSchema> = schema_json.try_into();
1379        assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1380    }
1381
1382    #[test]
1383    fn can_declare_other_type_when_action_type_prohibited() {
1384        let schema_json: NamespaceDefinition = serde_json::from_str(
1385            r#"
1386            {
1387                "entityTypes": { "Foo": { } },
1388                "actions": {}
1389              }
1390            "#,
1391        )
1392        .expect("Expected valid schema");
1393
1394        TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1395    }
1396
1397    #[test]
1398    fn cannot_declare_action_in_group_when_prohibited() {
1399        let schema_json: SchemaFragment = serde_json::from_str(
1400            r#"
1401            {"": {
1402                "entityTypes": {},
1403                "actions": {
1404                    "universe": { },
1405                    "view_photo": {
1406                        "attributes": {"id": "universe"}
1407                    },
1408                    "edit_photo": {
1409                        "attributes": {"id": "universe"}
1410                    },
1411                    "delete_photo": {
1412                        "attributes": {"id": "universe"}
1413                    }
1414                }
1415              }}
1416            "#,
1417        )
1418        .expect("Expected valid schema");
1419
1420        let schema = ValidatorSchemaFragment::from_schema_fragment(
1421            schema_json,
1422            ActionBehavior::ProhibitAttributes,
1423            Extensions::all_available(),
1424        );
1425        match schema {
1426            Err(SchemaError::UnsupportedFeature(UnsupportedFeature::ActionAttributes(actions))) => {
1427                assert_eq!(
1428                    actions.into_iter().collect::<HashSet<_>>(),
1429                    HashSet::from([
1430                        "view_photo".to_string(),
1431                        "edit_photo".to_string(),
1432                        "delete_photo".to_string(),
1433                    ])
1434                )
1435            }
1436            _ => panic!("Did not see expected error."),
1437        }
1438    }
1439
1440    #[test]
1441    fn test_entity_type_no_namespace() {
1442        let src = json!({"type": "Entity", "name": "Foo"});
1443        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1444        assert_eq!(
1445            schema_ty,
1446            SchemaType::Type(SchemaTypeVariant::Entity {
1447                name: "Foo".parse().unwrap()
1448            })
1449        );
1450        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1451            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1452            schema_ty,
1453        )
1454        .expect("Error converting schema type to type.")
1455        .resolve_type_defs(&HashMap::new())
1456        .unwrap();
1457        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1458    }
1459
1460    #[test]
1461    fn test_entity_type_namespace() {
1462        let src = json!({"type": "Entity", "name": "NS::Foo"});
1463        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1464        assert_eq!(
1465            schema_ty,
1466            SchemaType::Type(SchemaTypeVariant::Entity {
1467                name: "NS::Foo".parse().unwrap()
1468            })
1469        );
1470        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1471            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1472            schema_ty,
1473        )
1474        .expect("Error converting schema type to type.")
1475        .resolve_type_defs(&HashMap::new())
1476        .unwrap();
1477        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1478    }
1479
1480    #[test]
1481    fn test_entity_type_namespace_parse_error() {
1482        let src = json!({"type": "Entity", "name": "::Foo"});
1483        let schema_ty: std::result::Result<SchemaType, _> = serde_json::from_value(src);
1484        assert!(schema_ty.is_err());
1485    }
1486
1487    #[test]
1488    fn schema_type_record_is_validator_type_record() {
1489        let src = json!({"type": "Record", "attributes": {}});
1490        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1491        assert_eq!(
1492            schema_ty,
1493            SchemaType::Type(SchemaTypeVariant::Record {
1494                attributes: BTreeMap::new(),
1495                additional_attributes: false,
1496            }),
1497        );
1498        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
1499            .expect("Error converting schema type to type.")
1500            .resolve_type_defs(&HashMap::new())
1501            .unwrap();
1502        assert_eq!(ty, Type::closed_record_with_attributes(None));
1503    }
1504
1505    #[test]
1506    fn get_namespaces() {
1507        let fragment: SchemaFragment = serde_json::from_value(json!({
1508            "Foo::Bar::Baz": {
1509                "entityTypes": {},
1510                "actions": {}
1511            },
1512            "Foo": {
1513                "entityTypes": {},
1514                "actions": {}
1515            },
1516            "Bar": {
1517                "entityTypes": {},
1518                "actions": {}
1519            },
1520        }))
1521        .unwrap();
1522
1523        let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
1524        assert_eq!(
1525            schema_fragment
1526                .0
1527                .iter()
1528                .map(|f| f.namespace())
1529                .collect::<HashSet<_>>(),
1530            HashSet::from([
1531                &Some("Foo::Bar::Baz".parse().unwrap()),
1532                &Some("Foo".parse().unwrap()),
1533                &Some("Bar".parse().unwrap())
1534            ])
1535        );
1536    }
1537
1538    #[test]
1539    fn schema_no_fragments() {
1540        let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
1541        assert!(schema.entity_types.is_empty());
1542        assert!(schema.action_ids.is_empty());
1543    }
1544
1545    #[test]
1546    fn same_action_different_namespace() {
1547        let fragment: SchemaFragment = serde_json::from_value(json!({
1548            "Foo::Bar": {
1549                "entityTypes": {},
1550                "actions": {
1551                    "Baz": {}
1552                }
1553            },
1554            "Bar::Foo": {
1555                "entityTypes": {},
1556                "actions": {
1557                    "Baz": { }
1558                }
1559            },
1560            "Biz": {
1561                "entityTypes": {},
1562                "actions": {
1563                    "Baz": { }
1564                }
1565            }
1566        }))
1567        .unwrap();
1568
1569        let schema: ValidatorSchema = fragment.try_into().unwrap();
1570        assert!(schema
1571            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1572            .is_some());
1573        assert!(schema
1574            .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1575            .is_some());
1576        assert!(schema
1577            .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
1578            .is_some());
1579    }
1580
1581    #[test]
1582    fn same_type_different_namespace() {
1583        let fragment: SchemaFragment = serde_json::from_value(json!({
1584            "Foo::Bar": {
1585                "entityTypes": {"Baz" : {}},
1586                "actions": { }
1587            },
1588            "Bar::Foo": {
1589                "entityTypes": {"Baz" : {}},
1590                "actions": { }
1591            },
1592            "Biz": {
1593                "entityTypes": {"Baz" : {}},
1594                "actions": { }
1595            }
1596        }))
1597        .unwrap();
1598        let schema: ValidatorSchema = fragment.try_into().unwrap();
1599
1600        assert!(schema
1601            .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
1602            .is_some());
1603        assert!(schema
1604            .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
1605            .is_some());
1606        assert!(schema
1607            .get_entity_type(&"Biz::Baz".parse().unwrap())
1608            .is_some());
1609    }
1610
1611    #[test]
1612    fn member_of_different_namespace() {
1613        let fragment: SchemaFragment = serde_json::from_value(json!({
1614            "Bar": {
1615                "entityTypes": {
1616                    "Baz": {
1617                        "memberOfTypes": ["Foo::Buz"]
1618                    }
1619                },
1620                "actions": {}
1621            },
1622            "Foo": {
1623                "entityTypes": { "Buz": {} },
1624                "actions": { }
1625            }
1626        }))
1627        .unwrap();
1628        let schema: ValidatorSchema = fragment.try_into().unwrap();
1629
1630        let buz = schema
1631            .get_entity_type(&"Foo::Buz".parse().unwrap())
1632            .unwrap();
1633        assert_eq!(
1634            buz.descendants,
1635            HashSet::from(["Bar::Baz".parse().unwrap()])
1636        );
1637    }
1638
1639    #[test]
1640    fn attribute_different_namespace() {
1641        let fragment: SchemaFragment = serde_json::from_value(json!({
1642            "Bar": {
1643                "entityTypes": {
1644                    "Baz": {
1645                        "shape": {
1646                            "type": "Record",
1647                            "attributes": {
1648                                "fiz": {
1649                                    "type": "Entity",
1650                                    "name": "Foo::Buz"
1651                                }
1652                            }
1653                        }
1654                    }
1655                },
1656                "actions": {}
1657            },
1658            "Foo": {
1659                "entityTypes": { "Buz": {} },
1660                "actions": { }
1661            }
1662        }))
1663        .unwrap();
1664
1665        let schema: ValidatorSchema = fragment.try_into().unwrap();
1666        let baz = schema
1667            .get_entity_type(&"Bar::Baz".parse().unwrap())
1668            .unwrap();
1669        assert_eq!(
1670            baz.attr("fiz").unwrap().attr_type,
1671            Type::named_entity_reference_from_str("Foo::Buz"),
1672        );
1673    }
1674
1675    #[test]
1676    fn applies_to_different_namespace() {
1677        let fragment: SchemaFragment = serde_json::from_value(json!({
1678            "Foo::Bar": {
1679                "entityTypes": { },
1680                "actions": {
1681                    "Baz": {
1682                        "appliesTo": {
1683                            "principalTypes": [ "Fiz::Buz" ],
1684                            "resourceTypes": [ "Fiz::Baz" ],
1685                        }
1686                    }
1687                }
1688            },
1689            "Fiz": {
1690                "entityTypes": {
1691                    "Buz": {},
1692                    "Baz": {}
1693                },
1694                "actions": { }
1695            }
1696        }))
1697        .unwrap();
1698        let schema: ValidatorSchema = fragment.try_into().unwrap();
1699
1700        let baz = schema
1701            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1702            .unwrap();
1703        assert_eq!(
1704            baz.applies_to
1705                .applicable_principal_types()
1706                .collect::<HashSet<_>>(),
1707            HashSet::from([&EntityType::Specified("Fiz::Buz".parse().unwrap())])
1708        );
1709        assert_eq!(
1710            baz.applies_to
1711                .applicable_resource_types()
1712                .collect::<HashSet<_>>(),
1713            HashSet::from([&EntityType::Specified("Fiz::Baz".parse().unwrap())])
1714        );
1715    }
1716
1717    #[test]
1718    fn simple_defined_type() {
1719        let fragment: SchemaFragment = serde_json::from_value(json!({
1720            "": {
1721                "commonTypes": {
1722                    "MyLong": {"type": "Long"}
1723                },
1724                "entityTypes": {
1725                    "User": {
1726                        "shape": {
1727                            "type": "Record",
1728                            "attributes": {
1729                                "a": {"type": "MyLong"}
1730                            }
1731                        }
1732                    }
1733                },
1734                "actions": {}
1735            }
1736        }))
1737        .unwrap();
1738        let schema: ValidatorSchema = fragment.try_into().unwrap();
1739        assert_eq!(
1740            schema.entity_types.iter().next().unwrap().1.attributes,
1741            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1742        );
1743    }
1744
1745    #[test]
1746    fn defined_record_as_attrs() {
1747        let fragment: SchemaFragment = serde_json::from_value(json!({
1748            "": {
1749                "commonTypes": {
1750                    "MyRecord": {
1751                        "type": "Record",
1752                        "attributes":  {
1753                            "a": {"type": "Long"}
1754                        }
1755                    }
1756                },
1757                "entityTypes": {
1758                    "User": { "shape": { "type": "MyRecord", } }
1759                },
1760                "actions": {}
1761            }
1762        }))
1763        .unwrap();
1764        let schema: ValidatorSchema = fragment.try_into().unwrap();
1765        assert_eq!(
1766            schema.entity_types.iter().next().unwrap().1.attributes,
1767            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1768        );
1769    }
1770
1771    #[test]
1772    fn cross_namespace_type() {
1773        let fragment: SchemaFragment = serde_json::from_value(json!({
1774            "A": {
1775                "commonTypes": {
1776                    "MyLong": {"type": "Long"}
1777                },
1778                "entityTypes": { },
1779                "actions": {}
1780            },
1781            "B": {
1782                "entityTypes": {
1783                    "User": {
1784                        "shape": {
1785                            "type": "Record",
1786                            "attributes": {
1787                                "a": {"type": "A::MyLong"}
1788                            }
1789                        }
1790                    }
1791                },
1792                "actions": {}
1793            }
1794        }))
1795        .unwrap();
1796        let schema: ValidatorSchema = fragment.try_into().unwrap();
1797        assert_eq!(
1798            schema.entity_types.iter().next().unwrap().1.attributes,
1799            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1800        );
1801    }
1802
1803    #[test]
1804    fn cross_fragment_type() {
1805        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1806            "A": {
1807                "commonTypes": {
1808                    "MyLong": {"type": "Long"}
1809                },
1810                "entityTypes": { },
1811                "actions": {}
1812            }
1813        }))
1814        .unwrap()
1815        .try_into()
1816        .unwrap();
1817        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1818            "A": {
1819                "entityTypes": {
1820                    "User": {
1821                        "shape": {
1822                            "type": "Record",
1823                            "attributes": {
1824                                "a": {"type": "MyLong"}
1825                            }
1826                        }
1827                    }
1828                },
1829                "actions": {}
1830            }
1831        }))
1832        .unwrap()
1833        .try_into()
1834        .unwrap();
1835        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
1836
1837        assert_eq!(
1838            schema.entity_types.iter().next().unwrap().1.attributes,
1839            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1840        );
1841    }
1842
1843    #[test]
1844    fn cross_fragment_duplicate_type() {
1845        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1846            "A": {
1847                "commonTypes": {
1848                    "MyLong": {"type": "Long"}
1849                },
1850                "entityTypes": {},
1851                "actions": {}
1852            }
1853        }))
1854        .unwrap()
1855        .try_into()
1856        .unwrap();
1857        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1858            "A": {
1859                "commonTypes": {
1860                    "MyLong": {"type": "Long"}
1861                },
1862                "entityTypes": {},
1863                "actions": {}
1864            }
1865        }))
1866        .unwrap()
1867        .try_into()
1868        .unwrap();
1869
1870        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]);
1871
1872        match schema {
1873            Err(SchemaError::DuplicateCommonType(s)) if s.contains("A::MyLong") => (),
1874            _ => panic!("should have errored because schema fragments have duplicate types"),
1875        };
1876    }
1877
1878    #[test]
1879    fn undeclared_type_in_attr() {
1880        let fragment: SchemaFragment = serde_json::from_value(json!({
1881            "": {
1882                "commonTypes": { },
1883                "entityTypes": {
1884                    "User": {
1885                        "shape": {
1886                            "type": "Record",
1887                            "attributes": {
1888                                "a": {"type": "MyLong"}
1889                            }
1890                        }
1891                    }
1892                },
1893                "actions": {}
1894            }
1895        }))
1896        .unwrap();
1897        match TryInto::<ValidatorSchema>::try_into(fragment) {
1898            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1899            s => panic!(
1900                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1901                s
1902            ),
1903        }
1904    }
1905
1906    #[test]
1907    fn undeclared_type_in_type_def() {
1908        let fragment: SchemaFragment = serde_json::from_value(json!({
1909            "": {
1910                "commonTypes": {
1911                    "a": { "type": "b" }
1912                },
1913                "entityTypes": { },
1914                "actions": {}
1915            }
1916        }))
1917        .unwrap();
1918        match TryInto::<ValidatorSchema>::try_into(fragment) {
1919            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1920            s => panic!(
1921                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1922                s
1923            ),
1924        }
1925    }
1926
1927    #[test]
1928    fn shape_not_record() {
1929        let fragment: SchemaFragment = serde_json::from_value(json!({
1930            "": {
1931                "commonTypes": {
1932                    "MyLong": { "type": "Long" }
1933                },
1934                "entityTypes": {
1935                    "User": {
1936                        "shape": { "type": "MyLong" }
1937                    }
1938                },
1939                "actions": {}
1940            }
1941        }))
1942        .unwrap();
1943        match TryInto::<ValidatorSchema>::try_into(fragment) {
1944            Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
1945            s => panic!(
1946                "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
1947                s
1948            ),
1949        }
1950    }
1951
1952    /// This test checks for regressions on (adapted versions of) the examples
1953    /// mentioned in the thread at
1954    /// [cedar#134](https://github.com/cedar-policy/cedar/pull/134)
1955    #[test]
1956    fn counterexamples_from_cedar_134() {
1957        // non-normalized entity type name
1958        let bad1 = json!({
1959            "": {
1960                "entityTypes": {
1961                    "User // comment": {
1962                        "memberOfTypes": [
1963                            "UserGroup"
1964                        ]
1965                    },
1966                    "User": {
1967                        "memberOfTypes": [
1968                            "UserGroup"
1969                        ]
1970                    },
1971                    "UserGroup": {}
1972                },
1973                "actions": {}
1974            }
1975        });
1976        let fragment = serde_json::from_value::<SchemaFragment>(bad1); // should this fail in the future?
1977                                                                       // The future has come?
1978        assert!(fragment.is_err());
1979
1980        // non-normalized schema namespace
1981        let bad2 = json!({
1982            "ABC     :: //comment \n XYZ  ": {
1983                "entityTypes": {
1984                    "User": {
1985                        "memberOfTypes": []
1986                    }
1987                },
1988                "actions": {}
1989            }
1990        });
1991        let fragment = serde_json::from_value::<SchemaFragment>(bad2); // should this fail in the future?
1992                                                                       // The future has come?
1993        assert!(fragment.is_err());
1994    }
1995
1996    #[test]
1997    fn simple_action_entity() {
1998        let src = json!(
1999        {
2000            "entityTypes": { },
2001            "actions": {
2002                "view_photo": { },
2003            }
2004        });
2005
2006        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2007        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2008        let actions = schema.action_entities().expect("Entity Construct Error");
2009
2010        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2011        let view_photo = actions.entity(&action_uid);
2012        assert_eq!(
2013            view_photo.unwrap(),
2014            &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
2015        );
2016    }
2017
2018    #[test]
2019    fn action_entity_hierarchy() {
2020        let src = json!(
2021        {
2022            "entityTypes": { },
2023            "actions": {
2024                "read": {},
2025                "view": {
2026                    "memberOf": [{"id": "read"}]
2027                },
2028                "view_photo": {
2029                    "memberOf": [{"id": "view"}]
2030                },
2031            }
2032        });
2033
2034        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2035        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2036        let actions = schema.action_entities().expect("Entity Construct Error");
2037
2038        let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2039        let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2040        let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2041
2042        let view_photo_entity = actions.entity(&view_photo_uid);
2043        assert_eq!(
2044            view_photo_entity.unwrap(),
2045            &Entity::new_with_attr_partial_value(
2046                view_photo_uid,
2047                HashMap::new(),
2048                HashSet::from([view_uid.clone(), read_uid.clone()])
2049            )
2050        );
2051
2052        let view_entity = actions.entity(&view_uid);
2053        assert_eq!(
2054            view_entity.unwrap(),
2055            &Entity::new_with_attr_partial_value(
2056                view_uid,
2057                HashMap::new(),
2058                HashSet::from([read_uid.clone()])
2059            )
2060        );
2061
2062        let read_entity = actions.entity(&read_uid);
2063        assert_eq!(
2064            read_entity.unwrap(),
2065            &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
2066        );
2067    }
2068
2069    #[test]
2070    fn action_entity_attribute() {
2071        let src = json!(
2072        {
2073            "entityTypes": { },
2074            "actions": {
2075                "view_photo": {
2076                    "attributes": { "attr": "foo" }
2077                },
2078            }
2079        });
2080
2081        let schema_file: NamespaceDefinitionWithActionAttributes =
2082            serde_json::from_value(src).expect("Parse Error");
2083        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2084        let actions = schema.action_entities().expect("Entity Construct Error");
2085
2086        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2087        let view_photo = actions.entity(&action_uid);
2088        assert_eq!(
2089            view_photo.unwrap(),
2090            &Entity::new(
2091                action_uid,
2092                HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2093                HashSet::new(),
2094                &Extensions::none(),
2095            )
2096            .unwrap(),
2097        );
2098    }
2099
2100    #[test]
2101    fn test_action_namespace_inference_multi_success() {
2102        let src = json!({
2103            "Foo" : {
2104                "entityTypes" : {},
2105                "actions" : {
2106                    "read" : {}
2107                }
2108            },
2109            "ExampleCo::Personnel" : {
2110                "entityTypes" : {},
2111                "actions" : {
2112                    "viewPhoto" : {
2113                        "memberOf" : [
2114                            {
2115                                "id" : "read",
2116                                "type" : "Foo::Action"
2117                            }
2118                        ]
2119                    }
2120                }
2121            },
2122        });
2123        let schema_fragment =
2124            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2125        let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2126        let view_photo = schema
2127            .action_entities_iter()
2128            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2129            .unwrap();
2130        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2131        let read = ancestors[0];
2132        assert_eq!(read.eid().to_string(), "read");
2133        assert_eq!(read.entity_type().to_string(), "Foo::Action");
2134    }
2135
2136    #[test]
2137    fn test_action_namespace_inference_multi() {
2138        let src = json!({
2139            "ExampleCo::Personnel::Foo" : {
2140                "entityTypes" : {},
2141                "actions" : {
2142                    "read" : {}
2143                }
2144            },
2145            "ExampleCo::Personnel" : {
2146                "entityTypes" : {},
2147                "actions" : {
2148                    "viewPhoto" : {
2149                        "memberOf" : [
2150                            {
2151                                "id" : "read",
2152                                "type" : "Foo::Action"
2153                            }
2154                        ]
2155                    }
2156                }
2157            },
2158        });
2159        let schema_fragment =
2160            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2161        let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2162        schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2163    }
2164
2165    #[test]
2166    fn test_action_namespace_inference() {
2167        let src = json!({
2168            "ExampleCo::Personnel" : {
2169                "entityTypes" : { },
2170                "actions" : {
2171                    "read" : {},
2172                    "viewPhoto" : {
2173                        "memberOf" : [
2174                            {
2175                                "id" :  "read",
2176                                "type" : "Action"
2177                            }
2178                        ]
2179                    }
2180                }
2181            }
2182        });
2183        let schema_fragment =
2184            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2185        let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2186        let view_photo = schema
2187            .action_entities_iter()
2188            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2189            .unwrap();
2190        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2191        let read = ancestors[0];
2192        assert_eq!(read.eid().to_string(), "read");
2193        assert_eq!(
2194            read.entity_type().to_string(),
2195            "ExampleCo::Personnel::Action"
2196        );
2197    }
2198
2199    #[test]
2200    fn qualified_undeclared_common_types() {
2201        let src = json!(
2202            {
2203                "Demo": {
2204                  "entityTypes": {
2205                    "User": {
2206                      "memberOfTypes": [],
2207                      "shape": {
2208                        "type": "Record",
2209                        "attributes": {
2210                          "id": { "type": "id" },
2211                        }
2212                      }
2213                    }
2214                  },
2215                  "actions": {}
2216                },
2217                "": {
2218                  "commonTypes": {
2219                    "id": {
2220                      "type": "String"
2221                    },
2222                  },
2223                  "entityTypes": {},
2224                  "actions": {}
2225                }
2226              }
2227        );
2228        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2229        assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2230            assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2231    }
2232
2233    #[test]
2234    fn qualified_undeclared_common_types2() {
2235        let src = json!(
2236            {
2237                "Demo": {
2238                  "entityTypes": {
2239                    "User": {
2240                      "memberOfTypes": [],
2241                      "shape": {
2242                        "type": "Record",
2243                        "attributes": {
2244                          "id": { "type": "Demo::id" },
2245                        }
2246                      }
2247                    }
2248                  },
2249                  "actions": {}
2250                },
2251                "": {
2252                  "commonTypes": {
2253                    "id": {
2254                      "type": "String"
2255                    },
2256                  },
2257                  "entityTypes": {},
2258                  "actions": {}
2259                }
2260              }
2261        );
2262        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2263        assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2264            assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2265    }
2266}
2267
2268#[cfg(test)]
2269mod test_resolver {
2270    use std::collections::HashMap;
2271
2272    use cedar_policy_core::ast::Name;
2273    use cool_asserts::assert_matches;
2274
2275    use super::CommonTypeResolver;
2276    use crate::{types::Type, SchemaError, SchemaFragment, ValidatorSchemaFragment};
2277
2278    fn resolve(schema: SchemaFragment) -> Result<HashMap<Name, Type>, SchemaError> {
2279        let schema: ValidatorSchemaFragment = schema.try_into().unwrap();
2280        let mut type_defs = HashMap::new();
2281        for def in schema.0 {
2282            type_defs.extend(def.type_defs.type_defs.into_iter());
2283        }
2284        let resolver = CommonTypeResolver::new(&type_defs);
2285        resolver.resolve()
2286    }
2287
2288    #[test]
2289    fn test_simple() {
2290        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2291            {
2292                "": {
2293                    "entityTypes": {},
2294                    "actions": {},
2295                    "commonTypes": {
2296                        "a" : {
2297                            "type": "b"
2298                        },
2299                        "b": {
2300                            "type": "Boolean"
2301                        }
2302                    }
2303                }
2304            }
2305        ))
2306        .unwrap();
2307        let res = resolve(schema).unwrap();
2308        assert_eq!(
2309            res,
2310            HashMap::from_iter([
2311                ("a".parse().unwrap(), Type::primitive_boolean()),
2312                ("b".parse().unwrap(), Type::primitive_boolean())
2313            ])
2314        );
2315
2316        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2317            {
2318                "": {
2319                    "entityTypes": {},
2320                    "actions": {},
2321                    "commonTypes": {
2322                        "a" : {
2323                            "type": "b"
2324                        },
2325                        "b": {
2326                            "type": "c"
2327                        },
2328                        "c": {
2329                            "type": "Boolean"
2330                        }
2331                    }
2332                }
2333            }
2334        ))
2335        .unwrap();
2336        let res = resolve(schema).unwrap();
2337        assert_eq!(
2338            res,
2339            HashMap::from_iter([
2340                ("a".parse().unwrap(), Type::primitive_boolean()),
2341                ("b".parse().unwrap(), Type::primitive_boolean()),
2342                ("c".parse().unwrap(), Type::primitive_boolean())
2343            ])
2344        );
2345    }
2346
2347    #[test]
2348    fn test_set() {
2349        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2350            {
2351                "": {
2352                    "entityTypes": {},
2353                    "actions": {},
2354                    "commonTypes": {
2355                        "a" : {
2356                            "type": "Set",
2357                            "element": {
2358                                "type": "b"
2359                            }
2360                        },
2361                        "b": {
2362                            "type": "Boolean"
2363                        }
2364                    }
2365                }
2366            }
2367        ))
2368        .unwrap();
2369        let res = resolve(schema).unwrap();
2370        assert_eq!(
2371            res,
2372            HashMap::from_iter([
2373                ("a".parse().unwrap(), Type::set(Type::primitive_boolean())),
2374                ("b".parse().unwrap(), Type::primitive_boolean())
2375            ])
2376        );
2377    }
2378
2379    #[test]
2380    fn test_record() {
2381        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2382            {
2383                "": {
2384                    "entityTypes": {},
2385                    "actions": {},
2386                    "commonTypes": {
2387                        "a" : {
2388                            "type": "Record",
2389                            "attributes": {
2390                                "foo": {
2391                                    "type": "b"
2392                                }
2393                            }
2394                        },
2395                        "b": {
2396                            "type": "Boolean"
2397                        }
2398                    }
2399                }
2400            }
2401        ))
2402        .unwrap();
2403        let res = resolve(schema).unwrap();
2404        assert_eq!(
2405            res,
2406            HashMap::from_iter([
2407                (
2408                    "a".parse().unwrap(),
2409                    Type::record_with_required_attributes(
2410                        std::iter::once(("foo".into(), Type::primitive_boolean())),
2411                        crate::types::OpenTag::ClosedAttributes
2412                    )
2413                ),
2414                ("b".parse().unwrap(), Type::primitive_boolean())
2415            ])
2416        );
2417    }
2418
2419    #[test]
2420    fn test_names() {
2421        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2422            {
2423                "A": {
2424                    "entityTypes": {},
2425                    "actions": {},
2426                    "commonTypes": {
2427                        "a" : {
2428                            "type": "B::a"
2429                        }
2430                    }
2431                },
2432                "B": {
2433                    "entityTypes": {},
2434                    "actions": {},
2435                    "commonTypes": {
2436                        "a" : {
2437                            "type": "Boolean"
2438                        }
2439                    }
2440                }
2441            }
2442        ))
2443        .unwrap();
2444        let res = resolve(schema).unwrap();
2445        assert_eq!(
2446            res,
2447            HashMap::from_iter([
2448                ("A::a".parse().unwrap(), Type::primitive_boolean()),
2449                ("B::a".parse().unwrap(), Type::primitive_boolean())
2450            ])
2451        );
2452    }
2453
2454    #[test]
2455    fn test_cycles() {
2456        // self reference
2457        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2458            {
2459                "": {
2460                    "entityTypes": {},
2461                    "actions": {},
2462                    "commonTypes": {
2463                        "a" : {
2464                            "type": "a"
2465                        }
2466                    }
2467                }
2468            }
2469        ))
2470        .unwrap();
2471        let res = resolve(schema);
2472        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2473
2474        // 2 node loop
2475        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2476            {
2477                "": {
2478                    "entityTypes": {},
2479                    "actions": {},
2480                    "commonTypes": {
2481                        "a" : {
2482                            "type": "b"
2483                        },
2484                        "b" : {
2485                            "type": "a"
2486                        }
2487                    }
2488                }
2489            }
2490        ))
2491        .unwrap();
2492        let res = resolve(schema);
2493        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2494
2495        // 3 node loop
2496        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2497            {
2498                "": {
2499                    "entityTypes": {},
2500                    "actions": {},
2501                    "commonTypes": {
2502                        "a" : {
2503                            "type": "b"
2504                        },
2505                        "b" : {
2506                            "type": "c"
2507                        },
2508                        "c" : {
2509                            "type": "a"
2510                        }
2511                    }
2512                }
2513            }
2514        ))
2515        .unwrap();
2516        let res = resolve(schema);
2517        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2518
2519        // cross-namespace 2 node loop
2520        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2521            {
2522                "A": {
2523                    "entityTypes": {},
2524                    "actions": {},
2525                    "commonTypes": {
2526                        "a" : {
2527                            "type": "B::a"
2528                        }
2529                    }
2530                },
2531                "B": {
2532                    "entityTypes": {},
2533                    "actions": {},
2534                    "commonTypes": {
2535                        "a" : {
2536                            "type": "A::a"
2537                        }
2538                    }
2539                }
2540            }
2541        ))
2542        .unwrap();
2543        let res = resolve(schema);
2544        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2545
2546        // cross-namespace 3 node loop
2547        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2548            {
2549                "A": {
2550                    "entityTypes": {},
2551                    "actions": {},
2552                    "commonTypes": {
2553                        "a" : {
2554                            "type": "B::a"
2555                        }
2556                    }
2557                },
2558                "B": {
2559                    "entityTypes": {},
2560                    "actions": {},
2561                    "commonTypes": {
2562                        "a" : {
2563                            "type": "C::a"
2564                        }
2565                    }
2566                },
2567                "C": {
2568                    "entityTypes": {},
2569                    "actions": {},
2570                    "commonTypes": {
2571                        "a" : {
2572                            "type": "A::a"
2573                        }
2574                    }
2575                }
2576            }
2577        ))
2578        .unwrap();
2579        let res = resolve(schema);
2580        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2581
2582        // cross-namespace 3 node loop
2583        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2584            {
2585                "A": {
2586                    "entityTypes": {},
2587                    "actions": {},
2588                    "commonTypes": {
2589                        "a" : {
2590                            "type": "B::a"
2591                        }
2592                    }
2593                },
2594                "B": {
2595                    "entityTypes": {},
2596                    "actions": {},
2597                    "commonTypes": {
2598                        "a" : {
2599                            "type": "c"
2600                        },
2601                        "c": {
2602                            "type": "A::a"
2603                        }
2604                    }
2605                }
2606            }
2607        ))
2608        .unwrap();
2609        let res = resolve(schema);
2610        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2611    }
2612}
2613
2614#[cfg(test)]
2615mod test_access {
2616    use super::*;
2617
2618    fn schema() -> ValidatorSchema {
2619        let src = r#"
2620        type Task = {
2621    "id": Long,
2622    "name": String,
2623    "state": String,
2624};
2625
2626type Tasks = Set<Task>;
2627entity List in [Application] = {
2628  "editors": Team,
2629  "name": String,
2630  "owner": User,
2631  "readers": Team,
2632  "tasks": Tasks,
2633};
2634entity Application;
2635entity User in [Team, Application] = {
2636  "joblevel": Long,
2637  "location": String,
2638};
2639
2640entity CoolList;
2641
2642entity Team in [Team, Application];
2643
2644action Read, Write, Create;
2645
2646action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
2647    principal: [User],
2648    resource : [List]
2649};
2650
2651action GetList in Read appliesTo {
2652    principal : [User],
2653    resource : [List, CoolList]
2654};
2655
2656action GetLists in Read appliesTo {
2657    principal : [User],
2658    resource : [Application]
2659};
2660
2661action CreateList in Create appliesTo {
2662    principal : [User],
2663    resource : [Application]
2664};
2665
2666        "#;
2667
2668        ValidatorSchema::from_str_natural(src, Extensions::all_available())
2669            .unwrap()
2670            .0
2671    }
2672
2673    #[test]
2674    fn principals() {
2675        let schema = schema();
2676        let principals = schema.principals().collect::<HashSet<_>>();
2677        assert_eq!(principals.len(), 1);
2678        let user: EntityType = EntityType::Specified("User".parse().unwrap());
2679        assert!(principals.contains(&user));
2680        let principals = schema.principals().collect::<Vec<_>>();
2681        assert!(principals.len() > 1);
2682        assert!(principals.iter().all(|ety| **ety == user));
2683    }
2684
2685    #[test]
2686    fn empty_schema_principals_and_resources() {
2687        let empty: ValidatorSchema =
2688            ValidatorSchema::from_str_natural("", Extensions::all_available())
2689                .unwrap()
2690                .0;
2691        assert!(empty.principals().collect::<Vec<_>>().is_empty());
2692        assert!(empty.resources().collect::<Vec<_>>().is_empty());
2693    }
2694
2695    #[test]
2696    fn resources() {
2697        let schema = schema();
2698        let resources = schema.resources().cloned().collect::<HashSet<_>>();
2699        let expected: HashSet<EntityType> = HashSet::from([
2700            EntityType::Specified("List".parse().unwrap()),
2701            EntityType::Specified("Application".parse().unwrap()),
2702            EntityType::Specified("CoolList".parse().unwrap()),
2703        ]);
2704        assert_eq!(resources, expected);
2705    }
2706
2707    #[test]
2708    fn principals_for_action() {
2709        let schema = schema();
2710        let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
2711        let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
2712        let got = schema
2713            .principals_for_action(&delete_list)
2714            .unwrap()
2715            .cloned()
2716            .collect::<Vec<_>>();
2717        assert_eq!(got, vec![EntityType::Specified("User".parse().unwrap())]);
2718        assert!(schema.principals_for_action(&delete_user).is_none());
2719    }
2720
2721    #[test]
2722    fn resources_for_action() {
2723        let schema = schema();
2724        let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
2725        let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
2726        let create_list: EntityUID = r#"Action::"CreateList""#.parse().unwrap();
2727        let get_list: EntityUID = r#"Action::"GetList""#.parse().unwrap();
2728        let got = schema
2729            .resources_for_action(&delete_list)
2730            .unwrap()
2731            .cloned()
2732            .collect::<Vec<_>>();
2733        assert_eq!(got, vec![EntityType::Specified("List".parse().unwrap())]);
2734        let got = schema
2735            .resources_for_action(&create_list)
2736            .unwrap()
2737            .cloned()
2738            .collect::<Vec<_>>();
2739        assert_eq!(
2740            got,
2741            vec![EntityType::Specified("Application".parse().unwrap())]
2742        );
2743        let got = schema
2744            .resources_for_action(&get_list)
2745            .unwrap()
2746            .cloned()
2747            .collect::<HashSet<_>>();
2748        assert_eq!(
2749            got,
2750            HashSet::from([
2751                EntityType::Specified("List".parse().unwrap()),
2752                EntityType::Specified("CoolList".parse().unwrap())
2753            ])
2754        );
2755        assert!(schema.principals_for_action(&delete_user).is_none());
2756    }
2757
2758    #[test]
2759    fn principal_parents() {
2760        let schema = schema();
2761        let user: Name = "User".parse().unwrap();
2762        let parents = schema
2763            .ancestors(&user)
2764            .unwrap()
2765            .cloned()
2766            .collect::<HashSet<_>>();
2767        let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
2768        assert_eq!(parents, expected);
2769        let parents = schema
2770            .ancestors(&"List".parse().unwrap())
2771            .unwrap()
2772            .cloned()
2773            .collect::<HashSet<_>>();
2774        let expected = HashSet::from(["Application".parse().unwrap()]);
2775        assert_eq!(parents, expected);
2776        assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
2777        let parents = schema
2778            .ancestors(&"CoolList".parse().unwrap())
2779            .unwrap()
2780            .cloned()
2781            .collect::<HashSet<_>>();
2782        let expected = HashSet::from([]);
2783        assert_eq!(parents, expected);
2784    }
2785
2786    #[test]
2787    fn action_groups() {
2788        let schema = schema();
2789        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
2790        let expected = ["Read", "Write", "Create"]
2791            .into_iter()
2792            .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
2793            .collect::<HashSet<EntityUID>>();
2794        assert_eq!(groups, expected);
2795    }
2796
2797    #[test]
2798    fn actions() {
2799        let schema = schema();
2800        let actions = schema.actions().cloned().collect::<HashSet<_>>();
2801        let expected = [
2802            "Read",
2803            "Write",
2804            "Create",
2805            "DeleteList",
2806            "EditShare",
2807            "UpdateList",
2808            "CreateTask",
2809            "UpdateTask",
2810            "DeleteTask",
2811            "GetList",
2812            "GetLists",
2813            "CreateList",
2814        ]
2815        .into_iter()
2816        .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
2817        .collect::<HashSet<EntityUID>>();
2818        assert_eq!(actions, expected);
2819    }
2820
2821    #[test]
2822    fn entities() {
2823        let schema = schema();
2824        let entities = schema
2825            .entity_types()
2826            .map(|(ty, _)| ty)
2827            .cloned()
2828            .collect::<HashSet<_>>();
2829        let expected = ["List", "Application", "User", "CoolList", "Team"]
2830            .into_iter()
2831            .map(|ty| ty.parse().unwrap())
2832            .collect::<HashSet<Name>>();
2833        assert_eq!(entities, expected);
2834    }
2835}
2836
2837#[cfg(test)]
2838mod test_access_namespace {
2839    use super::*;
2840
2841    fn schema() -> ValidatorSchema {
2842        let src = r#"
2843        namespace Foo {
2844        type Task = {
2845    "id": Long,
2846    "name": String,
2847    "state": String,
2848};
2849
2850type Tasks = Set<Task>;
2851entity List in [Application] = {
2852  "editors": Team,
2853  "name": String,
2854  "owner": User,
2855  "readers": Team,
2856  "tasks": Tasks,
2857};
2858entity Application;
2859entity User in [Team, Application] = {
2860  "joblevel": Long,
2861  "location": String,
2862};
2863
2864entity CoolList;
2865
2866entity Team in [Team, Application];
2867
2868action Read, Write, Create;
2869
2870action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
2871    principal: [User],
2872    resource : [List]
2873};
2874
2875action GetList in Read appliesTo {
2876    principal : [User],
2877    resource : [List, CoolList]
2878};
2879
2880action GetLists in Read appliesTo {
2881    principal : [User],
2882    resource : [Application]
2883};
2884
2885action CreateList in Create appliesTo {
2886    principal : [User],
2887    resource : [Application]
2888};
2889    }
2890
2891        "#;
2892
2893        ValidatorSchema::from_str_natural(src, Extensions::all_available())
2894            .unwrap()
2895            .0
2896    }
2897
2898    #[test]
2899    fn principals() {
2900        let schema = schema();
2901        let principals = schema.principals().collect::<HashSet<_>>();
2902        assert_eq!(principals.len(), 1);
2903        let user: EntityType = EntityType::Specified("Foo::User".parse().unwrap());
2904        assert!(principals.contains(&user));
2905        let principals = schema.principals().collect::<Vec<_>>();
2906        assert!(principals.len() > 1);
2907        assert!(principals.iter().all(|ety| **ety == user));
2908    }
2909
2910    #[test]
2911    fn empty_schema_principals_and_resources() {
2912        let empty: ValidatorSchema =
2913            ValidatorSchema::from_str_natural("", Extensions::all_available())
2914                .unwrap()
2915                .0;
2916        assert!(empty.principals().collect::<Vec<_>>().is_empty());
2917        assert!(empty.resources().collect::<Vec<_>>().is_empty());
2918    }
2919
2920    #[test]
2921    fn resources() {
2922        let schema = schema();
2923        let resources = schema.resources().cloned().collect::<HashSet<_>>();
2924        let expected: HashSet<EntityType> = HashSet::from([
2925            EntityType::Specified("Foo::List".parse().unwrap()),
2926            EntityType::Specified("Foo::Application".parse().unwrap()),
2927            EntityType::Specified("Foo::CoolList".parse().unwrap()),
2928        ]);
2929        assert_eq!(resources, expected);
2930    }
2931
2932    #[test]
2933    fn principals_for_action() {
2934        let schema = schema();
2935        let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
2936        let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
2937        let got = schema
2938            .principals_for_action(&delete_list)
2939            .unwrap()
2940            .cloned()
2941            .collect::<Vec<_>>();
2942        assert_eq!(
2943            got,
2944            vec![EntityType::Specified("Foo::User".parse().unwrap())]
2945        );
2946        assert!(schema.principals_for_action(&delete_user).is_none());
2947    }
2948
2949    #[test]
2950    fn resources_for_action() {
2951        let schema = schema();
2952        let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
2953        let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
2954        let create_list: EntityUID = r#"Foo::Action::"CreateList""#.parse().unwrap();
2955        let get_list: EntityUID = r#"Foo::Action::"GetList""#.parse().unwrap();
2956        let got = schema
2957            .resources_for_action(&delete_list)
2958            .unwrap()
2959            .cloned()
2960            .collect::<Vec<_>>();
2961        assert_eq!(
2962            got,
2963            vec![EntityType::Specified("Foo::List".parse().unwrap())]
2964        );
2965        let got = schema
2966            .resources_for_action(&create_list)
2967            .unwrap()
2968            .cloned()
2969            .collect::<Vec<_>>();
2970        assert_eq!(
2971            got,
2972            vec![EntityType::Specified("Foo::Application".parse().unwrap())]
2973        );
2974        let got = schema
2975            .resources_for_action(&get_list)
2976            .unwrap()
2977            .cloned()
2978            .collect::<HashSet<_>>();
2979        assert_eq!(
2980            got,
2981            HashSet::from([
2982                EntityType::Specified("Foo::List".parse().unwrap()),
2983                EntityType::Specified("Foo::CoolList".parse().unwrap())
2984            ])
2985        );
2986        assert!(schema.principals_for_action(&delete_user).is_none());
2987    }
2988
2989    #[test]
2990    fn principal_parents() {
2991        let schema = schema();
2992        let user: Name = "Foo::User".parse().unwrap();
2993        let parents = schema
2994            .ancestors(&user)
2995            .unwrap()
2996            .cloned()
2997            .collect::<HashSet<_>>();
2998        let expected = HashSet::from([
2999            "Foo::Team".parse().unwrap(),
3000            "Foo::Application".parse().unwrap(),
3001        ]);
3002        assert_eq!(parents, expected);
3003        let parents = schema
3004            .ancestors(&"Foo::List".parse().unwrap())
3005            .unwrap()
3006            .cloned()
3007            .collect::<HashSet<_>>();
3008        let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
3009        assert_eq!(parents, expected);
3010        assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
3011        let parents = schema
3012            .ancestors(&"Foo::CoolList".parse().unwrap())
3013            .unwrap()
3014            .cloned()
3015            .collect::<HashSet<_>>();
3016        let expected = HashSet::from([]);
3017        assert_eq!(parents, expected);
3018    }
3019
3020    #[test]
3021    fn action_groups() {
3022        let schema = schema();
3023        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
3024        let expected = ["Read", "Write", "Create"]
3025            .into_iter()
3026            .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
3027            .collect::<HashSet<EntityUID>>();
3028        assert_eq!(groups, expected);
3029    }
3030
3031    #[test]
3032    fn actions() {
3033        let schema = schema();
3034        let actions = schema.actions().cloned().collect::<HashSet<_>>();
3035        let expected = [
3036            "Read",
3037            "Write",
3038            "Create",
3039            "DeleteList",
3040            "EditShare",
3041            "UpdateList",
3042            "CreateTask",
3043            "UpdateTask",
3044            "DeleteTask",
3045            "GetList",
3046            "GetLists",
3047            "CreateList",
3048        ]
3049        .into_iter()
3050        .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
3051        .collect::<HashSet<EntityUID>>();
3052        assert_eq!(actions, expected);
3053    }
3054
3055    #[test]
3056    fn entities() {
3057        let schema = schema();
3058        let entities = schema
3059            .entity_types()
3060            .map(|(ty, _)| ty)
3061            .cloned()
3062            .collect::<HashSet<_>>();
3063        let expected = [
3064            "Foo::List",
3065            "Foo::Application",
3066            "Foo::User",
3067            "Foo::CoolList",
3068            "Foo::Team",
3069        ]
3070        .into_iter()
3071        .map(|ty| ty.parse().unwrap())
3072        .collect::<HashSet<Name>>();
3073        assert_eq!(entities, expected);
3074    }
3075}