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 = &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<&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 = &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)]
713pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
714
715impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
716    type Error = SchemaError;
717
718    fn try_into(self) -> Result<ValidatorSchema> {
719        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
720            ValidatorNamespaceDef::from_namespace_definition(
721                None,
722                self.0,
723                crate::ActionBehavior::PermitAttributes,
724                Extensions::all_available(),
725            )?,
726        ])])
727    }
728}
729
730/// A common type reference resolver
731#[derive(Debug)]
732struct CommonTypeResolver<'a> {
733    /// Common type declarations to resolve
734    type_defs: &'a HashMap<Name, SchemaType>,
735    /// The dependency graph among common type names
736    /// 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
737    /// A common type name is prefixed with the namespace id where it's declared
738    graph: HashMap<Name, HashSet<Name>>,
739}
740
741impl<'a> CommonTypeResolver<'a> {
742    /// Construct a the resolver
743    fn new(type_defs: &'a HashMap<Name, SchemaType>) -> Self {
744        let mut graph = HashMap::new();
745        for (name, ty) in type_defs {
746            graph.insert(
747                name.clone(),
748                HashSet::from_iter(ty.common_type_references()),
749            );
750        }
751        Self { type_defs, graph }
752    }
753
754    /// Perform topological sort on the dependency graph
755    /// Let A -> B denote the RHS of type `A` refers to type `B` (i.e., `A`
756    /// depends on `B`)
757    /// topo_sort(A -> B -> C) produces [C, B, A]
758    /// If there is a cycle, a type name involving in this cycle is the error
759    /// It implements a variant of Kahn's algorithm
760    fn topo_sort(&self) -> std::result::Result<Vec<Name>, Name> {
761        // The in-degree map
762        // Note that the keys of this map may be a superset of all common type
763        // names
764        let mut indegrees: HashMap<&Name, usize> = HashMap::new();
765        for (ty_name, deps) in self.graph.iter() {
766            // Ensure that declared common types have values in `indegrees`
767            indegrees.entry(ty_name).or_insert(0);
768            for dep in deps {
769                match indegrees.entry(dep) {
770                    std::collections::hash_map::Entry::Occupied(mut o) => {
771                        o.insert(o.get() + 1);
772                    }
773                    std::collections::hash_map::Entry::Vacant(v) => {
774                        v.insert(1);
775                    }
776                }
777            }
778        }
779
780        // The set that contains type names with zero incoming edges
781        let mut work_set: HashSet<&Name> = HashSet::new();
782        let mut res: Vec<Name> = Vec::new();
783
784        // Find all type names with zero in coming edges
785        for (name, degree) in indegrees.iter() {
786            let name = *name;
787            if *degree == 0 {
788                work_set.insert(name);
789                // The result only contains *declared* type names
790                if self.graph.contains_key(name) {
791                    res.push(name.clone());
792                }
793            }
794        }
795
796        // Pop a node
797        while let Some(name) = work_set.iter().next().cloned() {
798            work_set.remove(name);
799            if let Some(deps) = self.graph.get(name) {
800                for dep in deps {
801                    if let Some(degree) = indegrees.get_mut(dep) {
802                        // There will not be any underflows here because
803                        // in order for the in-degree to underflow, `dep`'s
804                        // in-degree must be 0 at this point
805                        // The only possibility where a node's in-degree
806                        // becomes 0 is through the subtraction below, which
807                        // means it has been visited and hence has 0 in-degrees
808                        // In other words, all its in-coming edges have been
809                        // "removed" and hence contradicts with the fact that
810                        // one of them is being "removed"
811                        *degree -= 1;
812                        if *degree == 0 {
813                            work_set.insert(dep);
814                            if self.graph.contains_key(dep) {
815                                res.push(dep.clone());
816                            }
817                        }
818                    }
819                }
820            }
821        }
822
823        // The set of nodes that have not been added to the result
824        // i.e., there are still in-coming edges and hence exists a cycle
825        let mut set: HashSet<&Name> = HashSet::from_iter(self.graph.keys().clone());
826        for name in res.iter() {
827            set.remove(name);
828        }
829
830        if let Some(cycle) = set.into_iter().next() {
831            Err(cycle.clone())
832        } else {
833            // We need to reverse the result because, e.g.,
834            // `res` is now [A,B,C] for A -> B -> C because no one depends on A
835            res.reverse();
836            Ok(res)
837        }
838    }
839
840    // Substitute common type references in `ty` according to `resolve_table`
841    fn resolve_type(
842        resolve_table: &HashMap<&Name, SchemaType>,
843        ty: SchemaType,
844    ) -> Result<SchemaType> {
845        match ty {
846            SchemaType::TypeDef { type_name } => resolve_table
847                .get(&type_name)
848                .ok_or(SchemaError::UndeclaredCommonTypes(HashSet::from_iter(
849                    std::iter::once(type_name.to_string()),
850                )))
851                .cloned(),
852            SchemaType::Type(SchemaTypeVariant::Set { element }) => {
853                Ok(SchemaType::Type(SchemaTypeVariant::Set {
854                    element: Box::new(Self::resolve_type(resolve_table, *element)?),
855                }))
856            }
857            SchemaType::Type(SchemaTypeVariant::Record {
858                attributes,
859                additional_attributes,
860            }) => Ok(SchemaType::Type(SchemaTypeVariant::Record {
861                attributes: BTreeMap::from_iter(
862                    attributes
863                        .into_iter()
864                        .map(|(attr, attr_ty)| {
865                            Ok((
866                                attr,
867                                TypeOfAttribute {
868                                    required: attr_ty.required,
869                                    ty: Self::resolve_type(resolve_table, attr_ty.ty)?,
870                                },
871                            ))
872                        })
873                        .collect::<Result<Vec<(_, _)>>>()?,
874                ),
875                additional_attributes,
876            })),
877            _ => Ok(ty),
878        }
879    }
880
881    // Resolve common type references
882    fn resolve(&self) -> Result<HashMap<Name, Type>> {
883        let sorted_names = self
884            .topo_sort()
885            .map_err(SchemaError::CycleInCommonTypeReferences)?;
886
887        let mut resolve_table = HashMap::new();
888        let mut tys = HashMap::new();
889
890        for name in sorted_names.iter() {
891            let ns: Option<Name> = if name.is_unqualified() {
892                None
893            } else {
894                // PANIC SAFETY: The namespace of qualified names should be a valid name
895                #[allow(clippy::unwrap_used)]
896                Some(name.namespace().parse().unwrap())
897            };
898            // PANIC SAFETY: `name.basename()` should be an existing common type id
899            #[allow(clippy::unwrap_used)]
900            let ty = self.type_defs.get(name).unwrap();
901            let substituted_ty = Self::resolve_type(&resolve_table, ty.clone())?;
902            resolve_table.insert(name, substituted_ty.clone());
903            tys.insert(
904                name.clone(),
905                ValidatorNamespaceDef::try_schema_type_into_validator_type(
906                    ns.as_ref(),
907                    substituted_ty,
908                )?
909                .resolve_type_defs(&HashMap::new())?,
910            );
911        }
912
913        Ok(tys)
914    }
915}
916
917// PANIC SAFETY unit tests
918#[allow(clippy::panic)]
919// PANIC SAFETY unit tests
920#[allow(clippy::indexing_slicing)]
921#[cfg(test)]
922mod test {
923    use std::{collections::BTreeMap, str::FromStr};
924
925    use crate::types::Type;
926    use crate::{SchemaType, SchemaTypeVariant};
927
928    use cedar_policy_core::ast::RestrictedExpr;
929    use cool_asserts::assert_matches;
930    use serde_json::json;
931
932    use super::*;
933
934    // Well-formed schema
935    #[test]
936    fn test_from_schema_file() {
937        let src = json!(
938        {
939            "entityTypes": {
940                "User": {
941                    "memberOfTypes": [ "Group" ]
942                },
943                "Group": {
944                    "memberOfTypes": []
945                },
946                "Photo": {
947                    "memberOfTypes": [ "Album" ]
948                },
949                "Album": {
950                    "memberOfTypes": []
951                }
952            },
953            "actions": {
954                "view_photo": {
955                    "appliesTo": {
956                        "principalTypes": ["User", "Group"],
957                        "resourceTypes": ["Photo"]
958                    }
959                }
960            }
961        });
962        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
963        let schema: Result<ValidatorSchema> = schema_file.try_into();
964        assert!(schema.is_ok());
965    }
966
967    // Duplicate entity "Photo"
968    #[test]
969    fn test_from_schema_file_duplicate_entity() {
970        // Test written using `from_str` instead of `from_value` because the
971        // `json!` macro silently ignores duplicate map keys.
972        let src = r#"
973        {"": {
974            "entityTypes": {
975                "User": {
976                    "memberOfTypes": [ "Group" ]
977                },
978                "Group": {
979                    "memberOfTypes": []
980                },
981                "Photo": {
982                    "memberOfTypes": [ "Album" ]
983                },
984                "Photo": {
985                    "memberOfTypes": []
986                }
987            },
988            "actions": {
989                "view_photo": {
990                    "memberOf": [],
991                    "appliesTo": {
992                        "principalTypes": ["User", "Group"],
993                        "resourceTypes": ["Photo"]
994                    }
995                }
996            }
997        }}"#;
998
999        match ValidatorSchema::from_str(src) {
1000            Err(SchemaError::Serde(_)) => (),
1001            _ => panic!("Expected serde error due to duplicate entity type."),
1002        }
1003    }
1004
1005    // Duplicate action "view_photo"
1006    #[test]
1007    fn test_from_schema_file_duplicate_action() {
1008        // Test written using `from_str` instead of `from_value` because the
1009        // `json!` macro silently ignores duplicate map keys.
1010        let src = r#"
1011        {"": {
1012            "entityTypes": {
1013                "User": {
1014                    "memberOfTypes": [ "Group" ]
1015                },
1016                "Group": {
1017                    "memberOfTypes": []
1018                },
1019                "Photo": {
1020                    "memberOfTypes": []
1021                }
1022            },
1023            "actions": {
1024                "view_photo": {
1025                    "memberOf": [],
1026                    "appliesTo": {
1027                        "principalTypes": ["User", "Group"],
1028                        "resourceTypes": ["Photo"]
1029                    }
1030                },
1031                "view_photo": { }
1032            }
1033        }"#;
1034        match ValidatorSchema::from_str(src) {
1035            Err(SchemaError::Serde(_)) => (),
1036            _ => panic!("Expected serde error due to duplicate action type."),
1037        }
1038    }
1039
1040    // Undefined entity types "Grop", "Usr", "Phoot"
1041    #[test]
1042    fn test_from_schema_file_undefined_entities() {
1043        let src = json!(
1044        {
1045            "entityTypes": {
1046                "User": {
1047                    "memberOfTypes": [ "Grop" ]
1048                },
1049                "Group": {
1050                    "memberOfTypes": []
1051                },
1052                "Photo": {
1053                    "memberOfTypes": []
1054                }
1055            },
1056            "actions": {
1057                "view_photo": {
1058                    "appliesTo": {
1059                        "principalTypes": ["Usr", "Group"],
1060                        "resourceTypes": ["Phoot"]
1061                    }
1062                }
1063            }
1064        });
1065        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1066        let schema: Result<ValidatorSchema> = schema_file.try_into();
1067        match schema {
1068            Ok(_) => panic!("from_schema_file should have failed"),
1069            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1070                assert_eq!(v.len(), 3)
1071            }
1072            _ => panic!("Unexpected error from from_schema_file"),
1073        }
1074    }
1075
1076    #[test]
1077    fn undefined_entity_namespace_member_of() {
1078        let src = json!(
1079        {"Foo": {
1080            "entityTypes": {
1081                "User": {
1082                    "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1083                },
1084                "Group": { }
1085            },
1086            "actions": {}
1087        }});
1088        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1089        let schema: Result<ValidatorSchema> = schema_file.try_into();
1090        match schema {
1091            Ok(_) => panic!("try_into should have failed"),
1092            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1093                assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1094            }
1095            _ => panic!("Unexpected error from try_into"),
1096        }
1097    }
1098
1099    #[test]
1100    fn undefined_entity_namespace_applies_to() {
1101        let src = json!(
1102        {"Foo": {
1103            "entityTypes": { "User": { }, "Photo": { } },
1104            "actions": {
1105                "view_photo": {
1106                    "appliesTo": {
1107                        "principalTypes": ["Foo::User", "Bar::User"],
1108                        "resourceTypes": ["Photo", "Bar::Photo"],
1109                    }
1110                }
1111            }
1112        }});
1113        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1114        let schema: Result<ValidatorSchema> = schema_file.try_into();
1115        match schema {
1116            Ok(_) => panic!("try_into should have failed"),
1117            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1118                assert_eq!(
1119                    v,
1120                    HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1121                )
1122            }
1123            _ => panic!("Unexpected error from try_into"),
1124        }
1125    }
1126
1127    // Undefined action "photo_actions"
1128    #[test]
1129    fn test_from_schema_file_undefined_action() {
1130        let src = json!(
1131        {
1132            "entityTypes": {
1133                "User": {
1134                    "memberOfTypes": [ "Group" ]
1135                },
1136                "Group": {
1137                    "memberOfTypes": []
1138                },
1139                "Photo": {
1140                    "memberOfTypes": []
1141                }
1142            },
1143            "actions": {
1144                "view_photo": {
1145                    "memberOf": [ {"id": "photo_action"} ],
1146                    "appliesTo": {
1147                        "principalTypes": ["User", "Group"],
1148                        "resourceTypes": ["Photo"]
1149                    }
1150                }
1151            }
1152        });
1153        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1154        let schema: Result<ValidatorSchema> = schema_file.try_into();
1155        match schema {
1156            Ok(_) => panic!("from_schema_file should have failed"),
1157            Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1158            _ => panic!("Unexpected error from from_schema_file"),
1159        }
1160    }
1161
1162    // Trivial cycle in action hierarchy
1163    // view_photo -> view_photo
1164    #[test]
1165    fn test_from_schema_file_action_cycle1() {
1166        let src = json!(
1167        {
1168            "entityTypes": {},
1169            "actions": {
1170                "view_photo": {
1171                    "memberOf": [ {"id": "view_photo"} ]
1172                }
1173            }
1174        });
1175        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1176        let schema: Result<ValidatorSchema> = schema_file.try_into();
1177        assert_matches!(
1178            schema,
1179            Err(SchemaError::CycleInActionHierarchy(euid)) => {
1180                assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
1181            }
1182        )
1183    }
1184
1185    // Slightly more complex cycle in action hierarchy
1186    // view_photo -> edit_photo -> delete_photo -> view_photo
1187    #[test]
1188    fn test_from_schema_file_action_cycle2() {
1189        let src = json!(
1190        {
1191            "entityTypes": {},
1192            "actions": {
1193                "view_photo": {
1194                    "memberOf": [ {"id": "edit_photo"} ]
1195                },
1196                "edit_photo": {
1197                    "memberOf": [ {"id": "delete_photo"} ]
1198                },
1199                "delete_photo": {
1200                    "memberOf": [ {"id": "view_photo"} ]
1201                },
1202                "other_action": {
1203                    "memberOf": [ {"id": "edit_photo"} ]
1204                }
1205            }
1206        });
1207        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1208        let schema: Result<ValidatorSchema> = schema_file.try_into();
1209        assert_matches!(
1210            schema,
1211            // The exact action reported as being in the cycle isn't deterministic.
1212            Err(SchemaError::CycleInActionHierarchy(_)),
1213        )
1214    }
1215
1216    #[test]
1217    fn namespaced_schema() {
1218        let src = r#"
1219        { "N::S": {
1220            "entityTypes": {
1221                "User": {},
1222                "Photo": {}
1223            },
1224            "actions": {
1225                "view_photo": {
1226                    "appliesTo": {
1227                        "principalTypes": ["User"],
1228                        "resourceTypes": ["Photo"]
1229                    }
1230                }
1231            }
1232        } }
1233        "#;
1234        let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1235        let schema: ValidatorSchema = schema_file
1236            .try_into()
1237            .expect("Namespaced schema failed to convert.");
1238        dbg!(&schema);
1239        let user_entity_type = &"N::S::User"
1240            .parse()
1241            .expect("Namespaced entity type should have parsed");
1242        let photo_entity_type = &"N::S::Photo"
1243            .parse()
1244            .expect("Namespaced entity type should have parsed");
1245        assert!(
1246            schema.entity_types.contains_key(user_entity_type),
1247            "Expected and entity type User."
1248        );
1249        assert!(
1250            schema.entity_types.contains_key(photo_entity_type),
1251            "Expected an entity type Photo."
1252        );
1253        assert_eq!(
1254            schema.entity_types.len(),
1255            2,
1256            "Expected exactly 2 entity types."
1257        );
1258        assert!(
1259            schema.action_ids.contains_key(
1260                &"N::S::Action::\"view_photo\""
1261                    .parse()
1262                    .expect("Namespaced action should have parsed")
1263            ),
1264            "Expected an action \"view_photo\"."
1265        );
1266        assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1267
1268        let apply_spec = &schema
1269            .action_ids
1270            .values()
1271            .next()
1272            .expect("Expected Action")
1273            .applies_to;
1274        assert_eq!(
1275            apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1276            vec![&EntityType::Specified(user_entity_type.clone())]
1277        );
1278        assert_eq!(
1279            apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1280            vec![&EntityType::Specified(photo_entity_type.clone())]
1281        );
1282    }
1283
1284    #[test]
1285    fn cant_use_namespace_in_entity_type() {
1286        let src = r#"
1287        {
1288            "entityTypes": { "NS::User": {} },
1289            "actions": {}
1290        }
1291        "#;
1292        let schema_file: std::result::Result<NamespaceDefinition, _> = serde_json::from_str(src);
1293        assert!(schema_file.is_err());
1294    }
1295
1296    #[test]
1297    fn entity_attribute_entity_type_with_namespace() {
1298        let schema_json: SchemaFragment = serde_json::from_str(
1299            r#"
1300            {"A::B": {
1301                "entityTypes": {
1302                    "Foo": {
1303                        "shape": {
1304                            "type": "Record",
1305                            "attributes": {
1306                                "name": { "type": "Entity", "name": "C::D::Foo" }
1307                            }
1308                        }
1309                    }
1310                },
1311                "actions": {}
1312              }}
1313            "#,
1314        )
1315        .expect("Expected valid schema");
1316
1317        let schema: Result<ValidatorSchema> = schema_json.try_into();
1318        match schema {
1319            Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1320                assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1321            }
1322            _ => panic!("Schema construction should have failed due to undeclared entity type."),
1323        }
1324    }
1325
1326    #[test]
1327    fn entity_attribute_entity_type_with_declared_namespace() {
1328        let schema_json: SchemaFragment = serde_json::from_str(
1329            r#"
1330            {"A::B": {
1331                "entityTypes": {
1332                    "Foo": {
1333                        "shape": {
1334                            "type": "Record",
1335                            "attributes": {
1336                                "name": { "type": "Entity", "name": "A::B::Foo" }
1337                            }
1338                        }
1339                    }
1340                },
1341                "actions": {}
1342              }}
1343            "#,
1344        )
1345        .expect("Expected valid schema");
1346
1347        let schema: ValidatorSchema = schema_json
1348            .try_into()
1349            .expect("Expected schema to construct without error.");
1350
1351        let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1352        let foo_type = schema
1353            .entity_types
1354            .get(&foo_name)
1355            .expect("Expected to find entity");
1356        let name_type = foo_type
1357            .attr("name")
1358            .expect("Expected attribute name")
1359            .attr_type
1360            .clone();
1361        let expected_name_type = Type::named_entity_reference(foo_name);
1362        assert_eq!(name_type, expected_name_type);
1363    }
1364
1365    #[test]
1366    fn cannot_declare_action_type_when_prohibited() {
1367        let schema_json: NamespaceDefinition = serde_json::from_str(
1368            r#"
1369            {
1370                "entityTypes": { "Action": {} },
1371                "actions": {}
1372              }
1373            "#,
1374        )
1375        .expect("Expected valid schema");
1376
1377        let schema: Result<ValidatorSchema> = schema_json.try_into();
1378        assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1379    }
1380
1381    #[test]
1382    fn can_declare_other_type_when_action_type_prohibited() {
1383        let schema_json: NamespaceDefinition = serde_json::from_str(
1384            r#"
1385            {
1386                "entityTypes": { "Foo": { } },
1387                "actions": {}
1388              }
1389            "#,
1390        )
1391        .expect("Expected valid schema");
1392
1393        TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1394    }
1395
1396    #[test]
1397    fn cannot_declare_action_in_group_when_prohibited() {
1398        let schema_json: SchemaFragment = serde_json::from_str(
1399            r#"
1400            {"": {
1401                "entityTypes": {},
1402                "actions": {
1403                    "universe": { },
1404                    "view_photo": {
1405                        "attributes": {"id": "universe"}
1406                    },
1407                    "edit_photo": {
1408                        "attributes": {"id": "universe"}
1409                    },
1410                    "delete_photo": {
1411                        "attributes": {"id": "universe"}
1412                    }
1413                }
1414              }}
1415            "#,
1416        )
1417        .expect("Expected valid schema");
1418
1419        let schema = ValidatorSchemaFragment::from_schema_fragment(
1420            schema_json,
1421            ActionBehavior::ProhibitAttributes,
1422            Extensions::all_available(),
1423        );
1424        match schema {
1425            Err(SchemaError::UnsupportedFeature(UnsupportedFeature::ActionAttributes(actions))) => {
1426                assert_eq!(
1427                    actions.into_iter().collect::<HashSet<_>>(),
1428                    HashSet::from([
1429                        "view_photo".to_string(),
1430                        "edit_photo".to_string(),
1431                        "delete_photo".to_string(),
1432                    ])
1433                )
1434            }
1435            _ => panic!("Did not see expected error."),
1436        }
1437    }
1438
1439    #[test]
1440    fn test_entity_type_no_namespace() {
1441        let src = json!({"type": "Entity", "name": "Foo"});
1442        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1443        assert_eq!(
1444            schema_ty,
1445            SchemaType::Type(SchemaTypeVariant::Entity {
1446                name: "Foo".parse().unwrap()
1447            })
1448        );
1449        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1450            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1451            schema_ty,
1452        )
1453        .expect("Error converting schema type to type.")
1454        .resolve_type_defs(&HashMap::new())
1455        .unwrap();
1456        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1457    }
1458
1459    #[test]
1460    fn test_entity_type_namespace() {
1461        let src = json!({"type": "Entity", "name": "NS::Foo"});
1462        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1463        assert_eq!(
1464            schema_ty,
1465            SchemaType::Type(SchemaTypeVariant::Entity {
1466                name: "NS::Foo".parse().unwrap()
1467            })
1468        );
1469        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1470            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1471            schema_ty,
1472        )
1473        .expect("Error converting schema type to type.")
1474        .resolve_type_defs(&HashMap::new())
1475        .unwrap();
1476        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1477    }
1478
1479    #[test]
1480    fn test_entity_type_namespace_parse_error() {
1481        let src = json!({"type": "Entity", "name": "::Foo"});
1482        let schema_ty: std::result::Result<SchemaType, _> = serde_json::from_value(src);
1483        assert!(schema_ty.is_err());
1484    }
1485
1486    #[test]
1487    fn schema_type_record_is_validator_type_record() {
1488        let src = json!({"type": "Record", "attributes": {}});
1489        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1490        assert_eq!(
1491            schema_ty,
1492            SchemaType::Type(SchemaTypeVariant::Record {
1493                attributes: BTreeMap::new(),
1494                additional_attributes: false,
1495            }),
1496        );
1497        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
1498            .expect("Error converting schema type to type.")
1499            .resolve_type_defs(&HashMap::new())
1500            .unwrap();
1501        assert_eq!(ty, Type::closed_record_with_attributes(None));
1502    }
1503
1504    #[test]
1505    fn get_namespaces() {
1506        let fragment: SchemaFragment = serde_json::from_value(json!({
1507            "Foo::Bar::Baz": {
1508                "entityTypes": {},
1509                "actions": {}
1510            },
1511            "Foo": {
1512                "entityTypes": {},
1513                "actions": {}
1514            },
1515            "Bar": {
1516                "entityTypes": {},
1517                "actions": {}
1518            },
1519        }))
1520        .unwrap();
1521
1522        let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
1523        assert_eq!(
1524            schema_fragment
1525                .0
1526                .iter()
1527                .map(|f| f.namespace())
1528                .collect::<HashSet<_>>(),
1529            HashSet::from([
1530                &Some("Foo::Bar::Baz".parse().unwrap()),
1531                &Some("Foo".parse().unwrap()),
1532                &Some("Bar".parse().unwrap())
1533            ])
1534        );
1535    }
1536
1537    #[test]
1538    fn schema_no_fragments() {
1539        let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
1540        assert!(schema.entity_types.is_empty());
1541        assert!(schema.action_ids.is_empty());
1542    }
1543
1544    #[test]
1545    fn same_action_different_namespace() {
1546        let fragment: SchemaFragment = serde_json::from_value(json!({
1547            "Foo::Bar": {
1548                "entityTypes": {},
1549                "actions": {
1550                    "Baz": {}
1551                }
1552            },
1553            "Bar::Foo": {
1554                "entityTypes": {},
1555                "actions": {
1556                    "Baz": { }
1557                }
1558            },
1559            "Biz": {
1560                "entityTypes": {},
1561                "actions": {
1562                    "Baz": { }
1563                }
1564            }
1565        }))
1566        .unwrap();
1567
1568        let schema: ValidatorSchema = fragment.try_into().unwrap();
1569        assert!(schema
1570            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1571            .is_some());
1572        assert!(schema
1573            .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1574            .is_some());
1575        assert!(schema
1576            .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
1577            .is_some());
1578    }
1579
1580    #[test]
1581    fn same_type_different_namespace() {
1582        let fragment: SchemaFragment = serde_json::from_value(json!({
1583            "Foo::Bar": {
1584                "entityTypes": {"Baz" : {}},
1585                "actions": { }
1586            },
1587            "Bar::Foo": {
1588                "entityTypes": {"Baz" : {}},
1589                "actions": { }
1590            },
1591            "Biz": {
1592                "entityTypes": {"Baz" : {}},
1593                "actions": { }
1594            }
1595        }))
1596        .unwrap();
1597        let schema: ValidatorSchema = fragment.try_into().unwrap();
1598
1599        assert!(schema
1600            .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
1601            .is_some());
1602        assert!(schema
1603            .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
1604            .is_some());
1605        assert!(schema
1606            .get_entity_type(&"Biz::Baz".parse().unwrap())
1607            .is_some());
1608    }
1609
1610    #[test]
1611    fn member_of_different_namespace() {
1612        let fragment: SchemaFragment = serde_json::from_value(json!({
1613            "Bar": {
1614                "entityTypes": {
1615                    "Baz": {
1616                        "memberOfTypes": ["Foo::Buz"]
1617                    }
1618                },
1619                "actions": {}
1620            },
1621            "Foo": {
1622                "entityTypes": { "Buz": {} },
1623                "actions": { }
1624            }
1625        }))
1626        .unwrap();
1627        let schema: ValidatorSchema = fragment.try_into().unwrap();
1628
1629        let buz = schema
1630            .get_entity_type(&"Foo::Buz".parse().unwrap())
1631            .unwrap();
1632        assert_eq!(
1633            buz.descendants,
1634            HashSet::from(["Bar::Baz".parse().unwrap()])
1635        );
1636    }
1637
1638    #[test]
1639    fn attribute_different_namespace() {
1640        let fragment: SchemaFragment = serde_json::from_value(json!({
1641            "Bar": {
1642                "entityTypes": {
1643                    "Baz": {
1644                        "shape": {
1645                            "type": "Record",
1646                            "attributes": {
1647                                "fiz": {
1648                                    "type": "Entity",
1649                                    "name": "Foo::Buz"
1650                                }
1651                            }
1652                        }
1653                    }
1654                },
1655                "actions": {}
1656            },
1657            "Foo": {
1658                "entityTypes": { "Buz": {} },
1659                "actions": { }
1660            }
1661        }))
1662        .unwrap();
1663
1664        let schema: ValidatorSchema = fragment.try_into().unwrap();
1665        let baz = schema
1666            .get_entity_type(&"Bar::Baz".parse().unwrap())
1667            .unwrap();
1668        assert_eq!(
1669            baz.attr("fiz").unwrap().attr_type,
1670            Type::named_entity_reference_from_str("Foo::Buz"),
1671        );
1672    }
1673
1674    #[test]
1675    fn applies_to_different_namespace() {
1676        let fragment: SchemaFragment = serde_json::from_value(json!({
1677            "Foo::Bar": {
1678                "entityTypes": { },
1679                "actions": {
1680                    "Baz": {
1681                        "appliesTo": {
1682                            "principalTypes": [ "Fiz::Buz" ],
1683                            "resourceTypes": [ "Fiz::Baz" ],
1684                        }
1685                    }
1686                }
1687            },
1688            "Fiz": {
1689                "entityTypes": {
1690                    "Buz": {},
1691                    "Baz": {}
1692                },
1693                "actions": { }
1694            }
1695        }))
1696        .unwrap();
1697        let schema: ValidatorSchema = fragment.try_into().unwrap();
1698
1699        let baz = schema
1700            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1701            .unwrap();
1702        assert_eq!(
1703            baz.applies_to
1704                .applicable_principal_types()
1705                .collect::<HashSet<_>>(),
1706            HashSet::from([&EntityType::Specified("Fiz::Buz".parse().unwrap())])
1707        );
1708        assert_eq!(
1709            baz.applies_to
1710                .applicable_resource_types()
1711                .collect::<HashSet<_>>(),
1712            HashSet::from([&EntityType::Specified("Fiz::Baz".parse().unwrap())])
1713        );
1714    }
1715
1716    #[test]
1717    fn simple_defined_type() {
1718        let fragment: SchemaFragment = serde_json::from_value(json!({
1719            "": {
1720                "commonTypes": {
1721                    "MyLong": {"type": "Long"}
1722                },
1723                "entityTypes": {
1724                    "User": {
1725                        "shape": {
1726                            "type": "Record",
1727                            "attributes": {
1728                                "a": {"type": "MyLong"}
1729                            }
1730                        }
1731                    }
1732                },
1733                "actions": {}
1734            }
1735        }))
1736        .unwrap();
1737        let schema: ValidatorSchema = fragment.try_into().unwrap();
1738        assert_eq!(
1739            schema.entity_types.iter().next().unwrap().1.attributes,
1740            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1741        );
1742    }
1743
1744    #[test]
1745    fn defined_record_as_attrs() {
1746        let fragment: SchemaFragment = serde_json::from_value(json!({
1747            "": {
1748                "commonTypes": {
1749                    "MyRecord": {
1750                        "type": "Record",
1751                        "attributes":  {
1752                            "a": {"type": "Long"}
1753                        }
1754                    }
1755                },
1756                "entityTypes": {
1757                    "User": { "shape": { "type": "MyRecord", } }
1758                },
1759                "actions": {}
1760            }
1761        }))
1762        .unwrap();
1763        let schema: ValidatorSchema = fragment.try_into().unwrap();
1764        assert_eq!(
1765            schema.entity_types.iter().next().unwrap().1.attributes,
1766            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1767        );
1768    }
1769
1770    #[test]
1771    fn cross_namespace_type() {
1772        let fragment: SchemaFragment = serde_json::from_value(json!({
1773            "A": {
1774                "commonTypes": {
1775                    "MyLong": {"type": "Long"}
1776                },
1777                "entityTypes": { },
1778                "actions": {}
1779            },
1780            "B": {
1781                "entityTypes": {
1782                    "User": {
1783                        "shape": {
1784                            "type": "Record",
1785                            "attributes": {
1786                                "a": {"type": "A::MyLong"}
1787                            }
1788                        }
1789                    }
1790                },
1791                "actions": {}
1792            }
1793        }))
1794        .unwrap();
1795        let schema: ValidatorSchema = fragment.try_into().unwrap();
1796        assert_eq!(
1797            schema.entity_types.iter().next().unwrap().1.attributes,
1798            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1799        );
1800    }
1801
1802    #[test]
1803    fn cross_fragment_type() {
1804        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1805            "A": {
1806                "commonTypes": {
1807                    "MyLong": {"type": "Long"}
1808                },
1809                "entityTypes": { },
1810                "actions": {}
1811            }
1812        }))
1813        .unwrap()
1814        .try_into()
1815        .unwrap();
1816        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1817            "A": {
1818                "entityTypes": {
1819                    "User": {
1820                        "shape": {
1821                            "type": "Record",
1822                            "attributes": {
1823                                "a": {"type": "MyLong"}
1824                            }
1825                        }
1826                    }
1827                },
1828                "actions": {}
1829            }
1830        }))
1831        .unwrap()
1832        .try_into()
1833        .unwrap();
1834        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
1835
1836        assert_eq!(
1837            schema.entity_types.iter().next().unwrap().1.attributes,
1838            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1839        );
1840    }
1841
1842    #[test]
1843    fn cross_fragment_duplicate_type() {
1844        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1845            "A": {
1846                "commonTypes": {
1847                    "MyLong": {"type": "Long"}
1848                },
1849                "entityTypes": {},
1850                "actions": {}
1851            }
1852        }))
1853        .unwrap()
1854        .try_into()
1855        .unwrap();
1856        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1857            "A": {
1858                "commonTypes": {
1859                    "MyLong": {"type": "Long"}
1860                },
1861                "entityTypes": {},
1862                "actions": {}
1863            }
1864        }))
1865        .unwrap()
1866        .try_into()
1867        .unwrap();
1868
1869        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]);
1870
1871        match schema {
1872            Err(SchemaError::DuplicateCommonType(s)) if s.contains("A::MyLong") => (),
1873            _ => panic!("should have errored because schema fragments have duplicate types"),
1874        };
1875    }
1876
1877    #[test]
1878    fn undeclared_type_in_attr() {
1879        let fragment: SchemaFragment = serde_json::from_value(json!({
1880            "": {
1881                "commonTypes": { },
1882                "entityTypes": {
1883                    "User": {
1884                        "shape": {
1885                            "type": "Record",
1886                            "attributes": {
1887                                "a": {"type": "MyLong"}
1888                            }
1889                        }
1890                    }
1891                },
1892                "actions": {}
1893            }
1894        }))
1895        .unwrap();
1896        match TryInto::<ValidatorSchema>::try_into(fragment) {
1897            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1898            s => panic!(
1899                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1900                s
1901            ),
1902        }
1903    }
1904
1905    #[test]
1906    fn undeclared_type_in_type_def() {
1907        let fragment: SchemaFragment = serde_json::from_value(json!({
1908            "": {
1909                "commonTypes": {
1910                    "a": { "type": "b" }
1911                },
1912                "entityTypes": { },
1913                "actions": {}
1914            }
1915        }))
1916        .unwrap();
1917        match TryInto::<ValidatorSchema>::try_into(fragment) {
1918            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1919            s => panic!(
1920                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1921                s
1922            ),
1923        }
1924    }
1925
1926    #[test]
1927    fn shape_not_record() {
1928        let fragment: SchemaFragment = serde_json::from_value(json!({
1929            "": {
1930                "commonTypes": {
1931                    "MyLong": { "type": "Long" }
1932                },
1933                "entityTypes": {
1934                    "User": {
1935                        "shape": { "type": "MyLong" }
1936                    }
1937                },
1938                "actions": {}
1939            }
1940        }))
1941        .unwrap();
1942        match TryInto::<ValidatorSchema>::try_into(fragment) {
1943            Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
1944            s => panic!(
1945                "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
1946                s
1947            ),
1948        }
1949    }
1950
1951    /// This test checks for regressions on (adapted versions of) the examples
1952    /// mentioned in the thread at
1953    /// [cedar#134](https://github.com/cedar-policy/cedar/pull/134)
1954    #[test]
1955    fn counterexamples_from_cedar_134() {
1956        // non-normalized entity type name
1957        let bad1 = json!({
1958            "": {
1959                "entityTypes": {
1960                    "User // comment": {
1961                        "memberOfTypes": [
1962                            "UserGroup"
1963                        ]
1964                    },
1965                    "User": {
1966                        "memberOfTypes": [
1967                            "UserGroup"
1968                        ]
1969                    },
1970                    "UserGroup": {}
1971                },
1972                "actions": {}
1973            }
1974        });
1975        let fragment = serde_json::from_value::<SchemaFragment>(bad1); // should this fail in the future?
1976                                                                       // The future has come?
1977        assert!(fragment.is_err());
1978
1979        // non-normalized schema namespace
1980        let bad2 = json!({
1981            "ABC     :: //comment \n XYZ  ": {
1982                "entityTypes": {
1983                    "User": {
1984                        "memberOfTypes": []
1985                    }
1986                },
1987                "actions": {}
1988            }
1989        });
1990        let fragment = serde_json::from_value::<SchemaFragment>(bad2); // should this fail in the future?
1991                                                                       // The future has come?
1992        assert!(fragment.is_err());
1993    }
1994
1995    #[test]
1996    fn simple_action_entity() {
1997        let src = json!(
1998        {
1999            "entityTypes": { },
2000            "actions": {
2001                "view_photo": { },
2002            }
2003        });
2004
2005        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2006        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2007        let actions = schema.action_entities().expect("Entity Construct Error");
2008
2009        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2010        let view_photo = actions.entity(&action_uid);
2011        assert_eq!(
2012            view_photo.unwrap(),
2013            &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
2014        );
2015    }
2016
2017    #[test]
2018    fn action_entity_hierarchy() {
2019        let src = json!(
2020        {
2021            "entityTypes": { },
2022            "actions": {
2023                "read": {},
2024                "view": {
2025                    "memberOf": [{"id": "read"}]
2026                },
2027                "view_photo": {
2028                    "memberOf": [{"id": "view"}]
2029                },
2030            }
2031        });
2032
2033        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2034        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2035        let actions = schema.action_entities().expect("Entity Construct Error");
2036
2037        let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2038        let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2039        let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2040
2041        let view_photo_entity = actions.entity(&view_photo_uid);
2042        assert_eq!(
2043            view_photo_entity.unwrap(),
2044            &Entity::new_with_attr_partial_value(
2045                view_photo_uid,
2046                HashMap::new(),
2047                HashSet::from([view_uid.clone(), read_uid.clone()])
2048            )
2049        );
2050
2051        let view_entity = actions.entity(&view_uid);
2052        assert_eq!(
2053            view_entity.unwrap(),
2054            &Entity::new_with_attr_partial_value(
2055                view_uid,
2056                HashMap::new(),
2057                HashSet::from([read_uid.clone()])
2058            )
2059        );
2060
2061        let read_entity = actions.entity(&read_uid);
2062        assert_eq!(
2063            read_entity.unwrap(),
2064            &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
2065        );
2066    }
2067
2068    #[test]
2069    fn action_entity_attribute() {
2070        let src = json!(
2071        {
2072            "entityTypes": { },
2073            "actions": {
2074                "view_photo": {
2075                    "attributes": { "attr": "foo" }
2076                },
2077            }
2078        });
2079
2080        let schema_file: NamespaceDefinitionWithActionAttributes =
2081            serde_json::from_value(src).expect("Parse Error");
2082        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2083        let actions = schema.action_entities().expect("Entity Construct Error");
2084
2085        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2086        let view_photo = actions.entity(&action_uid);
2087        assert_eq!(
2088            view_photo.unwrap(),
2089            &Entity::new(
2090                action_uid,
2091                HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2092                HashSet::new(),
2093                &Extensions::none(),
2094            )
2095            .unwrap(),
2096        );
2097    }
2098
2099    #[test]
2100    fn test_action_namespace_inference_multi_success() {
2101        let src = json!({
2102            "Foo" : {
2103                "entityTypes" : {},
2104                "actions" : {
2105                    "read" : {}
2106                }
2107            },
2108            "ExampleCo::Personnel" : {
2109                "entityTypes" : {},
2110                "actions" : {
2111                    "viewPhoto" : {
2112                        "memberOf" : [
2113                            {
2114                                "id" : "read",
2115                                "type" : "Foo::Action"
2116                            }
2117                        ]
2118                    }
2119                }
2120            },
2121        });
2122        let schema_fragment =
2123            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2124        let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2125        let view_photo = schema
2126            .action_entities_iter()
2127            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2128            .unwrap();
2129        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2130        let read = ancestors[0];
2131        assert_eq!(read.eid().to_string(), "read");
2132        assert_eq!(read.entity_type().to_string(), "Foo::Action");
2133    }
2134
2135    #[test]
2136    fn test_action_namespace_inference_multi() {
2137        let src = json!({
2138            "ExampleCo::Personnel::Foo" : {
2139                "entityTypes" : {},
2140                "actions" : {
2141                    "read" : {}
2142                }
2143            },
2144            "ExampleCo::Personnel" : {
2145                "entityTypes" : {},
2146                "actions" : {
2147                    "viewPhoto" : {
2148                        "memberOf" : [
2149                            {
2150                                "id" : "read",
2151                                "type" : "Foo::Action"
2152                            }
2153                        ]
2154                    }
2155                }
2156            },
2157        });
2158        let schema_fragment =
2159            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2160        let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2161        schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2162    }
2163
2164    #[test]
2165    fn test_action_namespace_inference() {
2166        let src = json!({
2167            "ExampleCo::Personnel" : {
2168                "entityTypes" : { },
2169                "actions" : {
2170                    "read" : {},
2171                    "viewPhoto" : {
2172                        "memberOf" : [
2173                            {
2174                                "id" :  "read",
2175                                "type" : "Action"
2176                            }
2177                        ]
2178                    }
2179                }
2180            }
2181        });
2182        let schema_fragment =
2183            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2184        let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2185        let view_photo = schema
2186            .action_entities_iter()
2187            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2188            .unwrap();
2189        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2190        let read = ancestors[0];
2191        assert_eq!(read.eid().to_string(), "read");
2192        assert_eq!(
2193            read.entity_type().to_string(),
2194            "ExampleCo::Personnel::Action"
2195        );
2196    }
2197
2198    #[test]
2199    fn qualified_undeclared_common_types() {
2200        let src = json!(
2201            {
2202                "Demo": {
2203                  "entityTypes": {
2204                    "User": {
2205                      "memberOfTypes": [],
2206                      "shape": {
2207                        "type": "Record",
2208                        "attributes": {
2209                          "id": { "type": "id" },
2210                        }
2211                      }
2212                    }
2213                  },
2214                  "actions": {}
2215                },
2216                "": {
2217                  "commonTypes": {
2218                    "id": {
2219                      "type": "String"
2220                    },
2221                  },
2222                  "entityTypes": {},
2223                  "actions": {}
2224                }
2225              }
2226        );
2227        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2228        assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2229            assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2230    }
2231
2232    #[test]
2233    fn qualified_undeclared_common_types2() {
2234        let src = json!(
2235            {
2236                "Demo": {
2237                  "entityTypes": {
2238                    "User": {
2239                      "memberOfTypes": [],
2240                      "shape": {
2241                        "type": "Record",
2242                        "attributes": {
2243                          "id": { "type": "Demo::id" },
2244                        }
2245                      }
2246                    }
2247                  },
2248                  "actions": {}
2249                },
2250                "": {
2251                  "commonTypes": {
2252                    "id": {
2253                      "type": "String"
2254                    },
2255                  },
2256                  "entityTypes": {},
2257                  "actions": {}
2258                }
2259              }
2260        );
2261        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2262        assert_matches!(schema, Err(SchemaError::UndeclaredCommonTypes(types)) =>
2263            assert_eq!(types, HashSet::from(["Demo::id".to_string()])));
2264    }
2265}
2266
2267#[cfg(test)]
2268mod test_resolver {
2269    use std::collections::HashMap;
2270
2271    use cedar_policy_core::ast::Name;
2272    use cool_asserts::assert_matches;
2273
2274    use super::CommonTypeResolver;
2275    use crate::{types::Type, SchemaError, SchemaFragment, ValidatorSchemaFragment};
2276
2277    fn resolve(schema: SchemaFragment) -> Result<HashMap<Name, Type>, SchemaError> {
2278        let schema: ValidatorSchemaFragment = schema.try_into().unwrap();
2279        let mut type_defs = HashMap::new();
2280        for def in schema.0 {
2281            type_defs.extend(def.type_defs.type_defs.into_iter());
2282        }
2283        let resolver = CommonTypeResolver::new(&type_defs);
2284        resolver.resolve()
2285    }
2286
2287    #[test]
2288    fn test_simple() {
2289        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2290            {
2291                "": {
2292                    "entityTypes": {},
2293                    "actions": {},
2294                    "commonTypes": {
2295                        "a" : {
2296                            "type": "b"
2297                        },
2298                        "b": {
2299                            "type": "Boolean"
2300                        }
2301                    }
2302                }
2303            }
2304        ))
2305        .unwrap();
2306        let res = resolve(schema).unwrap();
2307        assert_eq!(
2308            res,
2309            HashMap::from_iter([
2310                ("a".parse().unwrap(), Type::primitive_boolean()),
2311                ("b".parse().unwrap(), Type::primitive_boolean())
2312            ])
2313        );
2314
2315        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2316            {
2317                "": {
2318                    "entityTypes": {},
2319                    "actions": {},
2320                    "commonTypes": {
2321                        "a" : {
2322                            "type": "b"
2323                        },
2324                        "b": {
2325                            "type": "c"
2326                        },
2327                        "c": {
2328                            "type": "Boolean"
2329                        }
2330                    }
2331                }
2332            }
2333        ))
2334        .unwrap();
2335        let res = resolve(schema).unwrap();
2336        assert_eq!(
2337            res,
2338            HashMap::from_iter([
2339                ("a".parse().unwrap(), Type::primitive_boolean()),
2340                ("b".parse().unwrap(), Type::primitive_boolean()),
2341                ("c".parse().unwrap(), Type::primitive_boolean())
2342            ])
2343        );
2344    }
2345
2346    #[test]
2347    fn test_set() {
2348        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2349            {
2350                "": {
2351                    "entityTypes": {},
2352                    "actions": {},
2353                    "commonTypes": {
2354                        "a" : {
2355                            "type": "Set",
2356                            "element": {
2357                                "type": "b"
2358                            }
2359                        },
2360                        "b": {
2361                            "type": "Boolean"
2362                        }
2363                    }
2364                }
2365            }
2366        ))
2367        .unwrap();
2368        let res = resolve(schema).unwrap();
2369        assert_eq!(
2370            res,
2371            HashMap::from_iter([
2372                ("a".parse().unwrap(), Type::set(Type::primitive_boolean())),
2373                ("b".parse().unwrap(), Type::primitive_boolean())
2374            ])
2375        );
2376    }
2377
2378    #[test]
2379    fn test_record() {
2380        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2381            {
2382                "": {
2383                    "entityTypes": {},
2384                    "actions": {},
2385                    "commonTypes": {
2386                        "a" : {
2387                            "type": "Record",
2388                            "attributes": {
2389                                "foo": {
2390                                    "type": "b"
2391                                }
2392                            }
2393                        },
2394                        "b": {
2395                            "type": "Boolean"
2396                        }
2397                    }
2398                }
2399            }
2400        ))
2401        .unwrap();
2402        let res = resolve(schema).unwrap();
2403        assert_eq!(
2404            res,
2405            HashMap::from_iter([
2406                (
2407                    "a".parse().unwrap(),
2408                    Type::record_with_required_attributes(
2409                        std::iter::once(("foo".into(), Type::primitive_boolean())),
2410                        crate::types::OpenTag::ClosedAttributes
2411                    )
2412                ),
2413                ("b".parse().unwrap(), Type::primitive_boolean())
2414            ])
2415        );
2416    }
2417
2418    #[test]
2419    fn test_names() {
2420        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2421            {
2422                "A": {
2423                    "entityTypes": {},
2424                    "actions": {},
2425                    "commonTypes": {
2426                        "a" : {
2427                            "type": "B::a"
2428                        }
2429                    }
2430                },
2431                "B": {
2432                    "entityTypes": {},
2433                    "actions": {},
2434                    "commonTypes": {
2435                        "a" : {
2436                            "type": "Boolean"
2437                        }
2438                    }
2439                }
2440            }
2441        ))
2442        .unwrap();
2443        let res = resolve(schema).unwrap();
2444        assert_eq!(
2445            res,
2446            HashMap::from_iter([
2447                ("A::a".parse().unwrap(), Type::primitive_boolean()),
2448                ("B::a".parse().unwrap(), Type::primitive_boolean())
2449            ])
2450        );
2451    }
2452
2453    #[test]
2454    fn test_cycles() {
2455        // self reference
2456        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2457            {
2458                "": {
2459                    "entityTypes": {},
2460                    "actions": {},
2461                    "commonTypes": {
2462                        "a" : {
2463                            "type": "a"
2464                        }
2465                    }
2466                }
2467            }
2468        ))
2469        .unwrap();
2470        let res = resolve(schema);
2471        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2472
2473        // 2 node loop
2474        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2475            {
2476                "": {
2477                    "entityTypes": {},
2478                    "actions": {},
2479                    "commonTypes": {
2480                        "a" : {
2481                            "type": "b"
2482                        },
2483                        "b" : {
2484                            "type": "a"
2485                        }
2486                    }
2487                }
2488            }
2489        ))
2490        .unwrap();
2491        let res = resolve(schema);
2492        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2493
2494        // 3 node loop
2495        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2496            {
2497                "": {
2498                    "entityTypes": {},
2499                    "actions": {},
2500                    "commonTypes": {
2501                        "a" : {
2502                            "type": "b"
2503                        },
2504                        "b" : {
2505                            "type": "c"
2506                        },
2507                        "c" : {
2508                            "type": "a"
2509                        }
2510                    }
2511                }
2512            }
2513        ))
2514        .unwrap();
2515        let res = resolve(schema);
2516        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2517
2518        // cross-namespace 2 node loop
2519        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2520            {
2521                "A": {
2522                    "entityTypes": {},
2523                    "actions": {},
2524                    "commonTypes": {
2525                        "a" : {
2526                            "type": "B::a"
2527                        }
2528                    }
2529                },
2530                "B": {
2531                    "entityTypes": {},
2532                    "actions": {},
2533                    "commonTypes": {
2534                        "a" : {
2535                            "type": "A::a"
2536                        }
2537                    }
2538                }
2539            }
2540        ))
2541        .unwrap();
2542        let res = resolve(schema);
2543        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2544
2545        // cross-namespace 3 node loop
2546        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2547            {
2548                "A": {
2549                    "entityTypes": {},
2550                    "actions": {},
2551                    "commonTypes": {
2552                        "a" : {
2553                            "type": "B::a"
2554                        }
2555                    }
2556                },
2557                "B": {
2558                    "entityTypes": {},
2559                    "actions": {},
2560                    "commonTypes": {
2561                        "a" : {
2562                            "type": "C::a"
2563                        }
2564                    }
2565                },
2566                "C": {
2567                    "entityTypes": {},
2568                    "actions": {},
2569                    "commonTypes": {
2570                        "a" : {
2571                            "type": "A::a"
2572                        }
2573                    }
2574                }
2575            }
2576        ))
2577        .unwrap();
2578        let res = resolve(schema);
2579        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2580
2581        // cross-namespace 3 node loop
2582        let schema = serde_json::from_value::<SchemaFragment>(serde_json::json!(
2583            {
2584                "A": {
2585                    "entityTypes": {},
2586                    "actions": {},
2587                    "commonTypes": {
2588                        "a" : {
2589                            "type": "B::a"
2590                        }
2591                    }
2592                },
2593                "B": {
2594                    "entityTypes": {},
2595                    "actions": {},
2596                    "commonTypes": {
2597                        "a" : {
2598                            "type": "c"
2599                        },
2600                        "c": {
2601                            "type": "A::a"
2602                        }
2603                    }
2604                }
2605            }
2606        ))
2607        .unwrap();
2608        let res = resolve(schema);
2609        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
2610    }
2611}
2612
2613#[cfg(test)]
2614mod test_access {
2615    use super::*;
2616
2617    fn schema() -> ValidatorSchema {
2618        let src = r#"
2619        type Task = {
2620    "id": Long,
2621    "name": String,
2622    "state": String,
2623};
2624
2625type Tasks = Set<Task>;
2626entity List in [Application] = {
2627  "editors": Team,
2628  "name": String,
2629  "owner": User,
2630  "readers": Team,
2631  "tasks": Tasks,
2632};
2633entity Application;
2634entity User in [Team, Application] = {
2635  "joblevel": Long,
2636  "location": String,
2637};
2638
2639entity CoolList;
2640
2641entity Team in [Team, Application];
2642
2643action Read, Write, Create;
2644
2645action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
2646    principal: [User],
2647    resource : [List]
2648};
2649
2650action GetList in Read appliesTo {
2651    principal : [User],
2652    resource : [List, CoolList]
2653};
2654
2655action GetLists in Read appliesTo {
2656    principal : [User],
2657    resource : [Application]
2658};
2659
2660action CreateList in Create appliesTo {
2661    principal : [User],
2662    resource : [Application]
2663};
2664
2665        "#;
2666
2667        ValidatorSchema::from_str_natural(src, Extensions::all_available())
2668            .unwrap()
2669            .0
2670    }
2671
2672    #[test]
2673    fn principals() {
2674        let schema = schema();
2675        let principals = schema.principals().collect::<HashSet<_>>();
2676        assert_eq!(principals.len(), 1);
2677        let user: EntityType = EntityType::Specified("User".parse().unwrap());
2678        assert!(principals.contains(&user));
2679        let principals = schema.principals().collect::<Vec<_>>();
2680        assert!(principals.len() > 1);
2681        assert!(principals.iter().all(|ety| **ety == user));
2682    }
2683
2684    #[test]
2685    fn empty_schema_principals_and_resources() {
2686        let empty: ValidatorSchema =
2687            ValidatorSchema::from_str_natural("", Extensions::all_available())
2688                .unwrap()
2689                .0;
2690        assert!(empty.principals().collect::<Vec<_>>().is_empty());
2691        assert!(empty.resources().collect::<Vec<_>>().is_empty());
2692    }
2693
2694    #[test]
2695    fn resources() {
2696        let schema = schema();
2697        let resources = schema.resources().cloned().collect::<HashSet<_>>();
2698        let expected: HashSet<EntityType> = HashSet::from([
2699            EntityType::Specified("List".parse().unwrap()),
2700            EntityType::Specified("Application".parse().unwrap()),
2701            EntityType::Specified("CoolList".parse().unwrap()),
2702        ]);
2703        assert_eq!(resources, expected);
2704    }
2705
2706    #[test]
2707    fn principals_for_action() {
2708        let schema = schema();
2709        let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
2710        let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
2711        let got = schema
2712            .principals_for_action(&delete_list)
2713            .unwrap()
2714            .cloned()
2715            .collect::<Vec<_>>();
2716        assert_eq!(got, vec![EntityType::Specified("User".parse().unwrap())]);
2717        assert!(schema.principals_for_action(&delete_user).is_none());
2718    }
2719
2720    #[test]
2721    fn resources_for_action() {
2722        let schema = schema();
2723        let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
2724        let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
2725        let create_list: EntityUID = r#"Action::"CreateList""#.parse().unwrap();
2726        let get_list: EntityUID = r#"Action::"GetList""#.parse().unwrap();
2727        let got = schema
2728            .resources_for_action(&delete_list)
2729            .unwrap()
2730            .cloned()
2731            .collect::<Vec<_>>();
2732        assert_eq!(got, vec![EntityType::Specified("List".parse().unwrap())]);
2733        let got = schema
2734            .resources_for_action(&create_list)
2735            .unwrap()
2736            .cloned()
2737            .collect::<Vec<_>>();
2738        assert_eq!(
2739            got,
2740            vec![EntityType::Specified("Application".parse().unwrap())]
2741        );
2742        let got = schema
2743            .resources_for_action(&get_list)
2744            .unwrap()
2745            .cloned()
2746            .collect::<HashSet<_>>();
2747        assert_eq!(
2748            got,
2749            HashSet::from([
2750                EntityType::Specified("List".parse().unwrap()),
2751                EntityType::Specified("CoolList".parse().unwrap())
2752            ])
2753        );
2754        assert!(schema.principals_for_action(&delete_user).is_none());
2755    }
2756
2757    #[test]
2758    fn principal_parents() {
2759        let schema = schema();
2760        let user: Name = "User".parse().unwrap();
2761        let parents = schema
2762            .ancestors(&user)
2763            .unwrap()
2764            .cloned()
2765            .collect::<HashSet<_>>();
2766        let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
2767        assert_eq!(parents, expected);
2768        let parents = schema
2769            .ancestors(&"List".parse().unwrap())
2770            .unwrap()
2771            .cloned()
2772            .collect::<HashSet<_>>();
2773        let expected = HashSet::from(["Application".parse().unwrap()]);
2774        assert_eq!(parents, expected);
2775        assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
2776        let parents = schema
2777            .ancestors(&"CoolList".parse().unwrap())
2778            .unwrap()
2779            .cloned()
2780            .collect::<HashSet<_>>();
2781        let expected = HashSet::from([]);
2782        assert_eq!(parents, expected);
2783    }
2784
2785    #[test]
2786    fn action_groups() {
2787        let schema = schema();
2788        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
2789        let expected = ["Read", "Write", "Create"]
2790            .into_iter()
2791            .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
2792            .collect::<HashSet<EntityUID>>();
2793        assert_eq!(groups, expected);
2794    }
2795
2796    #[test]
2797    fn actions() {
2798        let schema = schema();
2799        let actions = schema.actions().cloned().collect::<HashSet<_>>();
2800        let expected = [
2801            "Read",
2802            "Write",
2803            "Create",
2804            "DeleteList",
2805            "EditShare",
2806            "UpdateList",
2807            "CreateTask",
2808            "UpdateTask",
2809            "DeleteTask",
2810            "GetList",
2811            "GetLists",
2812            "CreateList",
2813        ]
2814        .into_iter()
2815        .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
2816        .collect::<HashSet<EntityUID>>();
2817        assert_eq!(actions, expected);
2818    }
2819
2820    #[test]
2821    fn entities() {
2822        let schema = schema();
2823        let entities = schema
2824            .entity_types()
2825            .map(|(ty, _)| ty)
2826            .cloned()
2827            .collect::<HashSet<_>>();
2828        let expected = ["List", "Application", "User", "CoolList", "Team"]
2829            .into_iter()
2830            .map(|ty| ty.parse().unwrap())
2831            .collect::<HashSet<Name>>();
2832        assert_eq!(entities, expected);
2833    }
2834}
2835
2836#[cfg(test)]
2837mod test_access_namespace {
2838    use super::*;
2839
2840    fn schema() -> ValidatorSchema {
2841        let src = r#"
2842        namespace Foo {
2843        type Task = {
2844    "id": Long,
2845    "name": String,
2846    "state": String,
2847};
2848
2849type Tasks = Set<Task>;
2850entity List in [Application] = {
2851  "editors": Team,
2852  "name": String,
2853  "owner": User,
2854  "readers": Team,
2855  "tasks": Tasks,
2856};
2857entity Application;
2858entity User in [Team, Application] = {
2859  "joblevel": Long,
2860  "location": String,
2861};
2862
2863entity CoolList;
2864
2865entity Team in [Team, Application];
2866
2867action Read, Write, Create;
2868
2869action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
2870    principal: [User],
2871    resource : [List]
2872};
2873
2874action GetList in Read appliesTo {
2875    principal : [User],
2876    resource : [List, CoolList]
2877};
2878
2879action GetLists in Read appliesTo {
2880    principal : [User],
2881    resource : [Application]
2882};
2883
2884action CreateList in Create appliesTo {
2885    principal : [User],
2886    resource : [Application]
2887};
2888    }
2889
2890        "#;
2891
2892        ValidatorSchema::from_str_natural(src, Extensions::all_available())
2893            .unwrap()
2894            .0
2895    }
2896
2897    #[test]
2898    fn principals() {
2899        let schema = schema();
2900        let principals = schema.principals().collect::<HashSet<_>>();
2901        assert_eq!(principals.len(), 1);
2902        let user: EntityType = EntityType::Specified("Foo::User".parse().unwrap());
2903        assert!(principals.contains(&user));
2904        let principals = schema.principals().collect::<Vec<_>>();
2905        assert!(principals.len() > 1);
2906        assert!(principals.iter().all(|ety| **ety == user));
2907    }
2908
2909    #[test]
2910    fn empty_schema_principals_and_resources() {
2911        let empty: ValidatorSchema =
2912            ValidatorSchema::from_str_natural("", Extensions::all_available())
2913                .unwrap()
2914                .0;
2915        assert!(empty.principals().collect::<Vec<_>>().is_empty());
2916        assert!(empty.resources().collect::<Vec<_>>().is_empty());
2917    }
2918
2919    #[test]
2920    fn resources() {
2921        let schema = schema();
2922        let resources = schema.resources().cloned().collect::<HashSet<_>>();
2923        let expected: HashSet<EntityType> = HashSet::from([
2924            EntityType::Specified("Foo::List".parse().unwrap()),
2925            EntityType::Specified("Foo::Application".parse().unwrap()),
2926            EntityType::Specified("Foo::CoolList".parse().unwrap()),
2927        ]);
2928        assert_eq!(resources, expected);
2929    }
2930
2931    #[test]
2932    fn principals_for_action() {
2933        let schema = schema();
2934        let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
2935        let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
2936        let got = schema
2937            .principals_for_action(&delete_list)
2938            .unwrap()
2939            .cloned()
2940            .collect::<Vec<_>>();
2941        assert_eq!(
2942            got,
2943            vec![EntityType::Specified("Foo::User".parse().unwrap())]
2944        );
2945        assert!(schema.principals_for_action(&delete_user).is_none());
2946    }
2947
2948    #[test]
2949    fn resources_for_action() {
2950        let schema = schema();
2951        let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
2952        let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
2953        let create_list: EntityUID = r#"Foo::Action::"CreateList""#.parse().unwrap();
2954        let get_list: EntityUID = r#"Foo::Action::"GetList""#.parse().unwrap();
2955        let got = schema
2956            .resources_for_action(&delete_list)
2957            .unwrap()
2958            .cloned()
2959            .collect::<Vec<_>>();
2960        assert_eq!(
2961            got,
2962            vec![EntityType::Specified("Foo::List".parse().unwrap())]
2963        );
2964        let got = schema
2965            .resources_for_action(&create_list)
2966            .unwrap()
2967            .cloned()
2968            .collect::<Vec<_>>();
2969        assert_eq!(
2970            got,
2971            vec![EntityType::Specified("Foo::Application".parse().unwrap())]
2972        );
2973        let got = schema
2974            .resources_for_action(&get_list)
2975            .unwrap()
2976            .cloned()
2977            .collect::<HashSet<_>>();
2978        assert_eq!(
2979            got,
2980            HashSet::from([
2981                EntityType::Specified("Foo::List".parse().unwrap()),
2982                EntityType::Specified("Foo::CoolList".parse().unwrap())
2983            ])
2984        );
2985        assert!(schema.principals_for_action(&delete_user).is_none());
2986    }
2987
2988    #[test]
2989    fn principal_parents() {
2990        let schema = schema();
2991        let user: Name = "Foo::User".parse().unwrap();
2992        let parents = schema
2993            .ancestors(&user)
2994            .unwrap()
2995            .cloned()
2996            .collect::<HashSet<_>>();
2997        let expected = HashSet::from([
2998            "Foo::Team".parse().unwrap(),
2999            "Foo::Application".parse().unwrap(),
3000        ]);
3001        assert_eq!(parents, expected);
3002        let parents = schema
3003            .ancestors(&"Foo::List".parse().unwrap())
3004            .unwrap()
3005            .cloned()
3006            .collect::<HashSet<_>>();
3007        let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
3008        assert_eq!(parents, expected);
3009        assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
3010        let parents = schema
3011            .ancestors(&"Foo::CoolList".parse().unwrap())
3012            .unwrap()
3013            .cloned()
3014            .collect::<HashSet<_>>();
3015        let expected = HashSet::from([]);
3016        assert_eq!(parents, expected);
3017    }
3018
3019    #[test]
3020    fn action_groups() {
3021        let schema = schema();
3022        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
3023        let expected = ["Read", "Write", "Create"]
3024            .into_iter()
3025            .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
3026            .collect::<HashSet<EntityUID>>();
3027        assert_eq!(groups, expected);
3028    }
3029
3030    #[test]
3031    fn actions() {
3032        let schema = schema();
3033        let actions = schema.actions().cloned().collect::<HashSet<_>>();
3034        let expected = [
3035            "Read",
3036            "Write",
3037            "Create",
3038            "DeleteList",
3039            "EditShare",
3040            "UpdateList",
3041            "CreateTask",
3042            "UpdateTask",
3043            "DeleteTask",
3044            "GetList",
3045            "GetLists",
3046            "CreateList",
3047        ]
3048        .into_iter()
3049        .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
3050        .collect::<HashSet<EntityUID>>();
3051        assert_eq!(actions, expected);
3052    }
3053
3054    #[test]
3055    fn entities() {
3056        let schema = schema();
3057        let entities = schema
3058            .entity_types()
3059            .map(|(ty, _)| ty)
3060            .cloned()
3061            .collect::<HashSet<_>>();
3062        let expected = [
3063            "Foo::List",
3064            "Foo::Application",
3065            "Foo::User",
3066            "Foo::CoolList",
3067            "Foo::Team",
3068        ]
3069        .into_iter()
3070        .map(|ty| ty.parse().unwrap())
3071        .collect::<HashSet<Name>>();
3072        assert_eq!(entities, expected);
3073    }
3074}