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