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, BTreeSet, HashMap, HashSet};
24use std::str::FromStr;
25
26use cedar_policy_core::{
27    ast::{Entity, EntityType, EntityUID, InternalName, Name, UnreservedId},
28    entities::{err::EntitiesError, Entities, TCComputation},
29    extensions::Extensions,
30    transitive_closure::compute_tc,
31};
32use itertools::Itertools;
33use nonempty::NonEmpty;
34use serde::{Deserialize, Serialize};
35use serde_with::serde_as;
36use smol_str::ToSmolStr;
37
38use crate::{
39    cedar_schema::SchemaWarning,
40    err::schema_errors::*,
41    err::*,
42    json_schema,
43    types::{Attributes, EntityRecordKind, OpenTag, Type},
44};
45
46mod action;
47pub use action::ValidatorActionId;
48pub(crate) use action::ValidatorApplySpec;
49mod entity_type;
50pub use entity_type::ValidatorEntityType;
51mod namespace_def;
52use namespace_def::try_entity_attributes_into_validator_type;
53pub(crate) use namespace_def::try_jsonschema_type_into_validator_type;
54pub use namespace_def::ValidatorNamespaceDef;
55mod raw_name;
56pub use raw_name::{ConditionalName, RawName, ReferenceType};
57
58/// Configurable validator behaviors regarding actions
59#[derive(Debug, Eq, PartialEq, Copy, Clone, Default)]
60pub enum ActionBehavior {
61    /// Action entities cannot have attributes. Attempting to declare attributes
62    /// will result in a error when constructing the schema.
63    ///
64    /// Since we do not have a formal model for action attributes, this behavior
65    /// (disabling/prohibiting them) is the default.
66    #[default]
67    ProhibitAttributes,
68    /// Action entities may have attributes.
69    PermitAttributes,
70}
71
72/// A `ValidatorSchemaFragment` consists of any number (even 0) of
73/// `ValidatorNamespaceDef`s.
74#[derive(Debug)]
75pub struct ValidatorSchemaFragment<N, A>(Vec<ValidatorNamespaceDef<N, A>>);
76
77impl TryInto<ValidatorSchemaFragment<ConditionalName, ConditionalName>>
78    for json_schema::Fragment<RawName>
79{
80    type Error = SchemaError;
81
82    fn try_into(self) -> Result<ValidatorSchemaFragment<ConditionalName, ConditionalName>> {
83        ValidatorSchemaFragment::from_schema_fragment(
84            self,
85            ActionBehavior::default(),
86            Extensions::all_available(),
87        )
88    }
89}
90
91impl<N, A> ValidatorSchemaFragment<N, A> {
92    /// Construct a [`ValidatorSchemaFragment`] from multiple [`ValidatorNamespaceDef`]s
93    pub fn from_namespaces(
94        namespaces: impl IntoIterator<Item = ValidatorNamespaceDef<N, A>>,
95    ) -> Self {
96        Self(namespaces.into_iter().collect())
97    }
98
99    /// Get the fully-qualified [`InternalName`]s for the namespaces in this
100    /// fragment.
101    /// `None` indicates the empty namespace.
102    pub fn namespaces(&self) -> impl Iterator<Item = Option<&InternalName>> {
103        self.0.iter().map(|d| d.namespace())
104    }
105}
106
107impl ValidatorSchemaFragment<ConditionalName, ConditionalName> {
108    /// Construct a [`ValidatorSchemaFragment`] from a [`json_schema::Fragment`]
109    pub fn from_schema_fragment(
110        fragment: json_schema::Fragment<RawName>,
111        action_behavior: ActionBehavior,
112        extensions: &Extensions<'_>,
113    ) -> Result<Self> {
114        Ok(Self(
115            fragment
116                .0
117                .into_iter()
118                .map(|(fragment_ns, ns_def)| {
119                    ValidatorNamespaceDef::from_namespace_definition(
120                        fragment_ns.map(Into::into),
121                        ns_def,
122                        action_behavior,
123                        extensions,
124                    )
125                })
126                .collect::<Result<Vec<_>>>()?,
127        ))
128    }
129
130    /// Convert this [`ValidatorSchemaFragment<ConditionalName>`] into a
131    /// [`ValidatorSchemaFragment<Name>`] by fully-qualifying all typenames that
132    /// appear anywhere in any definitions.
133    ///
134    /// `all_defs` needs to contain the full set of all fully-qualified typenames
135    /// and actions that are defined in the schema (in all schema fragments).
136    pub fn fully_qualify_type_references(
137        self,
138        all_defs: &AllDefs,
139    ) -> Result<ValidatorSchemaFragment<InternalName, EntityType>> {
140        let (nsdefs, errs) = self
141            .0
142            .into_iter()
143            .map(|ns_def| ns_def.fully_qualify_type_references(all_defs))
144            .partition_result::<Vec<ValidatorNamespaceDef<InternalName, EntityType>>, Vec<SchemaError>, _, _>();
145        if let Some(errs) = NonEmpty::from_vec(errs) {
146            Err(SchemaError::join_nonempty(errs))
147        } else {
148            Ok(ValidatorSchemaFragment(nsdefs))
149        }
150    }
151}
152
153/// Internal representation of the schema for use by the validator.
154///
155/// In this representation, all common types are fully expanded, and all entity
156/// type names are fully disambiguated (fully qualified).
157#[serde_as]
158#[derive(Clone, Debug, Serialize)]
159#[serde(rename_all = "camelCase")]
160pub struct ValidatorSchema {
161    /// Map from entity type names to the [`ValidatorEntityType`] object.
162    #[serde_as(as = "Vec<(_, _)>")]
163    entity_types: HashMap<EntityType, ValidatorEntityType>,
164
165    /// Map from action id names to the [`ValidatorActionId`] object.
166    #[serde_as(as = "Vec<(_, _)>")]
167    action_ids: HashMap<EntityUID, ValidatorActionId>,
168}
169
170/// Construct [`ValidatorSchema`] from a string containing a schema formatted
171/// in the Cedar schema format.
172impl std::str::FromStr for ValidatorSchema {
173    type Err = CedarSchemaError;
174
175    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
176        Self::from_cedarschema_str(s, Extensions::all_available()).map(|(schema, _)| schema)
177    }
178}
179
180impl TryFrom<json_schema::NamespaceDefinition<RawName>> for ValidatorSchema {
181    type Error = SchemaError;
182
183    fn try_from(nsd: json_schema::NamespaceDefinition<RawName>) -> Result<ValidatorSchema> {
184        ValidatorSchema::from_schema_fragments(
185            [ValidatorSchemaFragment::from_namespaces([nsd.try_into()?])],
186            Extensions::all_available(),
187        )
188    }
189}
190
191impl TryFrom<json_schema::Fragment<RawName>> for ValidatorSchema {
192    type Error = SchemaError;
193
194    fn try_from(frag: json_schema::Fragment<RawName>) -> Result<ValidatorSchema> {
195        ValidatorSchema::from_schema_fragments([frag.try_into()?], Extensions::all_available())
196    }
197}
198
199impl ValidatorSchema {
200    /// Returns an iterator over every entity type that can be a principal for any action in this schema
201    pub fn principals(&self) -> impl Iterator<Item = &EntityType> {
202        self.action_ids
203            .values()
204            .flat_map(ValidatorActionId::principals)
205    }
206
207    /// Returns an iterator over every entity type that can be a resource for any action in this schema
208    pub fn resources(&self) -> impl Iterator<Item = &EntityType> {
209        self.action_ids
210            .values()
211            .flat_map(ValidatorActionId::resources)
212    }
213
214    /// Returns an iterator over every entity type that can be a principal for `action` in this schema
215    ///
216    /// # Errors
217    ///
218    /// Returns [`None`] if `action` is not found in the schema
219    pub fn principals_for_action(
220        &self,
221        action: &EntityUID,
222    ) -> Option<impl Iterator<Item = &EntityType>> {
223        self.action_ids
224            .get(action)
225            .map(ValidatorActionId::principals)
226    }
227
228    /// Returns an iterator over every entity type that can be a resource for `action` in this schema
229    ///
230    /// # Errors
231    ///
232    /// Returns [`None`] if `action` is not found in the schema
233    pub fn resources_for_action(
234        &self,
235        action: &EntityUID,
236    ) -> Option<impl Iterator<Item = &EntityType>> {
237        self.action_ids
238            .get(action)
239            .map(ValidatorActionId::resources)
240    }
241
242    /// Returns an iterator over all the entity types that can be a parent of `ty`
243    ///
244    /// # Errors
245    ///
246    /// Returns [`None`] if the `ty` is not found in the schema
247    pub fn ancestors<'a>(
248        &'a self,
249        ty: &'a EntityType,
250    ) -> Option<impl Iterator<Item = &EntityType> + 'a> {
251        if self.entity_types.contains_key(ty) {
252            Some(self.entity_types.values().filter_map(|ety| {
253                if ety.descendants.contains(ty) {
254                    Some(&ety.name)
255                } else {
256                    None
257                }
258            }))
259        } else {
260            None
261        }
262    }
263
264    /// Returns an iterator over all the action groups defined in this schema
265    pub fn action_groups(&self) -> impl Iterator<Item = &EntityUID> {
266        self.action_ids.values().filter_map(|action| {
267            if action.descendants.is_empty() {
268                None
269            } else {
270                Some(&action.name)
271            }
272        })
273    }
274
275    /// Returns an iterator over all actions defined in this schema
276    pub fn actions(&self) -> impl Iterator<Item = &EntityUID> {
277        self.action_ids.keys()
278    }
279
280    /// Create a [`ValidatorSchema`] without any definitions (of entity types,
281    /// common types, or actions).
282    pub fn empty() -> ValidatorSchema {
283        Self {
284            entity_types: HashMap::new(),
285            action_ids: HashMap::new(),
286        }
287    }
288
289    /// Construct a [`ValidatorSchema`] from a JSON value in the appropriate
290    /// shape.
291    pub fn from_json_value(json: serde_json::Value, extensions: &Extensions<'_>) -> Result<Self> {
292        Self::from_schema_frag(
293            json_schema::Fragment::<RawName>::from_json_value(json)?,
294            ActionBehavior::default(),
295            extensions,
296        )
297    }
298
299    /// Construct a [`ValidatorSchema`] from a string containing JSON in the
300    /// appropriate shape.
301    pub fn from_json_str(json: &str, extensions: &Extensions<'_>) -> Result<Self> {
302        Self::from_schema_frag(
303            json_schema::Fragment::<RawName>::from_json_str(json)?,
304            ActionBehavior::default(),
305            extensions,
306        )
307    }
308
309    /// Construct a [`ValidatorSchema`] directly from a file containing JSON
310    /// in the appropriate shape.
311    pub fn from_json_file(file: impl std::io::Read, extensions: &Extensions<'_>) -> Result<Self> {
312        Self::from_schema_frag(
313            json_schema::Fragment::<RawName>::from_json_file(file)?,
314            ActionBehavior::default(),
315            extensions,
316        )
317    }
318
319    /// Construct a [`ValidatorSchema`] directly from a file containing the
320    /// Cedar schema syntax.
321    pub fn from_cedarschema_file<'a>(
322        r: impl std::io::Read,
323        extensions: &'a Extensions<'a>,
324    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
325    {
326        let (fragment, warnings) = json_schema::Fragment::from_cedarschema_file(r, extensions)?;
327        let schema_and_warnings =
328            Self::from_schema_frag(fragment, ActionBehavior::default(), extensions)
329                .map(|schema| (schema, warnings))?;
330        Ok(schema_and_warnings)
331    }
332
333    /// Construct a [`ValidatorSchema`] from a string containing the Cedar
334    /// schema syntax.
335    pub fn from_cedarschema_str<'a>(
336        src: &str,
337        extensions: &Extensions<'a>,
338    ) -> std::result::Result<(Self, impl Iterator<Item = SchemaWarning> + 'a), CedarSchemaError>
339    {
340        let (fragment, warnings) = json_schema::Fragment::from_cedarschema_str(src, extensions)?;
341        let schema_and_warnings =
342            Self::from_schema_frag(fragment, ActionBehavior::default(), extensions)
343                .map(|schema| (schema, warnings))?;
344        Ok(schema_and_warnings)
345    }
346
347    /// Helper function to construct a [`ValidatorSchema`] from a single [`json_schema::Fragment`].
348    pub(crate) fn from_schema_frag(
349        schema_file: json_schema::Fragment<RawName>,
350        action_behavior: ActionBehavior,
351        extensions: &Extensions<'_>,
352    ) -> Result<ValidatorSchema> {
353        Self::from_schema_fragments(
354            [ValidatorSchemaFragment::from_schema_fragment(
355                schema_file,
356                action_behavior,
357                extensions,
358            )?],
359            extensions,
360        )
361    }
362
363    /// Construct a [`ValidatorSchema`] from some number of [`ValidatorSchemaFragment`]s.
364    pub fn from_schema_fragments(
365        fragments: impl IntoIterator<Item = ValidatorSchemaFragment<ConditionalName, ConditionalName>>,
366        extensions: &Extensions<'_>,
367    ) -> Result<ValidatorSchema> {
368        let mut fragments = fragments
369            .into_iter()
370            // All schemas implicitly include the following fragment as well,
371            // defining the items in the `__cedar` namespace.
372            .chain(std::iter::once(cedar_fragment(extensions)))
373            .collect::<Vec<_>>();
374
375        // Build the sets of all entity type, common type, and action definitions
376        // (fully-qualified names) in all fragments.
377        let mut all_defs = AllDefs::new(|| fragments.iter());
378
379        // Now we have enough information to do the checks required by RFC 70.
380        // We do not need all _references_ to types/actions to be fully resolved yet,
381        // because RFC 70 does not actually say anything about references, and can be
382        // enforced knowing only about the _definitions_.
383        // Furthermore, doing these checks before adding the builtin common-type aliases
384        // in the empty namespace is convenient, because at this point the only
385        // definitions in the empty namespace are the ones the user has put there, which
386        // are thus subject to RFC 70 shadowing rules.
387        all_defs.rfc_70_shadowing_checks()?;
388
389        // Add aliases for primitive and extension typenames in the empty namespace,
390        // so that they can be accessed without `__cedar`.
391        // (Only add each alias if it doesn't conflict with a user declaration --
392        // if it does conflict, we won't add the alias and the user needs to use
393        // `__cedar` to refer to the primitive/extension type.)
394        // In the future, if we support some kind of `use` keyword to make names
395        // available in the empty namespace, we'd probably add that here.
396        for tyname in primitive_types::<Name>()
397            .map(|(id, _)| Name::unqualified_name(id))
398            .chain(extensions.ext_types().cloned().map(Into::into))
399        {
400            if !all_defs.is_defined_as_entity(tyname.as_ref())
401                && !all_defs.is_defined_as_common(tyname.as_ref())
402            {
403                assert!(
404                    tyname.is_unqualified(),
405                    "expected all primitive and extension type names to be unqualified"
406                );
407                fragments.push(single_alias_in_empty_namespace(
408                    tyname.basename().clone(),
409                    tyname.as_ref().qualify_with(Some(&InternalName::__cedar())),
410                ));
411                all_defs.mark_as_defined_as_common_type(tyname.into());
412            }
413        }
414
415        // Now use `all_defs` to resolve all [`ConditionalName`] type references
416        // into fully-qualified [`InternalName`] references.
417        // ("Resolve" here just means convert to fully-qualified
418        // `InternalName`s; it does not mean inlining common types -- that will
419        // come later.)
420        // This produces an intermediate form of schema fragment,
421        // `ValidatorSchemaFragment<InternalName, EntityType>`.
422        let (fragments, errs) = fragments
423            .into_iter()
424            .map(|frag| frag.fully_qualify_type_references(&all_defs))
425            .partition_result::<Vec<ValidatorSchemaFragment<InternalName, EntityType>>, Vec<SchemaError>, _, _>();
426        if let Some(errs) = NonEmpty::from_vec(errs) {
427            return Err(SchemaError::join_nonempty(errs));
428        }
429
430        // Now that all references are fully-qualified, we can build the aggregate
431        // maps for common types, entity types, and actions, checking that nothing
432        // is defined twice. Since all of these names are already fully-qualified,
433        // the same base type name may appear multiple times so long as the
434        // namespaces are different.
435        let mut common_types = HashMap::new();
436        let mut entity_type_fragments: HashMap<EntityType, _> = HashMap::new();
437        let mut action_fragments = HashMap::new();
438        for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
439            for (name, ty) in ns_def.common_types.defs {
440                match common_types.entry(name) {
441                    Entry::Vacant(v) => v.insert(ty),
442                    Entry::Occupied(o) => {
443                        return Err(DuplicateCommonTypeError(o.key().clone()).into());
444                    }
445                };
446            }
447
448            for (name, entity_type) in ns_def.entity_types.defs {
449                match entity_type_fragments.entry(name) {
450                    Entry::Vacant(v) => v.insert(entity_type),
451                    Entry::Occupied(o) => {
452                        return Err(DuplicateEntityTypeError(o.key().clone()).into())
453                    }
454                };
455            }
456
457            for (action_euid, action) in ns_def.actions.actions {
458                match action_fragments.entry(action_euid) {
459                    Entry::Vacant(v) => v.insert(action),
460                    Entry::Occupied(o) => {
461                        return Err(DuplicateActionError(o.key().to_smolstr()).into())
462                    }
463                };
464            }
465        }
466
467        let resolver = CommonTypeResolver::new(&common_types);
468        let common_types = resolver.resolve(extensions)?;
469
470        // Invert the `parents` relation defined by entities and action so far
471        // to get a `children` relation.
472        let mut entity_children: HashMap<EntityType, HashSet<EntityType>> = HashMap::new();
473        for (name, entity_type) in entity_type_fragments.iter() {
474            for parent in entity_type.parents.iter() {
475                entity_children
476                    .entry(internal_name_to_entity_type(parent.clone())?)
477                    .or_default()
478                    .insert(name.clone());
479            }
480        }
481
482        let mut entity_types = entity_type_fragments
483            .into_iter()
484            .map(|(name, entity_type)| -> Result<_> {
485                // Keys of the `entity_children` map were values of an
486                // `memberOfTypes` list, so they might not have been declared in
487                // their fragment.  By removing entries from `entity_children`
488                // where the key is a declared name, we will be left with a map
489                // where the keys are undeclared. These keys are used to report
490                // an error when undeclared entity types are referenced inside a
491                // `memberOfTypes` list. The error is reported alongside the
492                // error for any other undeclared entity types by
493                // `check_for_undeclared`.
494                let descendants = entity_children.remove(&name).unwrap_or_default();
495                let (attributes, open_attributes) = {
496                    let unresolved = try_entity_attributes_into_validator_type(
497                        entity_type.attributes,
498                        extensions,
499                    )?;
500                    Self::record_attributes_or_none(
501                        unresolved.resolve_common_type_refs(&common_types)?,
502                    )
503                    .ok_or(ContextOrShapeNotRecordError(
504                        ContextOrShape::EntityTypeShape(name.clone()),
505                    ))?
506                };
507                Ok((
508                    name.clone(),
509                    ValidatorEntityType {
510                        name,
511                        descendants,
512                        attributes,
513                        open_attributes,
514                    },
515                ))
516            })
517            .collect::<Result<HashMap<_, _>>>()?;
518
519        let mut action_children = HashMap::new();
520        for (euid, action) in action_fragments.iter() {
521            for parent in action.parents.iter() {
522                action_children
523                    .entry(parent.clone().try_into()?)
524                    .or_insert_with(HashSet::new)
525                    .insert(euid.clone());
526            }
527        }
528        let mut action_ids = action_fragments
529            .into_iter()
530            .map(|(name, action)| -> Result<_> {
531                let descendants = action_children.remove(&name).unwrap_or_default();
532                let (context, open_context_attributes) = {
533                    let unresolved =
534                        try_jsonschema_type_into_validator_type(action.context, extensions)?;
535                    Self::record_attributes_or_none(
536                        unresolved.resolve_common_type_refs(&common_types)?,
537                    )
538                    .ok_or(ContextOrShapeNotRecordError(
539                        ContextOrShape::ActionContext(name.clone()),
540                    ))?
541                };
542                Ok((
543                    name.clone(),
544                    ValidatorActionId {
545                        name,
546                        applies_to: action.applies_to,
547                        descendants,
548                        context: Type::record_with_attributes(
549                            context.attrs,
550                            open_context_attributes,
551                        ),
552                        attribute_types: action.attribute_types,
553                        attributes: action.attributes,
554                    },
555                ))
556            })
557            .collect::<Result<HashMap<_, _>>>()?;
558
559        // We constructed entity types and actions with child maps, but we need
560        // transitively closed descendants.
561        compute_tc(&mut entity_types, false)
562            .map_err(|e| EntityTypeTransitiveClosureError::from(Box::new(e)))?;
563        // Pass `true` here so that we also check that the action hierarchy does
564        // not contain cycles.
565        compute_tc(&mut action_ids, true)?;
566
567        // Return with an error if there is an undeclared entity or action
568        // referenced in any fragment. `{entity,action}_children` are provided
569        // for the `undeclared_parent_{entities,actions}` arguments because
570        // removed keys from these maps as we encountered declarations for the
571        // entity types or actions. Any keys left in the map are therefore
572        // undeclared.
573        Self::check_for_undeclared(
574            &entity_types,
575            entity_children.into_keys(),
576            &action_ids,
577            action_children.into_keys(),
578            common_types.into_values(),
579        )?;
580
581        Ok(ValidatorSchema {
582            entity_types,
583            action_ids,
584        })
585    }
586
587    /// Check that all entity types and actions referenced in the schema are in
588    /// the set of declared entity type or action names.
589    /// This function assumes that all entity types are fully qualified, which
590    /// is indicated by the use of the [`EntityType`] and [`EntityUID`] types.
591    fn check_for_undeclared(
592        entity_types: &HashMap<EntityType, ValidatorEntityType>,
593        undeclared_parent_entities: impl IntoIterator<Item = EntityType>,
594        action_ids: &HashMap<EntityUID, ValidatorActionId>,
595        undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
596        common_types: impl IntoIterator<Item = Type>,
597    ) -> Result<()> {
598        // When we constructed `entity_types`, we removed entity types from  the
599        // `entity_children` map as we encountered a declaration for that type.
600        // Any entity types left in the map are therefore undeclared. These are
601        // any undeclared entity types which appeared in a `memberOf` list.
602        let mut undeclared_e = undeclared_parent_entities
603            .into_iter()
604            .collect::<BTreeSet<EntityType>>();
605        // Looking at entity types, we need to check entity references in
606        // attribute types. We already know that all elements of the
607        // `descendants` list were declared because the list is a result of
608        // inverting the `memberOf` relationship which mapped declared entity
609        // types to their parent entity types.
610        for entity_type in entity_types.values() {
611            for (_, attr_typ) in entity_type.attributes() {
612                Self::check_undeclared_in_type(
613                    &attr_typ.attr_type,
614                    entity_types,
615                    &mut undeclared_e,
616                );
617            }
618        }
619
620        // Check for undeclared entity types within common types.
621        for common_type in common_types {
622            Self::check_undeclared_in_type(&common_type, entity_types, &mut undeclared_e);
623        }
624
625        // Undeclared actions in a `memberOf` list.
626        let undeclared_a = undeclared_parent_actions.into_iter();
627        // For actions, we check entity references in the context attribute
628        // types and `appliesTo` lists. See the `entity_types` loop for why the
629        // `descendants` list is not checked.
630        for action in action_ids.values() {
631            Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
632
633            for p_entity in action.applies_to_principals() {
634                if !entity_types.contains_key(p_entity) {
635                    undeclared_e.insert(p_entity.clone());
636                }
637            }
638
639            for r_entity in action.applies_to_resources() {
640                if !entity_types.contains_key(r_entity) {
641                    undeclared_e.insert(r_entity.clone());
642                }
643            }
644        }
645        if !undeclared_e.is_empty() {
646            return Err(UndeclaredEntityTypesError(undeclared_e).into());
647        }
648        if let Some(euids) = NonEmpty::collect(undeclared_a) {
649            // This should not happen, because undeclared actions should be caught
650            // earlier, when we are resolving action names into fully-qualified [`Name`]s.
651            return Err(ActionInvariantViolationError { euids }.into());
652        }
653
654        Ok(())
655    }
656
657    fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
658        match ty {
659            Type::EntityOrRecord(EntityRecordKind::Record {
660                attrs,
661                open_attributes,
662            }) => Some((attrs, open_attributes)),
663            _ => None,
664        }
665    }
666
667    /// Check that all entity types appearing inside a type are in the set of
668    /// declared entity types, adding any undeclared entity types to the
669    /// `undeclared_types` set.
670    fn check_undeclared_in_type(
671        ty: &Type,
672        entity_types: &HashMap<EntityType, ValidatorEntityType>,
673        undeclared_types: &mut BTreeSet<EntityType>,
674    ) {
675        match ty {
676            Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
677                for name in lub.iter() {
678                    if !entity_types.contains_key(name) {
679                        undeclared_types.insert(name.clone());
680                    }
681                }
682            }
683
684            Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
685                for (_, attr_ty) in attrs.iter() {
686                    Self::check_undeclared_in_type(
687                        &attr_ty.attr_type,
688                        entity_types,
689                        undeclared_types,
690                    );
691                }
692            }
693
694            Type::Set {
695                element_type: Some(element_type),
696            } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
697
698            _ => (),
699        }
700    }
701
702    /// Lookup the [`ValidatorActionId`] object in the schema with the given name.
703    pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
704        self.action_ids.get(action_id)
705    }
706
707    /// Lookup the [`ValidatorEntityType`] object in the schema with the given name.
708    pub fn get_entity_type<'a>(
709        &'a self,
710        entity_type_id: &EntityType,
711    ) -> Option<&'a ValidatorEntityType> {
712        self.entity_types.get(entity_type_id)
713    }
714
715    /// Return true when the `action_id` corresponds to a valid action.
716    pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
717        self.action_ids.contains_key(action_id)
718    }
719
720    /// Return true when the `entity_type` corresponds to a valid entity type.
721    pub(crate) fn is_known_entity_type(&self, entity_type: &EntityType) -> bool {
722        entity_type.is_action() || self.entity_types.contains_key(entity_type)
723    }
724
725    /// Return true when `euid` has an entity type declared by the schema.
726    pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
727        self.is_known_entity_type(euid.entity_type())
728    }
729
730    /// An iterator over the action ids in the schema.
731    pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
732        self.action_ids.keys()
733    }
734
735    /// An iterator over the entity type names in the schema.
736    pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &EntityType> {
737        self.entity_types.keys()
738    }
739
740    /// An iterator matching the entity Types to their Validator Types
741    pub fn entity_types(&self) -> impl Iterator<Item = (&EntityType, &ValidatorEntityType)> {
742        self.entity_types.iter()
743    }
744
745    /// Get all entity types in the schema where an `{entity0} in {entity}` can
746    /// evaluate to `true` for some `entity0` with that entity type. This
747    /// includes all entity types that are descendants of the type of `entity`
748    /// according  to the schema, and the type of `entity` itself because
749    /// `entity in entity` evaluates to `true`.
750    pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&EntityType> {
751        let mut descendants = self
752            .get_entity_type(entity.entity_type())
753            .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
754            .unwrap_or_default();
755        descendants.push(entity.entity_type());
756        descendants
757    }
758
759    /// Get all entity types in the schema where an `{entity0} in {euids}` can
760    /// evaluate to `true` for some `entity0` with that entity type. See comment
761    /// on `get_entity_types_in`.
762    pub(crate) fn get_entity_types_in_set<'a>(
763        &'a self,
764        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
765    ) -> impl Iterator<Item = &EntityType> {
766        euids.into_iter().flat_map(|e| self.get_entity_types_in(e))
767    }
768
769    /// Get all action entities in the schema where `action in euids` evaluates
770    /// to `true`. This includes all actions which are descendants of some
771    /// element of `euids`, and all elements of `euids`.
772    pub(crate) fn get_actions_in_set<'a>(
773        &'a self,
774        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
775    ) -> Option<Vec<&'a EntityUID>> {
776        euids
777            .into_iter()
778            .map(|e| {
779                self.get_action_id(e).map(|action| {
780                    action
781                        .descendants
782                        .iter()
783                        .chain(std::iter::once(&action.name))
784                })
785            })
786            .collect::<Option<Vec<_>>>()
787            .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
788    }
789
790    /// Get the `Type` of context expected for the given `action`.
791    /// This always returns a closed record type.
792    ///
793    /// Returns `None` if the action is not in the schema.
794    pub fn context_type(&self, action: &EntityUID) -> Option<&Type> {
795        // INVARIANT: `ValidatorActionId::context_type` always returns a closed
796        // record type
797        self.get_action_id(action)
798            .map(ValidatorActionId::context_type)
799    }
800
801    /// Invert the action hierarchy to get the ancestor relation expected for
802    /// the `Entity` datatype instead of descendants as stored by the schema.
803    pub(crate) fn action_entities_iter(
804        &self,
805    ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
806        // We could store the un-inverted `memberOf` relation for each action,
807        // but I [john-h-kastner-aws] judge that the current implementation is
808        // actually less error prone, as it minimizes the threading of data
809        // structures through some complicated bits of schema construction code,
810        // and avoids computing the TC twice.
811        let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
812        for (action_euid, action_def) in &self.action_ids {
813            for descendant in &action_def.descendants {
814                action_ancestors
815                    .entry(descendant)
816                    .or_default()
817                    .insert(action_euid.clone());
818            }
819        }
820        self.action_ids.iter().map(move |(action_id, action)| {
821            Entity::new_with_attr_partial_value_serialized_as_expr(
822                action_id.clone(),
823                action.attributes.clone(),
824                action_ancestors.remove(action_id).unwrap_or_default(),
825            )
826        })
827    }
828
829    /// Construct an `Entity` object for each action in the schema
830    pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
831        let extensions = Extensions::all_available();
832        Entities::from_entities(
833            self.action_entities_iter(),
834            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
835            TCComputation::AssumeAlreadyComputed,
836            extensions,
837        )
838        .map_err(Into::into)
839    }
840}
841
842/// Used to write a schema implicitly overriding the default handling of action
843/// groups.
844#[derive(Debug, Clone, Deserialize)]
845#[serde(bound(deserialize = "N: Deserialize<'de> + From<RawName>"))]
846#[serde(transparent)]
847pub(crate) struct NamespaceDefinitionWithActionAttributes<N>(
848    pub(crate) json_schema::NamespaceDefinition<N>,
849);
850
851impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes<RawName> {
852    type Error = SchemaError;
853
854    fn try_into(self) -> Result<ValidatorSchema> {
855        ValidatorSchema::from_schema_fragments(
856            [ValidatorSchemaFragment::from_namespaces([
857                ValidatorNamespaceDef::from_namespace_definition(
858                    None,
859                    self.0,
860                    crate::ActionBehavior::PermitAttributes,
861                    Extensions::all_available(),
862                )?,
863            ])],
864            Extensions::all_available(),
865        )
866    }
867}
868
869/// Get a [`ValidatorSchemaFragment`] describing the items that implicitly exist
870/// in the `__cedar` namespace.
871fn cedar_fragment(
872    extensions: &Extensions<'_>,
873) -> ValidatorSchemaFragment<ConditionalName, ConditionalName> {
874    // PANIC SAFETY: these are valid `Id`s
875    #[allow(clippy::unwrap_used)]
876    let mut common_types = HashMap::from_iter(primitive_types());
877    for ext_type in extensions.ext_types() {
878        assert!(
879            ext_type.is_unqualified(),
880            "expected extension type names to be unqualified"
881        );
882        let ext_type = ext_type.basename().clone();
883        common_types.insert(
884            ext_type.clone(),
885            json_schema::Type::Type(json_schema::TypeVariant::Extension { name: ext_type }),
886        );
887    }
888
889    // PANIC SAFETY: this is a valid schema fragment. This code is tested by every test that constructs `ValidatorSchema`, and this fragment is the same every time, modulo active extensions.
890    #[allow(clippy::unwrap_used)]
891    ValidatorSchemaFragment(vec![ValidatorNamespaceDef::from_common_type_defs(
892        Some(InternalName::__cedar()),
893        common_types,
894    )
895    .unwrap()])
896}
897
898/// Get a [`ValidatorSchemaFragment`] containing just one common-type definition,
899/// defining the unqualified name `id` in the empty namespace as an alias for
900/// the fully-qualified name `def`. (This will eventually cause an error if
901/// `def` is not defined somewhere.)
902///
903/// `def` is allowed to be [`InternalName`] because it's totally valid to define
904/// `type Foo = __cedar::String` etc.
905fn single_alias_in_empty_namespace(
906    id: UnreservedId,
907    def: InternalName,
908) -> ValidatorSchemaFragment<ConditionalName, ConditionalName> {
909    ValidatorSchemaFragment(vec![ValidatorNamespaceDef::from_common_type_def(
910        None,
911        (
912            id,
913            json_schema::Type::Type(json_schema::TypeVariant::EntityOrCommon {
914                type_name: ConditionalName::unconditional(def, ReferenceType::CommonOrEntity),
915            }),
916        ),
917    )])
918}
919
920/// Get the names of all primitive types, as unqualified `UnreservedId`s,
921/// paired with the primitive [`json_schema::Type`]s they represent
922fn primitive_types<N>() -> impl Iterator<Item = (UnreservedId, json_schema::Type<N>)> {
923    // PANIC SAFETY: these are valid `UnreservedId`s
924    #[allow(clippy::unwrap_used)]
925    [
926        (
927            UnreservedId::from_str("Bool").unwrap(),
928            json_schema::Type::Type(json_schema::TypeVariant::Boolean),
929        ),
930        (
931            UnreservedId::from_str("Long").unwrap(),
932            json_schema::Type::Type(json_schema::TypeVariant::Long),
933        ),
934        (
935            UnreservedId::from_str("String").unwrap(),
936            json_schema::Type::Type(json_schema::TypeVariant::String),
937        ),
938    ]
939    .into_iter()
940}
941
942/// Convert an [`InternalName`] to an [`EntityType`].
943/// If this fails (because the name contained `__cedar`), this throws a
944/// `ReservedNameError`. As of this writing, there are no valid entity types
945/// containing `__cedar`.
946fn internal_name_to_entity_type(
947    name: InternalName,
948) -> std::result::Result<EntityType, cedar_policy_core::ast::ReservedNameError> {
949    Name::try_from(name).map(Into::into)
950}
951
952/// Holds the sets of all entity type, common type, and action definitions
953/// (fully-qualified names) in all fragments.
954#[derive(Debug)]
955pub struct AllDefs {
956    /// All entity type definitions, in all fragments, as fully-qualified names.
957    entity_defs: HashSet<InternalName>,
958    /// All common type definitions, in all fragments, as fully-qualified names.
959    common_defs: HashSet<InternalName>,
960    /// All action definitions, in all fragments, with fully-qualified typenames.
961    action_defs: HashSet<EntityUID>,
962}
963
964impl AllDefs {
965    /// Build the sets of all entity type, common type, and action definitions
966    /// (fully-qualified names) in all fragments.
967    pub fn new<'a, N: 'a, A: 'a, I>(fragments: impl Fn() -> I) -> Self
968    where
969        I: Iterator<Item = &'a ValidatorSchemaFragment<N, A>>,
970    {
971        Self {
972            entity_defs: fragments()
973                .flat_map(|f| f.0.iter())
974                .flat_map(|ns_def| ns_def.all_declared_entity_type_names().cloned())
975                .collect(),
976            common_defs: fragments()
977                .flat_map(|f| f.0.iter())
978                .flat_map(|ns_def| ns_def.all_declared_common_type_names().cloned())
979                .collect(),
980            action_defs: fragments()
981                .flat_map(|f| f.0.iter())
982                .flat_map(|ns_def| ns_def.all_declared_action_names().cloned())
983                .collect(),
984        }
985    }
986
987    /// Build an [`AllDefs`] assuming that the given fragment is the only
988    /// fragment that exists.
989    /// Any names referring to definitions in other fragments will not resolve
990    /// properly.
991    pub fn single_fragment<N, A>(fragment: &ValidatorSchemaFragment<N, A>) -> Self {
992        Self::new(|| std::iter::once(fragment))
993    }
994
995    /// Is the given (fully-qualified) [`InternalName`] defined as an entity
996    /// type in any fragment?
997    pub fn is_defined_as_entity(&self, name: &InternalName) -> bool {
998        self.entity_defs.contains(name)
999    }
1000
1001    /// Is the given (fully-qualified) [`InternalName`] defined as a common type
1002    /// in any fragment?
1003    pub fn is_defined_as_common(&self, name: &InternalName) -> bool {
1004        self.common_defs.contains(name)
1005    }
1006
1007    /// Is the given (fully-qualified) [`EntityUID`] defined as an action in any
1008    /// fragment?
1009    pub fn is_defined_as_action(&self, euid: &EntityUID) -> bool {
1010        self.action_defs.contains(euid)
1011    }
1012
1013    /// Mark the given [`InternalName`] as defined as a common type
1014    pub fn mark_as_defined_as_common_type(&mut self, name: InternalName) {
1015        self.common_defs.insert(name);
1016    }
1017
1018    /// Return an error if the definitions in this [`AllDefs`] violate the
1019    /// restrictions specified in [RFC 70].
1020    ///
1021    /// RFC 70 disallows definitions of entity types, common types, and actions
1022    /// that would shadow definitions of other entity types, common types, or
1023    /// actions in the empty namespace.
1024    ///
1025    /// [RFC 70]: https://github.com/cedar-policy/rfcs/blob/main/text/0070-disallow-empty-namespace-shadowing.md
1026    pub fn rfc_70_shadowing_checks(&self) -> Result<()> {
1027        for unqualified_name in self
1028            .entity_and_common_names()
1029            .filter(|name| name.is_unqualified())
1030        {
1031            // `unqualified_name` is a definition in the empty namespace
1032            if let Some(name) = self.entity_and_common_names().find(|name| {
1033                !name.is_unqualified() // RFC 70 specifies that shadowing an entity typename with a common typename is OK, including in the empty namespace
1034                && !name.is_reserved() // do not throw an error if the shadowing name is something like `__cedar::String` "shadowing" an empty-namespace declaration of `String`
1035                && name.basename() == unqualified_name.basename()
1036            }) {
1037                return Err(TypeShadowingError {
1038                    shadowed_def: unqualified_name.clone(),
1039                    shadowing_def: name.clone(),
1040                }
1041                .into());
1042            }
1043        }
1044        for unqualified_action in self
1045            .action_defs
1046            .iter()
1047            .filter(|euid| euid.entity_type().as_ref().is_unqualified())
1048        {
1049            // `unqualified_action` is a definition in the empty namespace
1050            if let Some(action) = self.action_defs.iter().find(|euid| {
1051                !euid.entity_type().as_ref().is_unqualified() // do not throw an error for an action "shadowing" itself
1052                // we do not need to check that the basenames are the same, because we assume they are both `Action`
1053                && euid.eid() == unqualified_action.eid()
1054            }) {
1055                return Err(ActionShadowingError {
1056                    shadowed_def: unqualified_action.clone(),
1057                    shadowing_def: action.clone(),
1058                }
1059                .into());
1060            }
1061        }
1062        Ok(())
1063    }
1064
1065    /// Iterate over all (fully-qualified) entity and common-type names defined
1066    /// in the [`AllDefs`].
1067    fn entity_and_common_names(&self) -> impl Iterator<Item = &InternalName> {
1068        self.entity_defs.iter().chain(self.common_defs.iter())
1069    }
1070
1071    /// Build an [`AllDefs`] that assumes the given fully-qualified
1072    /// [`InternalName`]s are defined (by the user) as entity types, and there
1073    /// are no defined common types or actions.
1074    #[cfg(test)]
1075    pub(crate) fn from_entity_defs(names: impl IntoIterator<Item = InternalName>) -> Self {
1076        Self {
1077            entity_defs: names.into_iter().collect(),
1078            common_defs: HashSet::new(),
1079            action_defs: HashSet::new(),
1080        }
1081    }
1082}
1083
1084/// A common type reference resolver.
1085/// This resolver is designed to operate on fully-qualified references.
1086/// It facilitates inlining the definitions of common types.
1087///
1088/// INVARIANT: There should be no dangling references. That is, all common-type
1089/// references that occur in the [`json_schema::Type`]s in `defs`, should be to
1090/// common types that appear as keys in `defs`.
1091/// This invariant is upheld by callers because the process of converting
1092/// references to fully-qualified ensures that the targets exist (else, it
1093/// throws [`TypeNotDefinedError`]).
1094#[derive(Debug)]
1095struct CommonTypeResolver<'a> {
1096    /// Definition of each common type.
1097    ///
1098    /// Definitions (values in the map) may refer to other common-type names,
1099    /// but not in a way that causes a cycle.
1100    ///
1101    /// In this map, names are already fully-qualified, both in common-type
1102    /// definitions (keys in the map) and in common-type references appearing in
1103    /// [`json_schema::Type`]s (values in the map).
1104    defs: &'a HashMap<InternalName, json_schema::Type<InternalName>>,
1105    /// The dependency graph among common type names.
1106    /// The graph contains a vertex for each [`InternalName`], and
1107    /// `graph.get(u)` gives the set of vertices `v` for which `(u,v)` is a
1108    /// directed edge in the graph.
1109    ///
1110    /// In this map, names are already fully-qualified, both in keys and values
1111    /// in the map.
1112    graph: HashMap<&'a InternalName, HashSet<&'a InternalName>>,
1113}
1114
1115impl<'a> CommonTypeResolver<'a> {
1116    /// Construct the resolver.
1117    /// Note that this requires that all common-type references are already
1118    /// fully qualified, because it uses [`InternalName`] and not [`RawName`].
1119    ///
1120    /// INVARIANT: There should be no dangling references. That is, all common-type
1121    /// references that occur in the [`json_schema::Type`]s in `defs`, should be
1122    /// to common types that appear as keys in `defs`.
1123    /// This invariant is upheld by callers because the process of converting
1124    /// references to fully-qualified ensures that the targets exist (else, it
1125    /// throws [`TypeNotDefinedError`]).
1126    fn new(defs: &'a HashMap<InternalName, json_schema::Type<InternalName>>) -> Self {
1127        let mut graph = HashMap::new();
1128        for (name, ty) in defs {
1129            graph.insert(name, HashSet::from_iter(ty.common_type_references()));
1130        }
1131        Self { defs, graph }
1132    }
1133
1134    /// Perform topological sort on the dependency graph
1135    ///
1136    /// Let A -> B denote the RHS of type `A` refers to type `B` (i.e., `A`
1137    /// depends on `B`)
1138    ///
1139    /// `topo_sort(A -> B -> C)` produces [C, B, A]
1140    ///
1141    /// If there is a cycle, a type name involving in this cycle is the error
1142    ///
1143    /// It implements a variant of Kahn's algorithm
1144    fn topo_sort(&self) -> std::result::Result<Vec<&'a InternalName>, InternalName> {
1145        // The in-degree map
1146        // Note that the keys of this map may be a superset of all common type
1147        // names
1148        let mut indegrees: HashMap<&InternalName, usize> = HashMap::new();
1149        for (ty_name, deps) in self.graph.iter() {
1150            // Ensure that declared common types have values in `indegrees`
1151            indegrees.entry(ty_name).or_insert(0);
1152            for dep in deps {
1153                match indegrees.entry(dep) {
1154                    std::collections::hash_map::Entry::Occupied(mut o) => {
1155                        o.insert(o.get() + 1);
1156                    }
1157                    std::collections::hash_map::Entry::Vacant(v) => {
1158                        v.insert(1);
1159                    }
1160                }
1161            }
1162        }
1163
1164        // The set that contains type names with zero incoming edges
1165        let mut work_set: HashSet<&'a InternalName> = HashSet::new();
1166        let mut res: Vec<&'a InternalName> = Vec::new();
1167
1168        // Find all type names with zero incoming edges
1169        for (name, degree) in indegrees.iter() {
1170            let name = *name;
1171            if *degree == 0 {
1172                work_set.insert(name);
1173                // The result only contains *declared* type names
1174                if self.graph.contains_key(name) {
1175                    res.push(name);
1176                }
1177            }
1178        }
1179
1180        // Pop a node
1181        while let Some(name) = work_set.iter().next().cloned() {
1182            work_set.remove(name);
1183            if let Some(deps) = self.graph.get(name) {
1184                for dep in deps {
1185                    if let Some(degree) = indegrees.get_mut(dep) {
1186                        // There will not be any underflows here because
1187                        // in order for the in-degree to underflow, `dep`'s
1188                        // in-degree must be 0 at this point
1189                        // The only possibility where a node's in-degree
1190                        // becomes 0 is through the subtraction below, which
1191                        // means it has been visited and hence has 0 in-degrees
1192                        // In other words, all its in-coming edges have been
1193                        // "removed" and hence contradicts with the fact that
1194                        // one of them is being "removed"
1195                        *degree -= 1;
1196                        if *degree == 0 {
1197                            work_set.insert(dep);
1198                            if self.graph.contains_key(dep) {
1199                                res.push(dep);
1200                            }
1201                        }
1202                    }
1203                }
1204            }
1205        }
1206
1207        // The set of nodes that have not been added to the result
1208        // i.e., there are still in-coming edges and hence exists a cycle
1209        let mut set: HashSet<&InternalName> = HashSet::from_iter(self.graph.keys().cloned());
1210        for name in res.iter() {
1211            set.remove(name);
1212        }
1213
1214        if let Some(cycle) = set.into_iter().next() {
1215            Err(cycle.clone())
1216        } else {
1217            // We need to reverse the result because, e.g.,
1218            // `res` is now [A,B,C] for A -> B -> C because no one depends on A
1219            res.reverse();
1220            Ok(res)
1221        }
1222    }
1223
1224    // Substitute common type references in `ty` according to `resolve_table`
1225    fn resolve_type(
1226        resolve_table: &HashMap<&InternalName, json_schema::Type<InternalName>>,
1227        ty: json_schema::Type<InternalName>,
1228    ) -> Result<json_schema::Type<InternalName>> {
1229        match ty {
1230            json_schema::Type::CommonTypeRef { type_name } => resolve_table
1231                .get(&type_name)
1232                .ok_or(CommonTypeInvariantViolationError { name: type_name }.into())
1233                .cloned(),
1234            json_schema::Type::Type(json_schema::TypeVariant::EntityOrCommon { type_name }) => {
1235                match resolve_table.get(&type_name) {
1236                    Some(def) => Ok(def.clone()),
1237                    None => Ok(json_schema::Type::Type(json_schema::TypeVariant::Entity {
1238                        name: type_name,
1239                    })),
1240                }
1241            }
1242            json_schema::Type::Type(json_schema::TypeVariant::Set { element }) => {
1243                Ok(json_schema::Type::Type(json_schema::TypeVariant::Set {
1244                    element: Box::new(Self::resolve_type(resolve_table, *element)?),
1245                }))
1246            }
1247            json_schema::Type::Type(json_schema::TypeVariant::Record(
1248                json_schema::RecordType {
1249                    attributes,
1250                    additional_attributes,
1251                },
1252            )) => Ok(json_schema::Type::Type(json_schema::TypeVariant::Record(
1253                json_schema::RecordType {
1254                    attributes: BTreeMap::from_iter(
1255                        attributes
1256                            .into_iter()
1257                            .map(|(attr, attr_ty)| {
1258                                Ok((
1259                                    attr,
1260                                    json_schema::RecordAttributeType {
1261                                        required: attr_ty.required,
1262                                        ty: Self::resolve_type(resolve_table, attr_ty.ty)?,
1263                                    },
1264                                ))
1265                            })
1266                            .collect::<Result<Vec<(_, _)>>>()?,
1267                    ),
1268                    additional_attributes,
1269                },
1270            ))),
1271            _ => Ok(ty),
1272        }
1273    }
1274
1275    // Resolve common type references, returning a map from (fully-qualified)
1276    // [`InternalName`] of a common type to its [`Type`] definition
1277    fn resolve(&self, extensions: &Extensions<'_>) -> Result<HashMap<&'a InternalName, Type>> {
1278        let sorted_names = self.topo_sort().map_err(|n| {
1279            SchemaError::CycleInCommonTypeReferences(CycleInCommonTypeReferencesError(n))
1280        })?;
1281
1282        let mut resolve_table: HashMap<&InternalName, json_schema::Type<InternalName>> =
1283            HashMap::new();
1284        let mut tys: HashMap<&'a InternalName, Type> = HashMap::new();
1285
1286        for &name in sorted_names.iter() {
1287            // PANIC SAFETY: `name.basename()` should be an existing common type id
1288            #[allow(clippy::unwrap_used)]
1289            let ty = self.defs.get(name).unwrap();
1290            let substituted_ty = Self::resolve_type(&resolve_table, ty.clone())?;
1291            resolve_table.insert(name, substituted_ty.clone());
1292            tys.insert(
1293                name,
1294                try_jsonschema_type_into_validator_type(substituted_ty, extensions)?
1295                    .resolve_common_type_refs(&HashMap::new())?,
1296            );
1297        }
1298
1299        Ok(tys)
1300    }
1301}
1302
1303// PANIC SAFETY unit tests
1304#[allow(clippy::panic)]
1305// PANIC SAFETY unit tests
1306#[allow(clippy::indexing_slicing)]
1307#[cfg(test)]
1308pub(crate) mod test {
1309    use std::{
1310        collections::{BTreeMap, HashSet},
1311        str::FromStr,
1312    };
1313
1314    use crate::json_schema;
1315    use crate::types::Type;
1316
1317    use cedar_policy_core::ast::RestrictedExpr;
1318    use cedar_policy_core::test_utils::{expect_err, ExpectedErrorMessageBuilder};
1319    use cool_asserts::assert_matches;
1320
1321    use serde_json::json;
1322
1323    use super::*;
1324
1325    /// Transform the output of functions like
1326    /// `ValidatorSchema::from_cedarschema_str()`, which has type `(ValidatorSchema, impl Iterator<...>)`,
1327    /// into `(ValidatorSchema, Vec<...>)`, which implements `Debug` and thus can be used with
1328    /// `assert_matches`, `.unwrap_err()`, etc
1329    pub fn collect_warnings<A, B, E>(
1330        r: std::result::Result<(A, impl Iterator<Item = B>), E>,
1331    ) -> std::result::Result<(A, Vec<B>), E> {
1332        r.map(|(a, iter)| (a, iter.collect()))
1333    }
1334
1335    // Well-formed schema
1336    #[test]
1337    fn test_from_schema_file() {
1338        let src = json!(
1339        {
1340            "entityTypes": {
1341                "User": {
1342                    "memberOfTypes": [ "Group" ]
1343                },
1344                "Group": {
1345                    "memberOfTypes": []
1346                },
1347                "Photo": {
1348                    "memberOfTypes": [ "Album" ]
1349                },
1350                "Album": {
1351                    "memberOfTypes": []
1352                }
1353            },
1354            "actions": {
1355                "view_photo": {
1356                    "appliesTo": {
1357                        "principalTypes": ["User", "Group"],
1358                        "resourceTypes": ["Photo"]
1359                    }
1360                }
1361            }
1362        });
1363        let schema_file: json_schema::NamespaceDefinition<RawName> =
1364            serde_json::from_value(src).unwrap();
1365        let schema: Result<ValidatorSchema> = schema_file.try_into();
1366        assert!(schema.is_ok());
1367    }
1368
1369    // Duplicate entity "Photo"
1370    #[test]
1371    fn test_from_schema_file_duplicate_entity() {
1372        // Test written using `from_str` instead of `from_value` because the
1373        // `json!` macro silently ignores duplicate map keys.
1374        let src = r#"
1375        {"": {
1376            "entityTypes": {
1377                "User": {
1378                    "memberOfTypes": [ "Group" ]
1379                },
1380                "Group": {
1381                    "memberOfTypes": []
1382                },
1383                "Photo": {
1384                    "memberOfTypes": [ "Album" ]
1385                },
1386                "Photo": {
1387                    "memberOfTypes": []
1388                }
1389            },
1390            "actions": {
1391                "view_photo": {
1392                    "memberOf": [],
1393                    "appliesTo": {
1394                        "principalTypes": ["User", "Group"],
1395                        "resourceTypes": ["Photo"]
1396                    }
1397                }
1398            }
1399        }}"#;
1400
1401        match ValidatorSchema::from_json_str(src, Extensions::all_available()) {
1402            Err(SchemaError::JsonDeserialization(_)) => (),
1403            _ => panic!("Expected JSON deserialization error due to duplicate entity type."),
1404        }
1405    }
1406
1407    // Duplicate action "view_photo"
1408    #[test]
1409    fn test_from_schema_file_duplicate_action() {
1410        // Test written using `from_str` instead of `from_value` because the
1411        // `json!` macro silently ignores duplicate map keys.
1412        let src = r#"
1413        {"": {
1414            "entityTypes": {
1415                "User": {
1416                    "memberOfTypes": [ "Group" ]
1417                },
1418                "Group": {
1419                    "memberOfTypes": []
1420                },
1421                "Photo": {
1422                    "memberOfTypes": []
1423                }
1424            },
1425            "actions": {
1426                "view_photo": {
1427                    "memberOf": [],
1428                    "appliesTo": {
1429                        "principalTypes": ["User", "Group"],
1430                        "resourceTypes": ["Photo"]
1431                    }
1432                },
1433                "view_photo": { }
1434            }
1435        }"#;
1436        match ValidatorSchema::from_json_str(src, Extensions::all_available()) {
1437            Err(SchemaError::JsonDeserialization(_)) => (),
1438            _ => panic!("Expected JSON deserialization error due to duplicate action type."),
1439        }
1440    }
1441
1442    // Undefined entity types "Grop", "Usr", "Phoot"
1443    #[test]
1444    fn test_from_schema_file_undefined_entities() {
1445        let src = json!(
1446        {
1447            "entityTypes": {
1448                "User": {
1449                    "memberOfTypes": [ "Grop" ]
1450                },
1451                "Group": {
1452                    "memberOfTypes": []
1453                },
1454                "Photo": {
1455                    "memberOfTypes": []
1456                }
1457            },
1458            "actions": {
1459                "view_photo": {
1460                    "appliesTo": {
1461                        "principalTypes": ["Usr", "Group"],
1462                        "resourceTypes": ["Phoot"]
1463                    }
1464                }
1465            }
1466        });
1467        let schema_file: json_schema::NamespaceDefinition<RawName> =
1468            serde_json::from_value(src.clone()).unwrap();
1469        let schema: Result<ValidatorSchema> = schema_file.try_into();
1470        assert_matches!(schema, Err(e) => {
1471            expect_err(
1472                &src,
1473                &miette::Report::new(e),
1474                &ExpectedErrorMessageBuilder::error(r#"failed to resolve types: Grop, Usr, Phoot"#)
1475                    .help("`Grop` has not been declared as an entity type")
1476                    .build());
1477        });
1478    }
1479
1480    #[test]
1481    fn undefined_entity_namespace_member_of() {
1482        let src = json!(
1483        {"Foo": {
1484            "entityTypes": {
1485                "User": {
1486                    "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1487                },
1488                "Group": { }
1489            },
1490            "actions": {}
1491        }});
1492        let schema_file = json_schema::Fragment::from_json_value(src.clone()).unwrap();
1493        let schema: Result<ValidatorSchema> = schema_file.try_into();
1494        assert_matches!(schema, Err(e) => {
1495            expect_err(
1496                &src,
1497                &miette::Report::new(e),
1498                &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: Bar::Group"#)
1499                    .help("`Bar::Group` has not been declared as an entity type")
1500                    .build());
1501        });
1502    }
1503
1504    #[test]
1505    fn undefined_entity_namespace_applies_to() {
1506        let src = json!(
1507        {"Foo": {
1508            "entityTypes": { "User": { }, "Photo": { } },
1509            "actions": {
1510                "view_photo": {
1511                    "appliesTo": {
1512                        "principalTypes": ["Foo::User", "Bar::User"],
1513                        "resourceTypes": ["Photo", "Bar::Photo"],
1514                    }
1515                }
1516            }
1517        }});
1518        let schema_file = json_schema::Fragment::from_json_value(src.clone()).unwrap();
1519        let schema: Result<ValidatorSchema> = schema_file.try_into();
1520        assert_matches!(schema, Err(e) => {
1521            expect_err(
1522                &src,
1523                &miette::Report::new(e),
1524                &ExpectedErrorMessageBuilder::error(r#"failed to resolve types: Bar::User, Bar::Photo"#)
1525                    .help("`Bar::User` has not been declared as an entity type")
1526                    .build());
1527        });
1528    }
1529
1530    // Undefined action "photo_actions"
1531    #[test]
1532    fn test_from_schema_file_undefined_action() {
1533        let src = json!(
1534        {
1535            "entityTypes": {
1536                "User": {
1537                    "memberOfTypes": [ "Group" ]
1538                },
1539                "Group": {
1540                    "memberOfTypes": []
1541                },
1542                "Photo": {
1543                    "memberOfTypes": []
1544                }
1545            },
1546            "actions": {
1547                "view_photo": {
1548                    "memberOf": [ {"id": "photo_action"} ],
1549                    "appliesTo": {
1550                        "principalTypes": ["User", "Group"],
1551                        "resourceTypes": ["Photo"]
1552                    }
1553                }
1554            }
1555        });
1556        let schema_file: json_schema::NamespaceDefinition<RawName> =
1557            serde_json::from_value(src.clone()).unwrap();
1558        let schema: Result<ValidatorSchema> = schema_file.try_into();
1559        assert_matches!(schema, Err(e) => {
1560            expect_err(
1561                &src,
1562                &miette::Report::new(e),
1563                &ExpectedErrorMessageBuilder::error(r#"undeclared action: Action::"photo_action""#)
1564                    .help("any actions appearing as parents need to be declared as actions")
1565                    .build());
1566        });
1567    }
1568
1569    // Trivial cycle in action hierarchy
1570    // view_photo -> view_photo
1571    #[test]
1572    fn test_from_schema_file_action_cycle1() {
1573        let src = json!(
1574        {
1575            "entityTypes": {},
1576            "actions": {
1577                "view_photo": {
1578                    "memberOf": [ {"id": "view_photo"} ]
1579                }
1580            }
1581        });
1582        let schema_file: json_schema::NamespaceDefinition<RawName> =
1583            serde_json::from_value(src).unwrap();
1584        let schema: Result<ValidatorSchema> = schema_file.try_into();
1585        assert_matches!(
1586            schema,
1587            Err(SchemaError::CycleInActionHierarchy(CycleInActionHierarchyError(euid))) => {
1588                assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
1589            }
1590        )
1591    }
1592
1593    // Slightly more complex cycle in action hierarchy
1594    // view_photo -> edit_photo -> delete_photo -> view_photo
1595    #[test]
1596    fn test_from_schema_file_action_cycle2() {
1597        let src = json!(
1598        {
1599            "entityTypes": {},
1600            "actions": {
1601                "view_photo": {
1602                    "memberOf": [ {"id": "edit_photo"} ]
1603                },
1604                "edit_photo": {
1605                    "memberOf": [ {"id": "delete_photo"} ]
1606                },
1607                "delete_photo": {
1608                    "memberOf": [ {"id": "view_photo"} ]
1609                },
1610                "other_action": {
1611                    "memberOf": [ {"id": "edit_photo"} ]
1612                }
1613            }
1614        });
1615        let schema_file: json_schema::NamespaceDefinition<RawName> =
1616            serde_json::from_value(src).unwrap();
1617        let schema: Result<ValidatorSchema> = schema_file.try_into();
1618        assert_matches!(
1619            schema,
1620            // The exact action reported as being in the cycle isn't deterministic.
1621            Err(SchemaError::CycleInActionHierarchy(_)),
1622        )
1623    }
1624
1625    #[test]
1626    fn namespaced_schema() {
1627        let src = r#"
1628        { "N::S": {
1629            "entityTypes": {
1630                "User": {},
1631                "Photo": {}
1632            },
1633            "actions": {
1634                "view_photo": {
1635                    "appliesTo": {
1636                        "principalTypes": ["User"],
1637                        "resourceTypes": ["Photo"]
1638                    }
1639                }
1640            }
1641        } }
1642        "#;
1643        let schema_file = json_schema::Fragment::from_json_str(src).unwrap();
1644        let schema: ValidatorSchema = schema_file
1645            .try_into()
1646            .expect("Namespaced schema failed to convert.");
1647        dbg!(&schema);
1648        let user_entity_type = &"N::S::User"
1649            .parse()
1650            .expect("Namespaced entity type should have parsed");
1651        let photo_entity_type = &"N::S::Photo"
1652            .parse()
1653            .expect("Namespaced entity type should have parsed");
1654        assert!(
1655            schema.entity_types.contains_key(user_entity_type),
1656            "Expected and entity type User."
1657        );
1658        assert!(
1659            schema.entity_types.contains_key(photo_entity_type),
1660            "Expected an entity type Photo."
1661        );
1662        assert_eq!(
1663            schema.entity_types.len(),
1664            2,
1665            "Expected exactly 2 entity types."
1666        );
1667        assert!(
1668            schema.action_ids.contains_key(
1669                &"N::S::Action::\"view_photo\""
1670                    .parse()
1671                    .expect("Namespaced action should have parsed")
1672            ),
1673            "Expected an action \"view_photo\"."
1674        );
1675        assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1676
1677        let action = &schema.action_ids.values().next().expect("Expected Action");
1678        assert_eq!(
1679            action.applies_to_principals().collect::<Vec<_>>(),
1680            vec![user_entity_type]
1681        );
1682        assert_eq!(
1683            action.applies_to_resources().collect::<Vec<_>>(),
1684            vec![photo_entity_type]
1685        );
1686    }
1687
1688    #[test]
1689    fn cant_use_namespace_in_entity_type() {
1690        let src = r#"
1691        {
1692            "entityTypes": { "NS::User": {} },
1693            "actions": {}
1694        }
1695        "#;
1696        assert_matches!(
1697            serde_json::from_str::<json_schema::NamespaceDefinition<RawName>>(src),
1698            Err(_)
1699        );
1700    }
1701
1702    #[test]
1703    fn entity_attribute_entity_type_with_namespace() {
1704        let src = json!(
1705        {"A::B": {
1706            "entityTypes": {
1707                "Foo": {
1708                    "shape": {
1709                        "type": "Record",
1710                        "attributes": {
1711                            "name": { "type": "Entity", "name": "C::D::Foo" }
1712                        }
1713                    }
1714                }
1715            },
1716            "actions": {}
1717          }});
1718        let schema_json = json_schema::Fragment::from_json_value(src.clone()).unwrap();
1719        let schema: Result<ValidatorSchema> = schema_json.try_into();
1720        assert_matches!(schema, Err(e) => {
1721            expect_err(
1722                &src,
1723                &miette::Report::new(e),
1724                &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: C::D::Foo"#)
1725                    .help("`C::D::Foo` has not been declared as an entity type")
1726                    .build());
1727        });
1728    }
1729
1730    #[test]
1731    fn entity_attribute_entity_type_with_declared_namespace() {
1732        let schema_json = json_schema::Fragment::from_json_str(
1733            r#"
1734            {"A::B": {
1735                "entityTypes": {
1736                    "Foo": {
1737                        "shape": {
1738                            "type": "Record",
1739                            "attributes": {
1740                                "name": { "type": "Entity", "name": "A::B::Foo" }
1741                            }
1742                        }
1743                    }
1744                },
1745                "actions": {}
1746              }}
1747            "#,
1748        )
1749        .unwrap();
1750
1751        let schema: ValidatorSchema = schema_json
1752            .try_into()
1753            .expect("Expected schema to construct without error.");
1754
1755        let foo_name: EntityType = "A::B::Foo".parse().expect("Expected entity type name");
1756        let foo_type = schema
1757            .entity_types
1758            .get(&foo_name)
1759            .expect("Expected to find entity");
1760        let name_type = foo_type
1761            .attr("name")
1762            .expect("Expected attribute name")
1763            .attr_type
1764            .clone();
1765        assert_eq!(name_type, Type::named_entity_reference(foo_name));
1766    }
1767
1768    #[test]
1769    fn cannot_declare_action_type_when_prohibited() {
1770        let schema_json: json_schema::NamespaceDefinition<RawName> = serde_json::from_str(
1771            r#"
1772            {
1773                "entityTypes": { "Action": {} },
1774                "actions": {}
1775              }
1776            "#,
1777        )
1778        .unwrap();
1779        let schema: Result<ValidatorSchema> = schema_json.try_into();
1780        assert!(matches!(
1781            schema,
1782            Err(SchemaError::ActionEntityTypeDeclared(_))
1783        ));
1784    }
1785
1786    #[test]
1787    fn can_declare_other_type_when_action_type_prohibited() {
1788        let schema_json: json_schema::NamespaceDefinition<RawName> = serde_json::from_str(
1789            r#"
1790            {
1791                "entityTypes": { "Foo": { } },
1792                "actions": {}
1793              }
1794            "#,
1795        )
1796        .unwrap();
1797
1798        TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1799    }
1800
1801    #[test]
1802    fn cannot_declare_action_in_group_when_prohibited() {
1803        let schema_json = json_schema::Fragment::from_json_str(
1804            r#"
1805            {"": {
1806                "entityTypes": {},
1807                "actions": {
1808                    "universe": { },
1809                    "view_photo": {
1810                        "attributes": {"id": "universe"}
1811                    },
1812                    "edit_photo": {
1813                        "attributes": {"id": "universe"}
1814                    },
1815                    "delete_photo": {
1816                        "attributes": {"id": "universe"}
1817                    }
1818                }
1819              }}
1820            "#,
1821        )
1822        .unwrap();
1823
1824        let schema = ValidatorSchemaFragment::from_schema_fragment(
1825            schema_json,
1826            ActionBehavior::ProhibitAttributes,
1827            Extensions::all_available(),
1828        );
1829        match schema {
1830            Err(e) => {
1831                expect_err(
1832                    "",
1833                    &miette::Report::new(e),
1834                    &ExpectedErrorMessageBuilder::error("unsupported feature used in schema")
1835                        .source(r#"action declared with attributes: [delete_photo, edit_photo, view_photo]"#)
1836                        .build()
1837                )
1838            }
1839            _ => panic!("Did not see expected error."),
1840        }
1841    }
1842
1843    #[test]
1844    fn test_entity_type_no_namespace() {
1845        let src = json!({"type": "Entity", "name": "Foo"});
1846        let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
1847        assert_eq!(
1848            schema_ty,
1849            json_schema::Type::Type(json_schema::TypeVariant::Entity {
1850                name: "Foo".parse().unwrap()
1851            })
1852        );
1853        let schema_ty = schema_ty.conditionally_qualify_type_references(Some(
1854            &InternalName::parse_unqualified_name("NS").unwrap(),
1855        ));
1856        let all_defs = AllDefs::from_entity_defs([
1857            InternalName::from_str("NS::Foo").unwrap(),
1858            InternalName::from_str("Bar").unwrap(),
1859        ]);
1860        let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
1861        let ty: Type =
1862            try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available())
1863                .expect("Error converting schema type to type.")
1864                .resolve_common_type_refs(&HashMap::new())
1865                .unwrap();
1866        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1867    }
1868
1869    #[test]
1870    fn test_entity_type_namespace() {
1871        let src = json!({"type": "Entity", "name": "NS::Foo"});
1872        let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
1873        assert_eq!(
1874            schema_ty,
1875            json_schema::Type::Type(json_schema::TypeVariant::Entity {
1876                name: "NS::Foo".parse().unwrap()
1877            })
1878        );
1879        let schema_ty = schema_ty.conditionally_qualify_type_references(Some(
1880            &InternalName::parse_unqualified_name("NS").unwrap(),
1881        ));
1882        let all_defs = AllDefs::from_entity_defs([
1883            InternalName::from_str("NS::Foo").unwrap(),
1884            InternalName::from_str("Foo").unwrap(),
1885        ]);
1886        let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
1887        let ty: Type =
1888            try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available())
1889                .expect("Error converting schema type to type.")
1890                .resolve_common_type_refs(&HashMap::new())
1891                .unwrap();
1892        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1893    }
1894
1895    #[test]
1896    fn test_entity_type_namespace_parse_error() {
1897        let src = json!({"type": "Entity", "name": "::Foo"});
1898        assert_matches!(
1899            serde_json::from_value::<json_schema::Type<RawName>>(src),
1900            Err(_)
1901        );
1902    }
1903
1904    #[test]
1905    fn schema_type_record_is_validator_type_record() {
1906        let src = json!({"type": "Record", "attributes": {}});
1907        let schema_ty: json_schema::Type<RawName> = serde_json::from_value(src).unwrap();
1908        assert_eq!(
1909            schema_ty,
1910            json_schema::Type::Type(json_schema::TypeVariant::Record(json_schema::RecordType {
1911                attributes: BTreeMap::new(),
1912                additional_attributes: false,
1913            })),
1914        );
1915        let schema_ty = schema_ty.conditionally_qualify_type_references(None);
1916        let all_defs = AllDefs::from_entity_defs([InternalName::from_str("Foo").unwrap()]);
1917        let schema_ty = schema_ty.fully_qualify_type_references(&all_defs).unwrap();
1918        let ty: Type =
1919            try_jsonschema_type_into_validator_type(schema_ty, Extensions::all_available())
1920                .expect("Error converting schema type to type.")
1921                .resolve_common_type_refs(&HashMap::new())
1922                .unwrap();
1923        assert_eq!(ty, Type::closed_record_with_attributes(None));
1924    }
1925
1926    #[test]
1927    fn get_namespaces() {
1928        let fragment = json_schema::Fragment::from_json_value(json!({
1929            "Foo::Bar::Baz": {
1930                "entityTypes": {},
1931                "actions": {}
1932            },
1933            "Foo": {
1934                "entityTypes": {},
1935                "actions": {}
1936            },
1937            "Bar": {
1938                "entityTypes": {},
1939                "actions": {}
1940            },
1941        }))
1942        .unwrap();
1943
1944        let schema_fragment: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
1945            fragment.try_into().unwrap();
1946        assert_eq!(
1947            schema_fragment
1948                .0
1949                .iter()
1950                .map(|f| f.namespace())
1951                .collect::<HashSet<_>>(),
1952            HashSet::from([
1953                Some(&"Foo::Bar::Baz".parse().unwrap()),
1954                Some(&"Foo".parse().unwrap()),
1955                Some(&"Bar".parse().unwrap())
1956            ])
1957        );
1958    }
1959
1960    #[test]
1961    fn schema_no_fragments() {
1962        let schema =
1963            ValidatorSchema::from_schema_fragments([], Extensions::all_available()).unwrap();
1964        assert!(schema.entity_types.is_empty());
1965        assert!(schema.action_ids.is_empty());
1966    }
1967
1968    #[test]
1969    fn same_action_different_namespace() {
1970        let fragment = json_schema::Fragment::from_json_value(json!({
1971            "Foo::Bar": {
1972                "entityTypes": {},
1973                "actions": {
1974                    "Baz": {}
1975                }
1976            },
1977            "Bar::Foo": {
1978                "entityTypes": {},
1979                "actions": {
1980                    "Baz": { }
1981                }
1982            },
1983            "Biz": {
1984                "entityTypes": {},
1985                "actions": {
1986                    "Baz": { }
1987                }
1988            }
1989        }))
1990        .unwrap();
1991
1992        let schema: ValidatorSchema = fragment.try_into().unwrap();
1993        assert!(schema
1994            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1995            .is_some());
1996        assert!(schema
1997            .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1998            .is_some());
1999        assert!(schema
2000            .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2001            .is_some());
2002    }
2003
2004    #[test]
2005    fn same_type_different_namespace() {
2006        let fragment = json_schema::Fragment::from_json_value(json!({
2007            "Foo::Bar": {
2008                "entityTypes": {"Baz" : {}},
2009                "actions": { }
2010            },
2011            "Bar::Foo": {
2012                "entityTypes": {"Baz" : {}},
2013                "actions": { }
2014            },
2015            "Biz": {
2016                "entityTypes": {"Baz" : {}},
2017                "actions": { }
2018            }
2019        }))
2020        .unwrap();
2021        let schema: ValidatorSchema = fragment.try_into().unwrap();
2022
2023        assert!(schema
2024            .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
2025            .is_some());
2026        assert!(schema
2027            .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
2028            .is_some());
2029        assert!(schema
2030            .get_entity_type(&"Biz::Baz".parse().unwrap())
2031            .is_some());
2032    }
2033
2034    #[test]
2035    fn member_of_different_namespace() {
2036        let fragment = json_schema::Fragment::from_json_value(json!({
2037            "Bar": {
2038                "entityTypes": {
2039                    "Baz": {
2040                        "memberOfTypes": ["Foo::Buz"]
2041                    }
2042                },
2043                "actions": {}
2044            },
2045            "Foo": {
2046                "entityTypes": { "Buz": {} },
2047                "actions": { }
2048            }
2049        }))
2050        .unwrap();
2051        let schema: ValidatorSchema = fragment.try_into().unwrap();
2052
2053        let buz = schema
2054            .get_entity_type(&"Foo::Buz".parse().unwrap())
2055            .unwrap();
2056        assert_eq!(
2057            buz.descendants,
2058            HashSet::from(["Bar::Baz".parse().unwrap()])
2059        );
2060    }
2061
2062    #[test]
2063    fn attribute_different_namespace() {
2064        let fragment = json_schema::Fragment::from_json_value(json!({
2065            "Bar": {
2066                "entityTypes": {
2067                    "Baz": {
2068                        "shape": {
2069                            "type": "Record",
2070                            "attributes": {
2071                                "fiz": {
2072                                    "type": "Entity",
2073                                    "name": "Foo::Buz"
2074                                }
2075                            }
2076                        }
2077                    }
2078                },
2079                "actions": {}
2080            },
2081            "Foo": {
2082                "entityTypes": { "Buz": {} },
2083                "actions": { }
2084            }
2085        }))
2086        .unwrap();
2087
2088        let schema: ValidatorSchema = fragment.try_into().unwrap();
2089        let baz = schema
2090            .get_entity_type(&"Bar::Baz".parse().unwrap())
2091            .unwrap();
2092        assert_eq!(
2093            baz.attr("fiz").unwrap().attr_type,
2094            Type::named_entity_reference_from_str("Foo::Buz"),
2095        );
2096    }
2097
2098    #[test]
2099    fn applies_to_different_namespace() {
2100        let fragment = json_schema::Fragment::from_json_value(json!({
2101            "Foo::Bar": {
2102                "entityTypes": { },
2103                "actions": {
2104                    "Baz": {
2105                        "appliesTo": {
2106                            "principalTypes": [ "Fiz::Buz" ],
2107                            "resourceTypes": [ "Fiz::Baz" ],
2108                        }
2109                    }
2110                }
2111            },
2112            "Fiz": {
2113                "entityTypes": {
2114                    "Buz": {},
2115                    "Baz": {}
2116                },
2117                "actions": { }
2118            }
2119        }))
2120        .unwrap();
2121        let schema: ValidatorSchema = fragment.try_into().unwrap();
2122
2123        let baz = schema
2124            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2125            .unwrap();
2126        assert_eq!(
2127            baz.applies_to
2128                .applicable_principal_types()
2129                .collect::<HashSet<_>>(),
2130            HashSet::from([&("Fiz::Buz".parse().unwrap())])
2131        );
2132        assert_eq!(
2133            baz.applies_to
2134                .applicable_resource_types()
2135                .collect::<HashSet<_>>(),
2136            HashSet::from([&("Fiz::Baz".parse().unwrap())])
2137        );
2138    }
2139
2140    #[test]
2141    fn simple_defined_type() {
2142        let fragment = json_schema::Fragment::from_json_value(json!({
2143            "": {
2144                "commonTypes": {
2145                    "MyLong": {"type": "Long"}
2146                },
2147                "entityTypes": {
2148                    "User": {
2149                        "shape": {
2150                            "type": "Record",
2151                            "attributes": {
2152                                "a": {"type": "MyLong"}
2153                            }
2154                        }
2155                    }
2156                },
2157                "actions": {}
2158            }
2159        }))
2160        .unwrap();
2161        let schema: ValidatorSchema = fragment.try_into().unwrap();
2162        assert_eq!(
2163            schema.entity_types.iter().next().unwrap().1.attributes,
2164            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2165        );
2166    }
2167
2168    #[test]
2169    fn defined_record_as_attrs() {
2170        let fragment = json_schema::Fragment::from_json_value(json!({
2171            "": {
2172                "commonTypes": {
2173                    "MyRecord": {
2174                        "type": "Record",
2175                        "attributes":  {
2176                            "a": {"type": "Long"}
2177                        }
2178                    }
2179                },
2180                "entityTypes": {
2181                    "User": { "shape": { "type": "MyRecord", } }
2182                },
2183                "actions": {}
2184            }
2185        }))
2186        .unwrap();
2187        let schema: ValidatorSchema = fragment.try_into().unwrap();
2188        assert_eq!(
2189            schema.entity_types.iter().next().unwrap().1.attributes,
2190            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2191        );
2192    }
2193
2194    #[test]
2195    fn cross_namespace_type() {
2196        let fragment = json_schema::Fragment::from_json_value(json!({
2197            "A": {
2198                "commonTypes": {
2199                    "MyLong": {"type": "Long"}
2200                },
2201                "entityTypes": { },
2202                "actions": {}
2203            },
2204            "B": {
2205                "entityTypes": {
2206                    "User": {
2207                        "shape": {
2208                            "type": "Record",
2209                            "attributes": {
2210                                "a": {"type": "A::MyLong"}
2211                            }
2212                        }
2213                    }
2214                },
2215                "actions": {}
2216            }
2217        }))
2218        .unwrap();
2219        let schema: ValidatorSchema = fragment.try_into().unwrap();
2220        assert_eq!(
2221            schema.entity_types.iter().next().unwrap().1.attributes,
2222            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2223        );
2224    }
2225
2226    #[test]
2227    fn cross_fragment_type() {
2228        let fragment1: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2229            json_schema::Fragment::from_json_value(json!({
2230                "A": {
2231                    "commonTypes": {
2232                        "MyLong": {"type": "Long"}
2233                    },
2234                    "entityTypes": { },
2235                    "actions": {}
2236                }
2237            }))
2238            .unwrap()
2239            .try_into()
2240            .unwrap();
2241        let fragment2: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2242            json_schema::Fragment::from_json_value(json!({
2243                "A": {
2244                    "entityTypes": {
2245                        "User": {
2246                            "shape": {
2247                                "type": "Record",
2248                                "attributes": {
2249                                    "a": {"type": "MyLong"}
2250                                }
2251                            }
2252                        }
2253                    },
2254                    "actions": {}
2255                }
2256            }))
2257            .unwrap()
2258            .try_into()
2259            .unwrap();
2260        let schema = ValidatorSchema::from_schema_fragments(
2261            [fragment1, fragment2],
2262            Extensions::all_available(),
2263        )
2264        .unwrap();
2265
2266        assert_eq!(
2267            schema.entity_types.iter().next().unwrap().1.attributes,
2268            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2269        );
2270    }
2271
2272    #[test]
2273    fn cross_fragment_duplicate_type() {
2274        let fragment1: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2275            json_schema::Fragment::from_json_value(json!({
2276                "A": {
2277                    "commonTypes": {
2278                        "MyLong": {"type": "Long"}
2279                    },
2280                    "entityTypes": {},
2281                    "actions": {}
2282                }
2283            }))
2284            .unwrap()
2285            .try_into()
2286            .unwrap();
2287        let fragment2: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
2288            json_schema::Fragment::from_json_value(json!({
2289                "A": {
2290                    "commonTypes": {
2291                        "MyLong": {"type": "Long"}
2292                    },
2293                    "entityTypes": {},
2294                    "actions": {}
2295                }
2296            }))
2297            .unwrap()
2298            .try_into()
2299            .unwrap();
2300
2301        let schema = ValidatorSchema::from_schema_fragments(
2302            [fragment1, fragment2],
2303            Extensions::all_available(),
2304        );
2305
2306        // should error because schema fragments have duplicate types
2307        assert_matches!(schema, Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError(s))) => {
2308            assert_eq!(s, "A::MyLong".parse().unwrap());
2309        });
2310    }
2311
2312    #[test]
2313    fn undeclared_type_in_attr() {
2314        let fragment = json_schema::Fragment::from_json_value(json!({
2315            "": {
2316                "commonTypes": { },
2317                "entityTypes": {
2318                    "User": {
2319                        "shape": {
2320                            "type": "Record",
2321                            "attributes": {
2322                                "a": {"type": "MyLong"}
2323                            }
2324                        }
2325                    }
2326                },
2327                "actions": {}
2328            }
2329        }))
2330        .unwrap();
2331        assert_matches!(
2332            TryInto::<ValidatorSchema>::try_into(fragment),
2333            Err(SchemaError::TypeNotDefined(_))
2334        );
2335    }
2336
2337    #[test]
2338    fn undeclared_type_in_common_types() {
2339        let fragment = json_schema::Fragment::from_json_value(json!({
2340            "": {
2341                "commonTypes": {
2342                    "a": { "type": "b" }
2343                },
2344                "entityTypes": { },
2345                "actions": {}
2346            }
2347        }))
2348        .unwrap();
2349        assert_matches!(
2350            TryInto::<ValidatorSchema>::try_into(fragment),
2351            Err(SchemaError::TypeNotDefined(_))
2352        );
2353    }
2354
2355    #[test]
2356    fn shape_not_record() {
2357        let fragment = json_schema::Fragment::from_json_value(json!({
2358            "": {
2359                "commonTypes": {
2360                    "MyLong": { "type": "Long" }
2361                },
2362                "entityTypes": {
2363                    "User": {
2364                        "shape": { "type": "MyLong" }
2365                    }
2366                },
2367                "actions": {}
2368            }
2369        }))
2370        .unwrap();
2371        assert_matches!(
2372            TryInto::<ValidatorSchema>::try_into(fragment),
2373            Err(SchemaError::ContextOrShapeNotRecord(_))
2374        );
2375    }
2376
2377    /// This test checks for regressions on (adapted versions of) the examples
2378    /// mentioned in the thread at
2379    /// [cedar#134](https://github.com/cedar-policy/cedar/pull/134)
2380    #[test]
2381    fn counterexamples_from_cedar_134() {
2382        // non-normalized entity type name
2383        let bad1 = json!({
2384            "": {
2385                "entityTypes": {
2386                    "User // comment": {
2387                        "memberOfTypes": [
2388                            "UserGroup"
2389                        ]
2390                    },
2391                    "User": {
2392                        "memberOfTypes": [
2393                            "UserGroup"
2394                        ]
2395                    },
2396                    "UserGroup": {}
2397                },
2398                "actions": {}
2399            }
2400        });
2401        assert_matches!(json_schema::Fragment::from_json_value(bad1), Err(_));
2402
2403        // non-normalized schema namespace
2404        let bad2 = json!({
2405            "ABC     :: //comment \n XYZ  ": {
2406                "entityTypes": {
2407                    "User": {
2408                        "memberOfTypes": []
2409                    }
2410                },
2411                "actions": {}
2412            }
2413        });
2414        assert_matches!(json_schema::Fragment::from_json_value(bad2), Err(_));
2415    }
2416
2417    #[test]
2418    fn simple_action_entity() {
2419        let src = json!(
2420        {
2421            "entityTypes": { },
2422            "actions": {
2423                "view_photo": { },
2424            }
2425        });
2426
2427        let schema_file: json_schema::NamespaceDefinition<RawName> =
2428            serde_json::from_value(src).unwrap();
2429        let schema: ValidatorSchema = schema_file.try_into().unwrap();
2430        let actions = schema.action_entities().expect("Entity Construct Error");
2431
2432        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2433        let view_photo = actions.entity(&action_uid);
2434        assert_eq!(
2435            view_photo.unwrap(),
2436            &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
2437        );
2438    }
2439
2440    #[test]
2441    fn action_entity_hierarchy() {
2442        let src = json!(
2443        {
2444            "entityTypes": { },
2445            "actions": {
2446                "read": {},
2447                "view": {
2448                    "memberOf": [{"id": "read"}]
2449                },
2450                "view_photo": {
2451                    "memberOf": [{"id": "view"}]
2452                },
2453            }
2454        });
2455
2456        let schema_file: json_schema::NamespaceDefinition<RawName> =
2457            serde_json::from_value(src).unwrap();
2458        let schema: ValidatorSchema = schema_file.try_into().unwrap();
2459        let actions = schema.action_entities().expect("Entity Construct Error");
2460
2461        let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2462        let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2463        let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2464
2465        let view_photo_entity = actions.entity(&view_photo_uid);
2466        assert_eq!(
2467            view_photo_entity.unwrap(),
2468            &Entity::new_with_attr_partial_value(
2469                view_photo_uid,
2470                HashMap::new(),
2471                HashSet::from([view_uid.clone(), read_uid.clone()])
2472            )
2473        );
2474
2475        let view_entity = actions.entity(&view_uid);
2476        assert_eq!(
2477            view_entity.unwrap(),
2478            &Entity::new_with_attr_partial_value(
2479                view_uid,
2480                HashMap::new(),
2481                HashSet::from([read_uid.clone()])
2482            )
2483        );
2484
2485        let read_entity = actions.entity(&read_uid);
2486        assert_eq!(
2487            read_entity.unwrap(),
2488            &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
2489        );
2490    }
2491
2492    #[test]
2493    fn action_entity_attribute() {
2494        let src = json!(
2495        {
2496            "entityTypes": { },
2497            "actions": {
2498                "view_photo": {
2499                    "attributes": { "attr": "foo" }
2500                },
2501            }
2502        });
2503
2504        let schema_file: NamespaceDefinitionWithActionAttributes<RawName> =
2505            serde_json::from_value(src).unwrap();
2506        let schema: ValidatorSchema = schema_file.try_into().unwrap();
2507        let actions = schema.action_entities().expect("Entity Construct Error");
2508
2509        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2510        let view_photo = actions.entity(&action_uid);
2511        assert_eq!(
2512            view_photo.unwrap(),
2513            &Entity::new(
2514                action_uid,
2515                HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2516                HashSet::new(),
2517                Extensions::none(),
2518            )
2519            .unwrap(),
2520        );
2521    }
2522
2523    #[test]
2524    fn test_action_namespace_inference_multi_success() {
2525        let src = json!({
2526            "Foo" : {
2527                "entityTypes" : {},
2528                "actions" : {
2529                    "read" : {}
2530                }
2531            },
2532            "ExampleCo::Personnel" : {
2533                "entityTypes" : {},
2534                "actions" : {
2535                    "viewPhoto" : {
2536                        "memberOf" : [
2537                            {
2538                                "id" : "read",
2539                                "type" : "Foo::Action"
2540                            }
2541                        ]
2542                    }
2543                }
2544            },
2545        });
2546        let schema_fragment =
2547            json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
2548        let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2549        let view_photo = schema
2550            .action_entities_iter()
2551            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2552            .unwrap();
2553        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2554        let read = ancestors[0];
2555        let read_eid: &str = read.eid().as_ref();
2556        assert_eq!(read_eid, "read");
2557        assert_eq!(read.entity_type().to_string(), "Foo::Action");
2558    }
2559
2560    #[test]
2561    fn test_action_namespace_inference_multi() {
2562        let src = json!({
2563            "ExampleCo::Personnel::Foo" : {
2564                "entityTypes" : {},
2565                "actions" : {
2566                    "read" : {}
2567                }
2568            },
2569            "ExampleCo::Personnel" : {
2570                "entityTypes" : {},
2571                "actions" : {
2572                    "viewPhoto" : {
2573                        "memberOf" : [
2574                            {
2575                                "id" : "read",
2576                                "type" : "Foo::Action"
2577                            }
2578                        ]
2579                    }
2580                }
2581            },
2582        });
2583        let schema_fragment =
2584            json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
2585        let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2586        schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2587    }
2588
2589    #[test]
2590    fn test_action_namespace_inference() {
2591        let src = json!({
2592            "ExampleCo::Personnel" : {
2593                "entityTypes" : { },
2594                "actions" : {
2595                    "read" : {},
2596                    "viewPhoto" : {
2597                        "memberOf" : [
2598                            {
2599                                "id" :  "read",
2600                                "type" : "Action"
2601                            }
2602                        ]
2603                    }
2604                }
2605            }
2606        });
2607        let schema_fragment =
2608            json_schema::Fragment::from_json_value(src).expect("Failed to parse schema");
2609        let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2610        let view_photo = schema
2611            .action_entities_iter()
2612            .find(|e| e.uid() == &r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2613            .unwrap();
2614        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2615        let read = ancestors[0];
2616        let read_eid: &str = read.eid().as_ref();
2617        assert_eq!(read_eid, "read");
2618        assert_eq!(
2619            read.entity_type().to_string(),
2620            "ExampleCo::Personnel::Action"
2621        );
2622    }
2623
2624    #[test]
2625    fn fallback_to_empty_namespace() {
2626        let src = json!(
2627            {
2628                "Demo": {
2629                  "entityTypes": {
2630                    "User": {
2631                      "memberOfTypes": [],
2632                      "shape": {
2633                        "type": "Record",
2634                        "attributes": {
2635                          "id": { "type": "id" },
2636                        }
2637                      }
2638                    }
2639                  },
2640                  "actions": {}
2641                },
2642                "": {
2643                  "commonTypes": {
2644                    "id": {
2645                      "type": "String"
2646                    },
2647                  },
2648                  "entityTypes": {},
2649                  "actions": {}
2650                }
2651              }
2652        );
2653        let schema =
2654            ValidatorSchema::from_json_value(src.clone(), Extensions::all_available()).unwrap();
2655        let mut attributes = schema
2656            .get_entity_type(&"Demo::User".parse().unwrap())
2657            .unwrap()
2658            .attributes();
2659        let (attr_name, attr_ty) = attributes.next().unwrap();
2660        assert_eq!(attr_name, "id");
2661        assert_eq!(&attr_ty.attr_type, &Type::primitive_string());
2662        assert_matches!(attributes.next(), None);
2663    }
2664
2665    #[test]
2666    fn qualified_undeclared_common_types2() {
2667        let src = json!(
2668            {
2669                "Demo": {
2670                  "entityTypes": {
2671                    "User": {
2672                      "memberOfTypes": [],
2673                      "shape": {
2674                        "type": "Record",
2675                        "attributes": {
2676                          "id": { "type": "Demo::id" },
2677                        }
2678                      }
2679                    }
2680                  },
2681                  "actions": {}
2682                },
2683                "": {
2684                  "commonTypes": {
2685                    "id": {
2686                      "type": "String"
2687                    },
2688                  },
2689                  "entityTypes": {},
2690                  "actions": {}
2691                }
2692              }
2693        );
2694        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2695        assert_matches!(schema, Err(e) => {
2696            expect_err(
2697                &src,
2698                &miette::Report::new(e),
2699                &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: Demo::id"#)
2700                    .help("`Demo::id` has not been declared as a common type")
2701                    .build());
2702        });
2703    }
2704
2705    #[test]
2706    fn undeclared_entity_type_in_common_type() {
2707        let src = json!(
2708            {
2709                "": {
2710                  "commonTypes": {
2711                    "id": {
2712                      "type": "Entity",
2713                      "name": "undeclared"
2714                    },
2715                  },
2716                  "entityTypes": {},
2717                  "actions": {}
2718                }
2719              }
2720        );
2721        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2722        assert_matches!(schema, Err(e) => {
2723            expect_err(
2724                &src,
2725                &miette::Report::new(e),
2726                &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
2727                    .help("`undeclared` has not been declared as an entity type")
2728                    .build());
2729        });
2730    }
2731
2732    #[test]
2733    fn undeclared_entity_type_in_common_type_record() {
2734        let src = json!(
2735            {
2736                "": {
2737                  "commonTypes": {
2738                    "id": {
2739                      "type": "Record",
2740                      "attributes": {
2741                        "first": {
2742                            "type": "Entity",
2743                            "name": "undeclared"
2744                        }
2745                      }
2746                    },
2747                  },
2748                  "entityTypes": {},
2749                  "actions": {}
2750                }
2751              }
2752        );
2753        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2754        assert_matches!(schema, Err(e) => {
2755            expect_err(
2756                &src,
2757                &miette::Report::new(e),
2758                &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
2759                    .help("`undeclared` has not been declared as an entity type")
2760                    .build());
2761        });
2762    }
2763
2764    #[test]
2765    fn undeclared_entity_type_in_common_type_set() {
2766        let src = json!(
2767            {
2768                "": {
2769                  "commonTypes": {
2770                    "id": {
2771                      "type": "Set",
2772                      "element": {
2773                        "type": "Entity",
2774                        "name": "undeclared"
2775                      }
2776                    },
2777                  },
2778                  "entityTypes": {},
2779                  "actions": {}
2780                }
2781              }
2782        );
2783        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2784        assert_matches!(schema, Err(e) => {
2785            expect_err(
2786                &src,
2787                &miette::Report::new(e),
2788                &ExpectedErrorMessageBuilder::error(r#"failed to resolve type: undeclared"#)
2789                    .help("`undeclared` has not been declared as an entity type")
2790                    .build());
2791        });
2792    }
2793
2794    #[test]
2795    fn unknown_extension_type() {
2796        let src: serde_json::Value = json!({
2797            "": {
2798                "commonTypes": { },
2799                "entityTypes": {
2800                    "User": {
2801                        "shape": {
2802                            "type": "Record",
2803                            "attributes": {
2804                                "a": {
2805                                    "type": "Extension",
2806                                    "name": "ip",
2807                                }
2808                            }
2809                        }
2810                    }
2811                },
2812                "actions": {}
2813            }
2814        });
2815        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2816        assert_matches!(schema, Err(e) => {
2817            expect_err(
2818                &src,
2819                &miette::Report::new(e),
2820                &ExpectedErrorMessageBuilder::error("unknown extension type `ip`")
2821                    .help("did you mean `ipaddr`?")
2822                    .build());
2823        });
2824
2825        let src: serde_json::Value = json!({
2826            "": {
2827                "commonTypes": { },
2828                "entityTypes": {
2829                    "User": {},
2830                    "Folder" :{}
2831                },
2832                "actions": {
2833                    "A": {
2834                        "appliesTo": {
2835                            "principalTypes" : ["User"],
2836                            "resourceTypes" : ["Folder"],
2837                            "context": {
2838                                "type": "Record",
2839                                "attributes": {
2840                                    "a": {
2841                                        "type": "Extension",
2842                                        "name": "deciml",
2843                                    }
2844                                }
2845                            }
2846                        }
2847                    }
2848                }
2849            }
2850        });
2851        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2852        assert_matches!(schema, Err(e) => {
2853            expect_err(
2854                &src,
2855                &miette::Report::new(e),
2856                &ExpectedErrorMessageBuilder::error("unknown extension type `deciml`")
2857                    .help("did you mean `decimal`?")
2858                    .build());
2859        });
2860
2861        let src: serde_json::Value = json!({
2862            "": {
2863                "commonTypes": {
2864                    "ty": {
2865                        "type": "Record",
2866                        "attributes": {
2867                            "a": {
2868                                "type": "Extension",
2869                                "name": "i",
2870                            }
2871                        }
2872                    }
2873                },
2874                "entityTypes": { },
2875                "actions": { },
2876            }
2877        });
2878        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2879        assert_matches!(schema, Err(e) => {
2880            expect_err(
2881                &src,
2882                &miette::Report::new(e),
2883                &ExpectedErrorMessageBuilder::error("unknown extension type `i`")
2884                    .help("did you mean `ipaddr`?")
2885                    .build());
2886        });
2887
2888        let src: serde_json::Value = json!({
2889            "": {
2890                "commonTypes": {
2891                    "ty": {
2892                        "type": "Record",
2893                        "attributes": {
2894                            "a": {
2895                                "type": "Extension",
2896                                "name": "partial_evaluation",
2897                            }
2898                        }
2899                    }
2900                },
2901                "entityTypes": { },
2902                "actions": { },
2903            }
2904        });
2905        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
2906        assert_matches!(schema, Err(e) => {
2907            expect_err(
2908                &src,
2909                &miette::Report::new(e),
2910                &ExpectedErrorMessageBuilder::error("unknown extension type `partial_evaluation`")
2911                    .help("did you mean `decimal`?")
2912                    .build());
2913        });
2914    }
2915
2916    #[track_caller]
2917    fn assert_invalid_json_schema(src: serde_json::Value) {
2918        let schema = ValidatorSchema::from_json_value(src, Extensions::all_available());
2919        assert_matches!(schema, Err(SchemaError::JsonDeserialization(e)) if e.to_smolstr().contains("Used reserved schema keyword"));
2920    }
2921
2922    // Names like `Set`, `Record`, `Entity`, and Extension` are not allowed as common type names, as specified in #1070 and #1139.
2923    #[test]
2924    fn test_common_type_name_conflicts() {
2925        // `Record` cannot be a common type name
2926        let src: serde_json::Value = json!({
2927            "": {
2928                "commonTypes": {
2929                    "Record": {
2930                        "type": "Record",
2931                        "attributes": {
2932                            "a": {
2933                                "type": "Long",
2934                            }
2935                        }
2936                    }
2937                },
2938                "entityTypes": {
2939                    "b": {
2940                        "shape" : {
2941                            "type" : "Record",
2942                            "attributes" : {
2943                                "c" : {
2944                                    "type" : "Record"
2945                                }
2946                        }
2947                    }
2948                }
2949                },
2950                "actions": { },
2951            }
2952        });
2953        assert_invalid_json_schema(src);
2954
2955        let src: serde_json::Value = json!({
2956            "NS": {
2957                "commonTypes": {
2958                    "Record": {
2959                        "type": "Record",
2960                        "attributes": {
2961                            "a": {
2962                                "type": "Long",
2963                            }
2964                        }
2965                    }
2966                },
2967                "entityTypes": {
2968                    "b": {
2969                        "shape" : {
2970                            "type" : "Record",
2971                            "attributes" : {
2972                                "c" : {
2973                                    "type" : "Record"
2974                                }
2975                        }
2976                    }
2977                }
2978                },
2979                "actions": { },
2980            }
2981        });
2982        assert_invalid_json_schema(src);
2983
2984        // `Extension` cannot be a common type name
2985        let src: serde_json::Value = json!({
2986            "": {
2987                "commonTypes": {
2988                    "Extension": {
2989                        "type": "Record",
2990                        "attributes": {
2991                            "a": {
2992                                "type": "Long",
2993                            }
2994                        }
2995                    }
2996                },
2997                "entityTypes": {
2998                    "b": {
2999                        "shape" : {
3000                            "type" : "Record",
3001                            "attributes" : {
3002                                "c" : {
3003                                    "type" : "Extension"
3004                                }
3005                        }
3006                    }
3007                }
3008                },
3009                "actions": { },
3010            }
3011        });
3012        assert_invalid_json_schema(src);
3013
3014        let src: serde_json::Value = json!({
3015            "NS": {
3016                "commonTypes": {
3017                    "Extension": {
3018                        "type": "Record",
3019                        "attributes": {
3020                            "a": {
3021                                "type": "Long",
3022                            }
3023                        }
3024                    }
3025                },
3026                "entityTypes": {
3027                    "b": {
3028                        "shape" : {
3029                            "type" : "Record",
3030                            "attributes" : {
3031                                "c" : {
3032                                    "type" : "Extension"
3033                                }
3034                        }
3035                    }
3036                }
3037                },
3038                "actions": { },
3039            }
3040        });
3041        assert_invalid_json_schema(src);
3042
3043        // `Entity` cannot be a common type name
3044        let src: serde_json::Value = json!({
3045            "": {
3046                "commonTypes": {
3047                    "Entity": {
3048                        "type": "Record",
3049                        "attributes": {
3050                            "a": {
3051                                "type": "Long",
3052                            }
3053                        }
3054                    }
3055                },
3056                "entityTypes": {
3057                    "b": {
3058                        "shape" : {
3059                            "type" : "Record",
3060                            "attributes" : {
3061                                "c" : {
3062                                    "type" : "Entity"
3063                                }
3064                        }
3065                    }
3066                }
3067                },
3068                "actions": { },
3069            }
3070        });
3071        assert_invalid_json_schema(src);
3072
3073        let src: serde_json::Value = json!({
3074            "NS": {
3075                "commonTypes": {
3076                    "Entity": {
3077                        "type": "Record",
3078                        "attributes": {
3079                            "a": {
3080                                "type": "Long",
3081                            }
3082                        }
3083                    }
3084                },
3085                "entityTypes": {
3086                    "b": {
3087                        "shape" : {
3088                            "type" : "Record",
3089                            "attributes" : {
3090                                "c" : {
3091                                    "type" : "Entity"
3092                                }
3093                        }
3094                    }
3095                }
3096                },
3097                "actions": { },
3098            }
3099        });
3100        assert_invalid_json_schema(src);
3101
3102        // `Set` cannot be a common type name
3103        let src: serde_json::Value = json!({
3104            "": {
3105                "commonTypes": {
3106                    "Set": {
3107                        "type": "Record",
3108                        "attributes": {
3109                            "a": {
3110                                "type": "Long",
3111                            }
3112                        }
3113                    }
3114                },
3115                "entityTypes": {
3116                    "b": {
3117                        "shape" : {
3118                            "type" : "Record",
3119                            "attributes" : {
3120                                "c" : {
3121                                    "type" : "Set"
3122                                }
3123                        }
3124                    }
3125                }
3126                },
3127                "actions": { },
3128            }
3129        });
3130        assert_invalid_json_schema(src);
3131
3132        let src: serde_json::Value = json!({
3133            "NS": {
3134                "commonTypes": {
3135                    "Set": {
3136                        "type": "Record",
3137                        "attributes": {
3138                            "a": {
3139                                "type": "Long",
3140                            }
3141                        }
3142                    }
3143                },
3144                "entityTypes": {
3145                    "b": {
3146                        "shape" : {
3147                            "type" : "Record",
3148                            "attributes" : {
3149                                "c" : {
3150                                    "type" : "Set"
3151                                }
3152                        }
3153                    }
3154                }
3155                },
3156                "actions": { },
3157            }
3158        });
3159        assert_invalid_json_schema(src);
3160
3161        // `Long` cannot be a common type name
3162        let src: serde_json::Value = json!({
3163            "": {
3164                "commonTypes": {
3165                    "Long": {
3166                        "type": "Record",
3167                        "attributes": {
3168                            "a": {
3169                                "type": "Long",
3170                            }
3171                        }
3172                    }
3173                },
3174                "entityTypes": {
3175                    "b": {
3176                        "shape" : {
3177                            "type" : "Record",
3178                            "attributes" : {
3179                                "c" : {
3180                                    "type" : "Long"
3181                                }
3182                        }
3183                    }
3184                }
3185                },
3186                "actions": { },
3187            }
3188        });
3189        assert_invalid_json_schema(src);
3190
3191        let src: serde_json::Value = json!({
3192            "NS": {
3193                "commonTypes": {
3194                    "Long": {
3195                        "type": "Record",
3196                        "attributes": {
3197                            "a": {
3198                                "type": "Long",
3199                            }
3200                        }
3201                    }
3202                },
3203                "entityTypes": {
3204                    "b": {
3205                        "shape" : {
3206                            "type" : "Record",
3207                            "attributes" : {
3208                                "c" : {
3209                                    "type" : "Long"
3210                                }
3211                        }
3212                    }
3213                }
3214                },
3215                "actions": { },
3216            }
3217        });
3218        assert_invalid_json_schema(src);
3219
3220        // `Boolean` cannot be a common type name
3221        let src: serde_json::Value = json!({
3222            "": {
3223                "commonTypes": {
3224                    "Boolean": {
3225                        "type": "Record",
3226                        "attributes": {
3227                            "a": {
3228                                "type": "Long",
3229                            }
3230                        }
3231                    }
3232                },
3233                "entityTypes": {
3234                    "b": {
3235                        "shape" : {
3236                            "type" : "Record",
3237                            "attributes" : {
3238                                "c" : {
3239                                    "type" : "Boolean"
3240                                }
3241                        }
3242                    }
3243                }
3244                },
3245                "actions": { },
3246            }
3247        });
3248        assert_invalid_json_schema(src);
3249
3250        let src: serde_json::Value = json!({
3251            "NS": {
3252                "commonTypes": {
3253                    "Boolean": {
3254                        "type": "Record",
3255                        "attributes": {
3256                            "a": {
3257                                "type": "Long",
3258                            }
3259                        }
3260                    }
3261                },
3262                "entityTypes": {
3263                    "b": {
3264                        "shape" : {
3265                            "type" : "Record",
3266                            "attributes" : {
3267                                "c" : {
3268                                    "type" : "Boolean"
3269                                }
3270                        }
3271                    }
3272                }
3273                },
3274                "actions": { },
3275            }
3276        });
3277        assert_invalid_json_schema(src);
3278
3279        // `String` cannot be a common type name
3280        let src: serde_json::Value = json!({
3281            "": {
3282                "commonTypes": {
3283                    "String": {
3284                        "type": "Record",
3285                        "attributes": {
3286                            "a": {
3287                                "type": "Long",
3288                            }
3289                        }
3290                    }
3291                },
3292                "entityTypes": {
3293                    "b": {
3294                        "shape" : {
3295                            "type" : "Record",
3296                            "attributes" : {
3297                                "c" : {
3298                                    "type" : "String"
3299                                }
3300                        }
3301                    }
3302                }
3303                },
3304                "actions": { },
3305            }
3306        });
3307        assert_invalid_json_schema(src);
3308
3309        let src: serde_json::Value = json!({
3310            "NS": {
3311                "commonTypes": {
3312                    "String": {
3313                        "type": "Record",
3314                        "attributes": {
3315                            "a": {
3316                                "type": "Long",
3317                            }
3318                        }
3319                    }
3320                },
3321                "entityTypes": {
3322                    "b": {
3323                        "shape" : {
3324                            "type" : "Record",
3325                            "attributes" : {
3326                                "c" : {
3327                                    "type" : "String"
3328                                }
3329                        }
3330                    }
3331                }
3332                },
3333                "actions": { },
3334            }
3335        });
3336        assert_invalid_json_schema(src);
3337
3338        // Cedar examines common type name declarations eagerly.
3339        // So it throws an error for the following example even though `Record`
3340        // is not referenced.
3341        let src: serde_json::Value = json!({
3342            "": {
3343                "commonTypes": {
3344                    "Record": {
3345                        "type": "Set",
3346                        "element": {
3347                            "type": "Long"
3348                        }
3349                    }
3350                },
3351                "entityTypes": {
3352                    "b": {
3353                        "shape" :
3354                        {
3355                            "type": "Record",
3356                            "attributes" : {
3357                                "c" : {
3358                                    "type" : "String"
3359                                }
3360                            }
3361                        }
3362                    }
3363                },
3364                "actions": { },
3365            }
3366        });
3367        assert_invalid_json_schema(src);
3368    }
3369
3370    #[test]
3371    fn reserved_namespace() {
3372        let src: serde_json::Value = json!({
3373            "__cedar": {
3374                "commonTypes": { },
3375                "entityTypes": { },
3376                "actions": { },
3377            }
3378        });
3379        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3380        assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3381
3382        let src: serde_json::Value = json!({
3383            "__cedar::A": {
3384                "commonTypes": { },
3385                "entityTypes": { },
3386                "actions": { },
3387            }
3388        });
3389        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3390        assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3391
3392        let src: serde_json::Value = json!({
3393            "": {
3394                "commonTypes": {
3395                    "__cedar": {
3396                        "type": "String",
3397                    }
3398                },
3399                "entityTypes": { },
3400                "actions": { },
3401            }
3402        });
3403        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3404        assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3405
3406        let src: serde_json::Value = json!({
3407            "A": {
3408                "commonTypes": {
3409                    "__cedar": {
3410                        "type": "String",
3411                    }
3412                },
3413                "entityTypes": { },
3414                "actions": { },
3415            }
3416        });
3417        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3418        assert_matches!(schema, Err(SchemaError::JsonDeserialization(_)));
3419
3420        let src: serde_json::Value = json!({
3421            "": {
3422                "commonTypes": {
3423                    "A": {
3424                        "type": "__cedar",
3425                    }
3426                },
3427                "entityTypes": { },
3428                "actions": { },
3429            }
3430        });
3431        let schema = ValidatorSchema::from_json_value(src.clone(), Extensions::all_available());
3432        assert_matches!(schema, Err(e) => {
3433            expect_err(
3434                &src,
3435                &miette::Report::new(e),
3436                &ExpectedErrorMessageBuilder::error("failed to resolve type: __cedar")
3437                    .help("`__cedar` has not been declared as a common type")
3438                    .build(),
3439            );
3440        });
3441    }
3442}
3443
3444#[cfg(test)]
3445mod test_579; // located in separate file test_579.rs
3446
3447#[cfg(test)]
3448mod test_rfc70 {
3449    use super::{test::collect_warnings, CedarSchemaError};
3450    use super::{SchemaError, ValidatorSchema};
3451    use crate::types::Type;
3452    use cedar_policy_core::{
3453        extensions::Extensions,
3454        test_utils::{expect_err, ExpectedErrorMessageBuilder},
3455    };
3456    use cool_asserts::assert_matches;
3457    use serde_json::json;
3458
3459    #[track_caller]
3460    fn assert_valid_cedar_schema(src: &str) -> ValidatorSchema {
3461        match ValidatorSchema::from_cedarschema_str(src, Extensions::all_available()) {
3462            Ok((schema, _)) => schema,
3463            Err(e) => panic!("{:?}", miette::Report::new(e)),
3464        }
3465    }
3466
3467    #[track_caller]
3468    fn assert_invalid_cedar_schema(src: &str) {
3469        match ValidatorSchema::from_cedarschema_str(src, Extensions::all_available()) {
3470            Ok(_) => panic!("{src} should be an invalid schema"),
3471            Err(CedarSchemaError::Parsing(_)) => {}
3472            Err(e) => panic!("unexpected error: {:?}", miette::Report::new(e)),
3473        }
3474    }
3475
3476    #[track_caller]
3477    fn assert_valid_json_schema(json: serde_json::Value) -> ValidatorSchema {
3478        match ValidatorSchema::from_json_value(json, Extensions::all_available()) {
3479            Ok(schema) => schema,
3480            Err(e) => panic!("{:?}", miette::Report::new(e)),
3481        }
3482    }
3483
3484    #[track_caller]
3485    fn assert_invalid_json_schema(json: serde_json::Value) {
3486        match ValidatorSchema::from_json_value(json.clone(), Extensions::all_available()) {
3487            Ok(_) => panic!("{json} should be an invalid schema"),
3488            Err(SchemaError::JsonDeserialization(_)) => {}
3489            Err(e) => panic!("unexpected error: {:?}", miette::Report::new(e)),
3490        }
3491    }
3492
3493    /// Common type shadowing a common type is disallowed in both syntaxes
3494    #[test]
3495    fn common_common_conflict() {
3496        let src = "
3497            type T = String;
3498            namespace NS {
3499                type T = String;
3500                entity User { t: T };
3501            }
3502        ";
3503        assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3504            expect_err(
3505                src,
3506                &miette::Report::new(e),
3507                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3508                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3509                    .build(),
3510            );
3511        });
3512
3513        let src_json = json!({
3514            "": {
3515                "commonTypes": {
3516                    "T": { "type": "String" },
3517                },
3518                "entityTypes": {},
3519                "actions": {},
3520            },
3521            "NS": {
3522                "commonTypes": {
3523                    "T": { "type": "String" },
3524                },
3525                "entityTypes": {
3526                    "User": {
3527                        "shape": {
3528                            "type": "Record",
3529                            "attributes": {
3530                                "t": { "type": "T" },
3531                            },
3532                        }
3533                    }
3534                },
3535                "actions": {},
3536            }
3537        });
3538        assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3539            expect_err(
3540                &src_json,
3541                &miette::Report::new(e),
3542                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3543                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3544                    .build(),
3545            );
3546        });
3547    }
3548
3549    /// Entity type shadowing an entity type is disallowed in both syntaxes
3550    #[test]
3551    fn entity_entity_conflict() {
3552        let src = "
3553            entity T in T { foo: String };
3554            namespace NS {
3555                entity T { bar: String };
3556                entity User { t: T };
3557            }
3558        ";
3559        assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3560            expect_err(
3561                src,
3562                &miette::Report::new(e),
3563                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3564                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3565                    .build(),
3566            );
3567        });
3568
3569        // still disallowed even if there are no ambiguous references to `T`
3570        let src = "
3571            entity T { foo: String };
3572            namespace NS {
3573                entity T { bar: String };
3574            }
3575        ";
3576        assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3577            expect_err(
3578                src,
3579                &miette::Report::new(e),
3580                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3581                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3582                    .build(),
3583            );
3584        });
3585
3586        let src_json = json!({
3587            "": {
3588                "entityTypes": {
3589                    "T": {
3590                        "memberOfTypes": ["T"],
3591                        "shape": {
3592                            "type": "Record",
3593                            "attributes": {
3594                                "foo": { "type": "String" },
3595                            },
3596                        }
3597                    }
3598                },
3599                "actions": {},
3600            },
3601            "NS": {
3602                "entityTypes": {
3603                    "T": {
3604                        "shape": {
3605                            "type": "Record",
3606                            "attributes": {
3607                                "bar": { "type": "String" },
3608                            },
3609                        }
3610                    },
3611                    "User": {
3612                        "shape": {
3613                            "type": "Record",
3614                            "attributes": {
3615                                "t": { "type": "Entity", "name": "T" },
3616                            },
3617                        }
3618                    },
3619                },
3620                "actions": {},
3621            }
3622        });
3623        assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3624            expect_err(
3625                &src_json,
3626                &miette::Report::new(e),
3627                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3628                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3629                    .build(),
3630            );
3631        });
3632    }
3633
3634    /// Common type shadowing an entity type is disallowed in both syntaxes,
3635    /// even though it would be unambiguous in the JSON syntax
3636    #[test]
3637    fn common_entity_conflict() {
3638        let src = "
3639            entity T in T { foo: String };
3640            namespace NS {
3641                type T = String;
3642                entity User { t: T };
3643            }
3644        ";
3645        assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3646            expect_err(
3647                src,
3648                &miette::Report::new(e),
3649                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3650                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3651                    .build(),
3652            );
3653        });
3654
3655        let src_json = json!({
3656            "": {
3657                "entityTypes": {
3658                    "T": {
3659                        "memberOfTypes": ["T"],
3660                        "shape": {
3661                            "type": "Record",
3662                            "attributes": {
3663                                "foo": { "type": "String" },
3664                            },
3665                        }
3666                    }
3667                },
3668                "actions": {},
3669            },
3670            "NS": {
3671                "commonTypes": {
3672                    "T": { "type": "String" },
3673                },
3674                "entityTypes": {
3675                    "User": {
3676                        "shape": {
3677                            "type": "Record",
3678                            "attributes": {
3679                                "t": { "type": "T" },
3680                            }
3681                        }
3682                    }
3683                },
3684                "actions": {},
3685            }
3686        });
3687        assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3688            expect_err(
3689                &src_json,
3690                &miette::Report::new(e),
3691                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3692                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3693                    .build(),
3694            );
3695        });
3696    }
3697
3698    /// Entity type shadowing a common type is disallowed in both syntaxes, even
3699    /// though it would be unambiguous in the JSON syntax
3700    #[test]
3701    fn entity_common_conflict() {
3702        let src = "
3703            type T = String;
3704            namespace NS {
3705                entity T in T { foo: String };
3706                entity User { t: T };
3707            }
3708        ";
3709        assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3710            expect_err(
3711                src,
3712                &miette::Report::new(e),
3713                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3714                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3715                    .build(),
3716            );
3717        });
3718
3719        let src_json = json!({
3720            "": {
3721                "commonTypes": {
3722                    "T": { "type": "String" },
3723                },
3724                "entityTypes": {},
3725                "actions": {},
3726            },
3727            "NS": {
3728                "entityTypes": {
3729                    "T": {
3730                        "memberOfTypes": ["T"],
3731                        "shape": {
3732                            "type": "Record",
3733                            "attributes": {
3734                                "foo": { "type": "String" },
3735                            },
3736                        }
3737                    },
3738                    "User": {
3739                        "shape": {
3740                            "type": "Record",
3741                            "attributes": {
3742                                "t": { "type": "T" },
3743                            }
3744                        }
3745                    }
3746                },
3747                "actions": {},
3748            }
3749        });
3750        assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3751            expect_err(
3752                &src_json,
3753                &miette::Report::new(e),
3754                &ExpectedErrorMessageBuilder::error("definition of `NS::T` illegally shadows the existing definition of `T`")
3755                    .help("try renaming one of the definitions, or moving `T` to a different namespace")
3756                    .build(),
3757            );
3758        });
3759    }
3760
3761    /// Action shadowing an action is disallowed in both syntaxes
3762    #[test]
3763    fn action_action_conflict() {
3764        let src = "
3765            action A;
3766            namespace NS {
3767                action A;
3768            }
3769        ";
3770        assert_matches!(collect_warnings(ValidatorSchema::from_cedarschema_str(src, Extensions::all_available())), Err(e) => {
3771            expect_err(
3772                src,
3773                &miette::Report::new(e),
3774                &ExpectedErrorMessageBuilder::error("definition of `NS::Action::\"A\"` illegally shadows the existing definition of `Action::\"A\"`")
3775                    .help("try renaming one of the actions, or moving `Action::\"A\"` to a different namespace")
3776                    .build(),
3777            );
3778        });
3779
3780        let src_json = json!({
3781            "": {
3782                "entityTypes": {},
3783                "actions": {
3784                    "A": {},
3785                },
3786            },
3787            "NS": {
3788                "entityTypes": {},
3789                "actions": {
3790                    "A": {},
3791                },
3792            }
3793        });
3794        assert_matches!(ValidatorSchema::from_json_value(src_json.clone(), Extensions::all_available()), Err(e) => {
3795            expect_err(
3796                &src_json,
3797                &miette::Report::new(e),
3798                &ExpectedErrorMessageBuilder::error("definition of `NS::Action::\"A\"` illegally shadows the existing definition of `Action::\"A\"`")
3799                    .help("try renaming one of the actions, or moving `Action::\"A\"` to a different namespace")
3800                    .build(),
3801            );
3802        });
3803    }
3804
3805    /// Action with same name as a common type is allowed
3806    #[test]
3807    fn action_common_conflict() {
3808        let src = "
3809            action A;
3810            action B; // same name as a common type in same (empty) namespace
3811            action C; // same name as a common type in different (nonempty) namespace
3812            type B = String;
3813            type E = String;
3814            namespace NS1 {
3815                type C = String;
3816                entity User { b: B, c: C, e: E };
3817            }
3818            namespace NS2 {
3819                type D = String;
3820                action D; // same name as a common type in same (nonempty) namespace
3821                action E; // same name as a common type in different (empty) namespace
3822                entity User { b: B, d: D, e: E };
3823            }
3824        ";
3825        assert_valid_cedar_schema(src);
3826
3827        let src_json = json!({
3828            "": {
3829                "commonTypes": {
3830                    "B": { "type": "String" },
3831                    "E": { "type": "String" },
3832                },
3833                "entityTypes": {},
3834                "actions": {
3835                    "A": {},
3836                    "B": {},
3837                    "C": {},
3838                },
3839            },
3840            "NS1": {
3841                "commonTypes": {
3842                    "C": { "type": "String" },
3843                },
3844                "entityTypes": {
3845                    "User": {
3846                        "shape": {
3847                            "type": "Record",
3848                            "attributes": {
3849                                "b": { "type": "B" },
3850                                "c": { "type": "C" },
3851                                "e": { "type": "E" },
3852                            }
3853                        }
3854                    },
3855                },
3856                "actions": {}
3857            },
3858            "NS2": {
3859                "commonTypes": {
3860                    "D": { "type": "String" },
3861                },
3862                "entityTypes": {
3863                    "User": {
3864                        "shape": {
3865                            "type": "Record",
3866                            "attributes": {
3867                                "b": { "type": "B" },
3868                                "d": { "type": "D" },
3869                                "e": { "type": "E" },
3870                            }
3871                        }
3872                    }
3873                },
3874                "actions": {
3875                    "D": {},
3876                    "E": {},
3877                }
3878            }
3879        });
3880        assert_valid_json_schema(src_json);
3881    }
3882
3883    /// Action with same name as an entity type is allowed
3884    #[test]
3885    fn action_entity_conflict() {
3886        let src = "
3887            action A;
3888            action B; // same name as an entity type in same (empty) namespace
3889            action C; // same name as an entity type in different (nonempty) namespace
3890            entity B;
3891            entity E;
3892            namespace NS1 {
3893                entity C;
3894                entity User { b: B, c: C, e: E };
3895            }
3896            namespace NS2 {
3897                entity D;
3898                action D; // same name as an entity type in same (nonempty) namespace
3899                action E; // same name as an entity type in different (empty) namespace
3900                entity User { b: B, d: D, e: E };
3901            }
3902        ";
3903        assert_valid_cedar_schema(src);
3904
3905        let src_json = json!({
3906            "": {
3907                "entityTypes": {
3908                    "B": {},
3909                    "E": {},
3910                },
3911                "actions": {
3912                    "A": {},
3913                    "B": {},
3914                    "C": {},
3915                },
3916            },
3917            "NS1": {
3918                "entityTypes": {
3919                    "C": {},
3920                    "User": {
3921                        "shape": {
3922                            "type": "Record",
3923                            "attributes": {
3924                                "b": { "type": "Entity", "name": "B" },
3925                                "c": { "type": "Entity", "name": "C" },
3926                                "e": { "type": "Entity", "name": "E" },
3927                            }
3928                        }
3929                    },
3930                },
3931                "actions": {}
3932            },
3933            "NS2": {
3934                "entityTypes": {
3935                    "D": {},
3936                    "User": {
3937                        "shape": {
3938                            "type": "Record",
3939                            "attributes": {
3940                                "b": { "type": "Entity", "name": "B" },
3941                                "d": { "type": "Entity", "name": "D" },
3942                                "e": { "type": "Entity", "name": "E" },
3943                            }
3944                        }
3945                    }
3946                },
3947                "actions": {
3948                    "D": {},
3949                    "E": {},
3950                }
3951            }
3952        });
3953        assert_valid_json_schema(src_json);
3954    }
3955
3956    /// Common type shadowing an entity type in the same namespace is allowed.
3957    /// In the JSON syntax, but not the Cedar syntax, you can even define
3958    /// `entity T; type T = T;`. (In the Cedar syntax, there's no way to specify
3959    /// that the RHS `T` should refer to the entity type, but in the JSON syntax
3960    /// there is.)
3961    #[test]
3962    fn common_shadowing_entity_same_namespace() {
3963        let src = "
3964            entity T;
3965            type T = Bool; // works in the empty namespace
3966            namespace NS {
3967                entity E;
3968                type E = Bool; // works in a nonempty namespace
3969            }
3970        ";
3971        assert_valid_cedar_schema(src);
3972
3973        let src_json = json!({
3974            "": {
3975                "commonTypes": {
3976                    "T": { "type": "Entity", "name": "T" },
3977                },
3978                "entityTypes": {
3979                    "T": {},
3980                },
3981                "actions": {}
3982            },
3983            "NS1": {
3984                "commonTypes": {
3985                    "E": { "type": "Entity", "name": "E" },
3986                },
3987                "entityTypes": {
3988                    "E": {},
3989                },
3990                "actions": {}
3991            },
3992            "NS2": {
3993                "commonTypes": {
3994                    "E": { "type": "String" },
3995                },
3996                "entityTypes": {
3997                    "E": {},
3998                },
3999                "actions": {}
4000            }
4001        });
4002        assert_valid_json_schema(src_json);
4003    }
4004
4005    /// Common type shadowing a JSON schema primitive type is disallowed per #1139;
4006    /// you can still refer to the primitive type using __cedar
4007    #[test]
4008    fn common_shadowing_primitive() {
4009        let src = "
4010            type String = Long;
4011            entity E {
4012                a: String,
4013                b: __cedar::String,
4014                c: Long,
4015                d: __cedar::Long,
4016            };
4017            namespace NS {
4018                type Bool = Long;
4019                entity F {
4020                    a: Bool,
4021                    b: __cedar::Bool,
4022                    c: Long,
4023                    d: __cedar::Long,
4024                };
4025            }
4026        ";
4027        assert_invalid_cedar_schema(src);
4028        let src = "
4029            type _String = Long;
4030            entity E {
4031                a: _String,
4032                b: __cedar::String,
4033                c: Long,
4034                d: __cedar::Long,
4035            };
4036            namespace NS {
4037                type _Bool = Long;
4038                entity F {
4039                    a: _Bool,
4040                    b: __cedar::Bool,
4041                    c: Long,
4042                    d: __cedar::Long,
4043                };
4044            }
4045        ";
4046        let schema = assert_valid_cedar_schema(src);
4047        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4048        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4049            assert_eq!(&atype.attr_type, &Type::primitive_long()); // using the common type definition
4050        });
4051        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4052            assert_eq!(&atype.attr_type, &Type::primitive_string());
4053        });
4054        assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4055            assert_eq!(&atype.attr_type, &Type::primitive_long());
4056        });
4057        assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4058            assert_eq!(&atype.attr_type, &Type::primitive_long());
4059        });
4060        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4061        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4062            assert_eq!(&atype.attr_type, &Type::primitive_long()); // using the common type definition
4063        });
4064        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4065            assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4066        });
4067        assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4068            assert_eq!(&atype.attr_type, &Type::primitive_long());
4069        });
4070        assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4071            assert_eq!(&atype.attr_type, &Type::primitive_long());
4072        });
4073
4074        let src_json = json!({
4075            "": {
4076                "commonTypes": {
4077                    "String": { "type": "Long" },
4078                },
4079                "entityTypes": {
4080                    "E": {
4081                        "shape": {
4082                            "type": "Record",
4083                            "attributes": {
4084                                "a": { "type": "String" },
4085                                "b": { "type": "__cedar::String" },
4086                                "c": { "type": "Long" },
4087                                "d": { "type": "__cedar::Long" },
4088                            }
4089                        }
4090                    },
4091                },
4092                "actions": {}
4093            },
4094            "NS": {
4095                "commonTypes": {
4096                    "Bool": { "type": "Long" },
4097                },
4098                "entityTypes": {
4099                    "F": {
4100                        "shape": {
4101                            "type": "Record",
4102                            "attributes": {
4103                                "a": { "type": "Bool" },
4104                                "b": { "type": "__cedar::Bool" },
4105                                "c": { "type": "Long" },
4106                                "d": { "type": "__cedar::Long" },
4107                            }
4108                        }
4109                    },
4110                },
4111                "actions": {}
4112            }
4113        });
4114        assert_invalid_json_schema(src_json);
4115        let src_json = json!({
4116            "": {
4117                "commonTypes": {
4118                    "_String": { "type": "Long" },
4119                },
4120                "entityTypes": {
4121                    "E": {
4122                        "shape": {
4123                            "type": "Record",
4124                            "attributes": {
4125                                "a": { "type": "_String" },
4126                                "b": { "type": "__cedar::String" },
4127                                "c": { "type": "Long" },
4128                                "d": { "type": "__cedar::Long" },
4129                            }
4130                        }
4131                    },
4132                },
4133                "actions": {}
4134            },
4135            "NS": {
4136                "commonTypes": {
4137                    "_Bool": { "type": "Long" },
4138                },
4139                "entityTypes": {
4140                    "F": {
4141                        "shape": {
4142                            "type": "Record",
4143                            "attributes": {
4144                                "a": { "type": "_Bool" },
4145                                "b": { "type": "__cedar::Bool" },
4146                                "c": { "type": "Long" },
4147                                "d": { "type": "__cedar::Long" },
4148                            }
4149                        }
4150                    },
4151                },
4152                "actions": {}
4153            }
4154        });
4155        let schema = assert_valid_json_schema(src_json);
4156        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4157        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4158            assert_eq!(&atype.attr_type, &Type::primitive_long());
4159        });
4160        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4161            assert_eq!(&atype.attr_type, &Type::primitive_string());
4162        });
4163        assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4164            assert_eq!(&atype.attr_type, &Type::primitive_long());
4165        });
4166        assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4167            assert_eq!(&atype.attr_type, &Type::primitive_long());
4168        });
4169        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4170        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4171            assert_eq!(&atype.attr_type, &Type::primitive_long()); // using the common type definition
4172        });
4173        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4174            assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4175        });
4176        assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4177            assert_eq!(&atype.attr_type, &Type::primitive_long());
4178        });
4179        assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4180            assert_eq!(&atype.attr_type, &Type::primitive_long());
4181        });
4182    }
4183
4184    /// Common type shadowing an extension type is allowed;
4185    /// you can still refer to the extension type using __cedar
4186    #[test]
4187    fn common_shadowing_extension() {
4188        let src = "
4189            type ipaddr = Long;
4190            entity E {
4191                a: ipaddr,
4192                b: __cedar::ipaddr,
4193                c: Long,
4194                d: __cedar::Long,
4195            };
4196            namespace NS {
4197                type decimal = Long;
4198                entity F {
4199                    a: decimal,
4200                    b: __cedar::decimal,
4201                    c: Long,
4202                    d: __cedar::Long,
4203                };
4204            }
4205        ";
4206        let schema = assert_valid_cedar_schema(src);
4207        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4208        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4209            assert_eq!(&atype.attr_type, &Type::primitive_long()); // using the common type definition
4210        });
4211        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4212            assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4213        });
4214        assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4215            assert_eq!(&atype.attr_type, &Type::primitive_long());
4216        });
4217        assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4218            assert_eq!(&atype.attr_type, &Type::primitive_long());
4219        });
4220        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4221        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4222            assert_eq!(&atype.attr_type, &Type::primitive_long()); // using the common type definition
4223        });
4224        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4225            assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4226        });
4227        assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4228            assert_eq!(&atype.attr_type, &Type::primitive_long());
4229        });
4230        assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4231            assert_eq!(&atype.attr_type, &Type::primitive_long());
4232        });
4233
4234        let src_json = json!({
4235            "": {
4236                "commonTypes": {
4237                    "ipaddr": { "type": "Long" },
4238                },
4239                "entityTypes": {
4240                    "E": {
4241                        "shape": {
4242                            "type": "Record",
4243                            "attributes": {
4244                                "a": { "type": "ipaddr" },
4245                                "b": { "type": "__cedar::ipaddr" },
4246                                "c": { "type": "Long" },
4247                                "d": { "type": "__cedar::Long" },
4248                            }
4249                        }
4250                    },
4251                },
4252                "actions": {}
4253            },
4254            "NS": {
4255                "commonTypes": {
4256                    "decimal": { "type": "Long" },
4257                },
4258                "entityTypes": {
4259                    "F": {
4260                        "shape": {
4261                            "type": "Record",
4262                            "attributes": {
4263                                "a": { "type": "decimal" },
4264                                "b": { "type": "__cedar::decimal" },
4265                                "c": { "type": "Long" },
4266                                "d": { "type": "__cedar::Long" },
4267                            }
4268                        }
4269                    },
4270                },
4271                "actions": {}
4272            }
4273        });
4274        let schema = assert_valid_json_schema(src_json);
4275        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4276        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4277            assert_eq!(&atype.attr_type, &Type::primitive_long()); // using the common type definition
4278        });
4279        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4280            assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4281        });
4282        assert_matches!(e.attributes.get_attr("c"), Some(atype) => {
4283            assert_eq!(&atype.attr_type, &Type::primitive_long());
4284        });
4285        assert_matches!(e.attributes.get_attr("d"), Some(atype) => {
4286            assert_eq!(&atype.attr_type, &Type::primitive_long());
4287        });
4288        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4289        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4290            assert_eq!(&atype.attr_type, &Type::primitive_long()); // using the common type definition
4291        });
4292        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4293            assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4294        });
4295        assert_matches!(f.attributes.get_attr("c"), Some(atype) => {
4296            assert_eq!(&atype.attr_type, &Type::primitive_long());
4297        });
4298        assert_matches!(f.attributes.get_attr("d"), Some(atype) => {
4299            assert_eq!(&atype.attr_type, &Type::primitive_long());
4300        });
4301    }
4302
4303    /// Entity type shadowing a primitive type is allowed;
4304    /// you can still refer to the primitive type using __cedar
4305    #[test]
4306    fn entity_shadowing_primitive() {
4307        let src = "
4308            entity String;
4309            entity E {
4310                a: String,
4311                b: __cedar::String,
4312            };
4313            namespace NS {
4314                entity Bool;
4315                entity F {
4316                    a: Bool,
4317                    b: __cedar::Bool,
4318                };
4319            }
4320        ";
4321        let schema = assert_valid_cedar_schema(src);
4322        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4323        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4324            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("String"));
4325        });
4326        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4327            assert_eq!(&atype.attr_type, &Type::primitive_string());
4328        });
4329        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4330        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4331            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::Bool")); // using the common type definition
4332        });
4333        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4334            assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4335        });
4336
4337        let src_json = json!({
4338            "": {
4339                "entityTypes": {
4340                    "String": {},
4341                    "E": {
4342                        "shape": {
4343                            "type": "Record",
4344                            "attributes": {
4345                                "a": { "type": "Entity", "name": "String" },
4346                                "b": { "type": "__cedar::String" },
4347                            }
4348                        }
4349                    },
4350                },
4351                "actions": {}
4352            },
4353            "NS": {
4354                "entityTypes": {
4355                    "Bool": {},
4356                    "F": {
4357                        "shape": {
4358                            "type": "Record",
4359                            "attributes": {
4360                                "a": { "type": "Entity", "name": "Bool" },
4361                                "b": { "type": "__cedar::Bool" },
4362                            }
4363                        }
4364                    },
4365                },
4366                "actions": {}
4367            }
4368        });
4369        let schema = assert_valid_json_schema(src_json);
4370        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4371        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4372            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("String"));
4373        });
4374        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4375            assert_eq!(&atype.attr_type, &Type::primitive_string());
4376        });
4377        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4378        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4379            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::Bool"));
4380        });
4381        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4382            assert_eq!(&atype.attr_type, &Type::primitive_boolean());
4383        });
4384    }
4385
4386    /// Entity type shadowing an extension type is allowed;
4387    /// you can still refer to the extension type using __cedar
4388    #[test]
4389    fn entity_shadowing_extension() {
4390        let src = "
4391            entity ipaddr;
4392            entity E {
4393                a: ipaddr,
4394                b: __cedar::ipaddr,
4395            };
4396            namespace NS {
4397                entity decimal;
4398                entity F {
4399                    a: decimal,
4400                    b: __cedar::decimal,
4401                };
4402            }
4403        ";
4404        let schema = assert_valid_cedar_schema(src);
4405        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4406        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4407            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("ipaddr"));
4408        });
4409        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4410            assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4411        });
4412        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4413        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4414            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::decimal"));
4415        });
4416        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4417            assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4418        });
4419
4420        let src_json = json!({
4421            "": {
4422                "entityTypes": {
4423                    "ipaddr": {},
4424                    "E": {
4425                        "shape": {
4426                            "type": "Record",
4427                            "attributes": {
4428                                "a": { "type": "Entity", "name": "ipaddr" },
4429                                "b": { "type": "__cedar::ipaddr" },
4430                            }
4431                        }
4432                    },
4433                },
4434                "actions": {}
4435            },
4436            "NS": {
4437                "entityTypes": {
4438                    "decimal": {},
4439                    "F": {
4440                        "shape": {
4441                            "type": "Record",
4442                            "attributes": {
4443                                "a": { "type": "Entity", "name": "decimal" },
4444                                "b": { "type": "__cedar::decimal" },
4445                            }
4446                        }
4447                    },
4448                },
4449                "actions": {}
4450            }
4451        });
4452        let schema = assert_valid_json_schema(src_json);
4453        let e = schema.get_entity_type(&"E".parse().unwrap()).unwrap();
4454        assert_matches!(e.attributes.get_attr("a"), Some(atype) => {
4455            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("ipaddr"));
4456        });
4457        assert_matches!(e.attributes.get_attr("b"), Some(atype) => {
4458            assert_eq!(&atype.attr_type, &Type::extension("ipaddr".parse().unwrap()));
4459        });
4460        let f = schema.get_entity_type(&"NS::F".parse().unwrap()).unwrap();
4461        assert_matches!(f.attributes.get_attr("a"), Some(atype) => {
4462            assert_eq!(&atype.attr_type, &Type::named_entity_reference_from_str("NS::decimal"));
4463        });
4464        assert_matches!(f.attributes.get_attr("b"), Some(atype) => {
4465            assert_eq!(&atype.attr_type, &Type::extension("decimal".parse().unwrap()));
4466        });
4467    }
4468}
4469
4470#[cfg(test)]
4471mod test_resolver {
4472    use std::collections::HashMap;
4473
4474    use cedar_policy_core::{ast::InternalName, extensions::Extensions};
4475    use cool_asserts::assert_matches;
4476
4477    use super::{AllDefs, CommonTypeResolver};
4478    use crate::{
4479        err::SchemaError, json_schema, types::Type, ConditionalName, ValidatorSchemaFragment,
4480    };
4481
4482    fn resolve(schema_json: serde_json::Value) -> Result<HashMap<InternalName, Type>, SchemaError> {
4483        let sfrag = json_schema::Fragment::from_json_value(schema_json).unwrap();
4484        let schema: ValidatorSchemaFragment<ConditionalName, ConditionalName> =
4485            sfrag.try_into().unwrap();
4486        let all_defs = AllDefs::single_fragment(&schema);
4487        let schema = schema.fully_qualify_type_references(&all_defs).unwrap();
4488        let mut defs = HashMap::new();
4489        for def in schema.0 {
4490            defs.extend(def.common_types.defs.into_iter());
4491        }
4492        let resolver = CommonTypeResolver::new(&defs);
4493        resolver
4494            .resolve(Extensions::all_available())
4495            .map(|map| map.into_iter().map(|(k, v)| (k.clone(), v)).collect())
4496    }
4497
4498    #[test]
4499    fn test_simple() {
4500        let schema = serde_json::json!(
4501            {
4502                "": {
4503                    "entityTypes": {},
4504                    "actions": {},
4505                    "commonTypes": {
4506                        "a" : {
4507                            "type": "b"
4508                        },
4509                        "b": {
4510                            "type": "Boolean"
4511                        }
4512                    }
4513                }
4514            }
4515        );
4516        let res = resolve(schema).unwrap();
4517        assert_eq!(
4518            res,
4519            HashMap::from_iter([
4520                ("a".parse().unwrap(), Type::primitive_boolean()),
4521                ("b".parse().unwrap(), Type::primitive_boolean())
4522            ])
4523        );
4524
4525        let schema = serde_json::json!(
4526            {
4527                "": {
4528                    "entityTypes": {},
4529                    "actions": {},
4530                    "commonTypes": {
4531                        "a" : {
4532                            "type": "b"
4533                        },
4534                        "b": {
4535                            "type": "c"
4536                        },
4537                        "c": {
4538                            "type": "Boolean"
4539                        }
4540                    }
4541                }
4542            }
4543        );
4544        let res = resolve(schema).unwrap();
4545        assert_eq!(
4546            res,
4547            HashMap::from_iter([
4548                ("a".parse().unwrap(), Type::primitive_boolean()),
4549                ("b".parse().unwrap(), Type::primitive_boolean()),
4550                ("c".parse().unwrap(), Type::primitive_boolean())
4551            ])
4552        );
4553    }
4554
4555    #[test]
4556    fn test_set() {
4557        let schema = serde_json::json!(
4558            {
4559                "": {
4560                    "entityTypes": {},
4561                    "actions": {},
4562                    "commonTypes": {
4563                        "a" : {
4564                            "type": "Set",
4565                            "element": {
4566                                "type": "b"
4567                            }
4568                        },
4569                        "b": {
4570                            "type": "Boolean"
4571                        }
4572                    }
4573                }
4574            }
4575        );
4576        let res = resolve(schema).unwrap();
4577        assert_eq!(
4578            res,
4579            HashMap::from_iter([
4580                ("a".parse().unwrap(), Type::set(Type::primitive_boolean())),
4581                ("b".parse().unwrap(), Type::primitive_boolean())
4582            ])
4583        );
4584    }
4585
4586    #[test]
4587    fn test_record() {
4588        let schema = serde_json::json!(
4589            {
4590                "": {
4591                    "entityTypes": {},
4592                    "actions": {},
4593                    "commonTypes": {
4594                        "a" : {
4595                            "type": "Record",
4596                            "attributes": {
4597                                "foo": {
4598                                    "type": "b"
4599                                }
4600                            }
4601                        },
4602                        "b": {
4603                            "type": "Boolean"
4604                        }
4605                    }
4606                }
4607            }
4608        );
4609        let res = resolve(schema).unwrap();
4610        assert_eq!(
4611            res,
4612            HashMap::from_iter([
4613                (
4614                    "a".parse().unwrap(),
4615                    Type::record_with_required_attributes(
4616                        std::iter::once(("foo".into(), Type::primitive_boolean())),
4617                        crate::types::OpenTag::ClosedAttributes
4618                    )
4619                ),
4620                ("b".parse().unwrap(), Type::primitive_boolean())
4621            ])
4622        );
4623    }
4624
4625    #[test]
4626    fn test_names() {
4627        let schema = serde_json::json!(
4628            {
4629                "A": {
4630                    "entityTypes": {},
4631                    "actions": {},
4632                    "commonTypes": {
4633                        "a" : {
4634                            "type": "B::a"
4635                        }
4636                    }
4637                },
4638                "B": {
4639                    "entityTypes": {},
4640                    "actions": {},
4641                    "commonTypes": {
4642                        "a" : {
4643                            "type": "Boolean"
4644                        }
4645                    }
4646                }
4647            }
4648        );
4649        let res = resolve(schema).unwrap();
4650        assert_eq!(
4651            res,
4652            HashMap::from_iter([
4653                ("A::a".parse().unwrap(), Type::primitive_boolean()),
4654                ("B::a".parse().unwrap(), Type::primitive_boolean())
4655            ])
4656        );
4657    }
4658
4659    #[test]
4660    fn test_cycles() {
4661        // self reference
4662        let schema = serde_json::json!(
4663            {
4664                "": {
4665                    "entityTypes": {},
4666                    "actions": {},
4667                    "commonTypes": {
4668                        "a" : {
4669                            "type": "a"
4670                        }
4671                    }
4672                }
4673            }
4674        );
4675        let res = resolve(schema);
4676        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4677
4678        // 2 node loop
4679        let schema = serde_json::json!(
4680            {
4681                "": {
4682                    "entityTypes": {},
4683                    "actions": {},
4684                    "commonTypes": {
4685                        "a" : {
4686                            "type": "b"
4687                        },
4688                        "b" : {
4689                            "type": "a"
4690                        }
4691                    }
4692                }
4693            }
4694        );
4695        let res = resolve(schema);
4696        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4697
4698        // 3 node loop
4699        let schema = serde_json::json!(
4700            {
4701                "": {
4702                    "entityTypes": {},
4703                    "actions": {},
4704                    "commonTypes": {
4705                        "a" : {
4706                            "type": "b"
4707                        },
4708                        "b" : {
4709                            "type": "c"
4710                        },
4711                        "c" : {
4712                            "type": "a"
4713                        }
4714                    }
4715                }
4716            }
4717        );
4718        let res = resolve(schema);
4719        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4720
4721        // cross-namespace 2 node loop
4722        let schema = serde_json::json!(
4723            {
4724                "A": {
4725                    "entityTypes": {},
4726                    "actions": {},
4727                    "commonTypes": {
4728                        "a" : {
4729                            "type": "B::a"
4730                        }
4731                    }
4732                },
4733                "B": {
4734                    "entityTypes": {},
4735                    "actions": {},
4736                    "commonTypes": {
4737                        "a" : {
4738                            "type": "A::a"
4739                        }
4740                    }
4741                }
4742            }
4743        );
4744        let res = resolve(schema);
4745        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4746
4747        // cross-namespace 3 node loop
4748        let schema = serde_json::json!(
4749            {
4750                "A": {
4751                    "entityTypes": {},
4752                    "actions": {},
4753                    "commonTypes": {
4754                        "a" : {
4755                            "type": "B::a"
4756                        }
4757                    }
4758                },
4759                "B": {
4760                    "entityTypes": {},
4761                    "actions": {},
4762                    "commonTypes": {
4763                        "a" : {
4764                            "type": "C::a"
4765                        }
4766                    }
4767                },
4768                "C": {
4769                    "entityTypes": {},
4770                    "actions": {},
4771                    "commonTypes": {
4772                        "a" : {
4773                            "type": "A::a"
4774                        }
4775                    }
4776                }
4777            }
4778        );
4779        let res = resolve(schema);
4780        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4781
4782        // cross-namespace 3 node loop
4783        let schema = serde_json::json!(
4784            {
4785                "A": {
4786                    "entityTypes": {},
4787                    "actions": {},
4788                    "commonTypes": {
4789                        "a" : {
4790                            "type": "B::a"
4791                        }
4792                    }
4793                },
4794                "B": {
4795                    "entityTypes": {},
4796                    "actions": {},
4797                    "commonTypes": {
4798                        "a" : {
4799                            "type": "c"
4800                        },
4801                        "c": {
4802                            "type": "A::a"
4803                        }
4804                    }
4805                }
4806            }
4807        );
4808        let res = resolve(schema);
4809        assert_matches!(res, Err(SchemaError::CycleInCommonTypeReferences(_)));
4810    }
4811}
4812
4813#[cfg(test)]
4814mod test_access {
4815    use super::*;
4816
4817    fn schema() -> ValidatorSchema {
4818        let src = r#"
4819        type Task = {
4820    "id": Long,
4821    "name": String,
4822    "state": String,
4823};
4824
4825type Tasks = Set<Task>;
4826entity List in [Application] = {
4827  "editors": Team,
4828  "name": String,
4829  "owner": User,
4830  "readers": Team,
4831  "tasks": Tasks,
4832};
4833entity Application;
4834entity User in [Team, Application] = {
4835  "joblevel": Long,
4836  "location": String,
4837};
4838
4839entity CoolList;
4840
4841entity Team in [Team, Application];
4842
4843action Read, Write, Create;
4844
4845action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
4846    principal: [User],
4847    resource : [List]
4848};
4849
4850action GetList in Read appliesTo {
4851    principal : [User],
4852    resource : [List, CoolList]
4853};
4854
4855action GetLists in Read appliesTo {
4856    principal : [User],
4857    resource : [Application]
4858};
4859
4860action CreateList in Create appliesTo {
4861    principal : [User],
4862    resource : [Application]
4863};
4864
4865        "#;
4866
4867        src.parse().unwrap()
4868    }
4869
4870    #[test]
4871    fn principals() {
4872        let schema = schema();
4873        let principals = schema.principals().collect::<HashSet<_>>();
4874        assert_eq!(principals.len(), 1);
4875        let user: EntityType = "User".parse().unwrap();
4876        assert!(principals.contains(&user));
4877        let principals = schema.principals().collect::<Vec<_>>();
4878        assert!(principals.len() > 1);
4879        assert!(principals.iter().all(|ety| **ety == user));
4880    }
4881
4882    #[test]
4883    fn empty_schema_principals_and_resources() {
4884        let empty: ValidatorSchema = "".parse().unwrap();
4885        assert!(empty.principals().collect::<Vec<_>>().is_empty());
4886        assert!(empty.resources().collect::<Vec<_>>().is_empty());
4887    }
4888
4889    #[test]
4890    fn resources() {
4891        let schema = schema();
4892        let resources = schema.resources().cloned().collect::<HashSet<_>>();
4893        let expected: HashSet<EntityType> = HashSet::from([
4894            "List".parse().unwrap(),
4895            "Application".parse().unwrap(),
4896            "CoolList".parse().unwrap(),
4897        ]);
4898        assert_eq!(resources, expected);
4899    }
4900
4901    #[test]
4902    fn principals_for_action() {
4903        let schema = schema();
4904        let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
4905        let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
4906        let got = schema
4907            .principals_for_action(&delete_list)
4908            .unwrap()
4909            .cloned()
4910            .collect::<Vec<_>>();
4911        assert_eq!(got, vec!["User".parse().unwrap()]);
4912        assert!(schema.principals_for_action(&delete_user).is_none());
4913    }
4914
4915    #[test]
4916    fn resources_for_action() {
4917        let schema = schema();
4918        let delete_list: EntityUID = r#"Action::"DeleteList""#.parse().unwrap();
4919        let delete_user: EntityUID = r#"Action::"DeleteUser""#.parse().unwrap();
4920        let create_list: EntityUID = r#"Action::"CreateList""#.parse().unwrap();
4921        let get_list: EntityUID = r#"Action::"GetList""#.parse().unwrap();
4922        let got = schema
4923            .resources_for_action(&delete_list)
4924            .unwrap()
4925            .cloned()
4926            .collect::<Vec<_>>();
4927        assert_eq!(got, vec!["List".parse().unwrap()]);
4928        let got = schema
4929            .resources_for_action(&create_list)
4930            .unwrap()
4931            .cloned()
4932            .collect::<Vec<_>>();
4933        assert_eq!(got, vec!["Application".parse().unwrap()]);
4934        let got = schema
4935            .resources_for_action(&get_list)
4936            .unwrap()
4937            .cloned()
4938            .collect::<HashSet<_>>();
4939        assert_eq!(
4940            got,
4941            HashSet::from(["List".parse().unwrap(), "CoolList".parse().unwrap()])
4942        );
4943        assert!(schema.principals_for_action(&delete_user).is_none());
4944    }
4945
4946    #[test]
4947    fn principal_parents() {
4948        let schema = schema();
4949        let user: EntityType = "User".parse().unwrap();
4950        let parents = schema
4951            .ancestors(&user)
4952            .unwrap()
4953            .cloned()
4954            .collect::<HashSet<_>>();
4955        let expected = HashSet::from(["Team".parse().unwrap(), "Application".parse().unwrap()]);
4956        assert_eq!(parents, expected);
4957        let parents = schema
4958            .ancestors(&"List".parse().unwrap())
4959            .unwrap()
4960            .cloned()
4961            .collect::<HashSet<_>>();
4962        let expected = HashSet::from(["Application".parse().unwrap()]);
4963        assert_eq!(parents, expected);
4964        assert!(schema.ancestors(&"Foo".parse().unwrap()).is_none());
4965        let parents = schema
4966            .ancestors(&"CoolList".parse().unwrap())
4967            .unwrap()
4968            .cloned()
4969            .collect::<HashSet<_>>();
4970        let expected = HashSet::from([]);
4971        assert_eq!(parents, expected);
4972    }
4973
4974    #[test]
4975    fn action_groups() {
4976        let schema = schema();
4977        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
4978        let expected = ["Read", "Write", "Create"]
4979            .into_iter()
4980            .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
4981            .collect::<HashSet<EntityUID>>();
4982        assert_eq!(groups, expected);
4983    }
4984
4985    #[test]
4986    fn actions() {
4987        let schema = schema();
4988        let actions = schema.actions().cloned().collect::<HashSet<_>>();
4989        let expected = [
4990            "Read",
4991            "Write",
4992            "Create",
4993            "DeleteList",
4994            "EditShare",
4995            "UpdateList",
4996            "CreateTask",
4997            "UpdateTask",
4998            "DeleteTask",
4999            "GetList",
5000            "GetLists",
5001            "CreateList",
5002        ]
5003        .into_iter()
5004        .map(|ty| format!("Action::\"{ty}\"").parse().unwrap())
5005        .collect::<HashSet<EntityUID>>();
5006        assert_eq!(actions, expected);
5007    }
5008
5009    #[test]
5010    fn entities() {
5011        let schema = schema();
5012        let entities = schema
5013            .entity_types()
5014            .map(|(ty, _)| ty)
5015            .cloned()
5016            .collect::<HashSet<_>>();
5017        let expected = ["List", "Application", "User", "CoolList", "Team"]
5018            .into_iter()
5019            .map(|ty| ty.parse().unwrap())
5020            .collect::<HashSet<EntityType>>();
5021        assert_eq!(entities, expected);
5022    }
5023}
5024
5025#[cfg(test)]
5026mod test_access_namespace {
5027    use super::*;
5028
5029    fn schema() -> ValidatorSchema {
5030        let src = r#"
5031        namespace Foo {
5032        type Task = {
5033    "id": Long,
5034    "name": String,
5035    "state": String,
5036};
5037
5038type Tasks = Set<Task>;
5039entity List in [Application] = {
5040  "editors": Team,
5041  "name": String,
5042  "owner": User,
5043  "readers": Team,
5044  "tasks": Tasks,
5045};
5046entity Application;
5047entity User in [Team, Application] = {
5048  "joblevel": Long,
5049  "location": String,
5050};
5051
5052entity CoolList;
5053
5054entity Team in [Team, Application];
5055
5056action Read, Write, Create;
5057
5058action DeleteList, EditShare, UpdateList, CreateTask, UpdateTask, DeleteTask in Write appliesTo {
5059    principal: [User],
5060    resource : [List]
5061};
5062
5063action GetList in Read appliesTo {
5064    principal : [User],
5065    resource : [List, CoolList]
5066};
5067
5068action GetLists in Read appliesTo {
5069    principal : [User],
5070    resource : [Application]
5071};
5072
5073action CreateList in Create appliesTo {
5074    principal : [User],
5075    resource : [Application]
5076};
5077    }
5078
5079        "#;
5080
5081        src.parse().unwrap()
5082    }
5083
5084    #[test]
5085    fn principals() {
5086        let schema = schema();
5087        let principals = schema.principals().collect::<HashSet<_>>();
5088        assert_eq!(principals.len(), 1);
5089        let user: EntityType = "Foo::User".parse().unwrap();
5090        assert!(principals.contains(&user));
5091        let principals = schema.principals().collect::<Vec<_>>();
5092        assert!(principals.len() > 1);
5093        assert!(principals.iter().all(|ety| **ety == user));
5094    }
5095
5096    #[test]
5097    fn empty_schema_principals_and_resources() {
5098        let empty: ValidatorSchema = "".parse().unwrap();
5099        assert!(empty.principals().collect::<Vec<_>>().is_empty());
5100        assert!(empty.resources().collect::<Vec<_>>().is_empty());
5101    }
5102
5103    #[test]
5104    fn resources() {
5105        let schema = schema();
5106        let resources = schema.resources().cloned().collect::<HashSet<_>>();
5107        let expected: HashSet<EntityType> = HashSet::from([
5108            "Foo::List".parse().unwrap(),
5109            "Foo::Application".parse().unwrap(),
5110            "Foo::CoolList".parse().unwrap(),
5111        ]);
5112        assert_eq!(resources, expected);
5113    }
5114
5115    #[test]
5116    fn principals_for_action() {
5117        let schema = schema();
5118        let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5119        let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5120        let got = schema
5121            .principals_for_action(&delete_list)
5122            .unwrap()
5123            .cloned()
5124            .collect::<Vec<_>>();
5125        assert_eq!(got, vec!["Foo::User".parse().unwrap()]);
5126        assert!(schema.principals_for_action(&delete_user).is_none());
5127    }
5128
5129    #[test]
5130    fn resources_for_action() {
5131        let schema = schema();
5132        let delete_list: EntityUID = r#"Foo::Action::"DeleteList""#.parse().unwrap();
5133        let delete_user: EntityUID = r#"Foo::Action::"DeleteUser""#.parse().unwrap();
5134        let create_list: EntityUID = r#"Foo::Action::"CreateList""#.parse().unwrap();
5135        let get_list: EntityUID = r#"Foo::Action::"GetList""#.parse().unwrap();
5136        let got = schema
5137            .resources_for_action(&delete_list)
5138            .unwrap()
5139            .cloned()
5140            .collect::<Vec<_>>();
5141        assert_eq!(got, vec!["Foo::List".parse().unwrap()]);
5142        let got = schema
5143            .resources_for_action(&create_list)
5144            .unwrap()
5145            .cloned()
5146            .collect::<Vec<_>>();
5147        assert_eq!(got, vec!["Foo::Application".parse().unwrap()]);
5148        let got = schema
5149            .resources_for_action(&get_list)
5150            .unwrap()
5151            .cloned()
5152            .collect::<HashSet<_>>();
5153        assert_eq!(
5154            got,
5155            HashSet::from([
5156                "Foo::List".parse().unwrap(),
5157                "Foo::CoolList".parse().unwrap()
5158            ])
5159        );
5160        assert!(schema.principals_for_action(&delete_user).is_none());
5161    }
5162
5163    #[test]
5164    fn principal_parents() {
5165        let schema = schema();
5166        let user: EntityType = "Foo::User".parse().unwrap();
5167        let parents = schema
5168            .ancestors(&user)
5169            .unwrap()
5170            .cloned()
5171            .collect::<HashSet<_>>();
5172        let expected = HashSet::from([
5173            "Foo::Team".parse().unwrap(),
5174            "Foo::Application".parse().unwrap(),
5175        ]);
5176        assert_eq!(parents, expected);
5177        let parents = schema
5178            .ancestors(&"Foo::List".parse().unwrap())
5179            .unwrap()
5180            .cloned()
5181            .collect::<HashSet<_>>();
5182        let expected = HashSet::from(["Foo::Application".parse().unwrap()]);
5183        assert_eq!(parents, expected);
5184        assert!(schema.ancestors(&"Foo::Foo".parse().unwrap()).is_none());
5185        let parents = schema
5186            .ancestors(&"Foo::CoolList".parse().unwrap())
5187            .unwrap()
5188            .cloned()
5189            .collect::<HashSet<_>>();
5190        let expected = HashSet::from([]);
5191        assert_eq!(parents, expected);
5192    }
5193
5194    #[test]
5195    fn action_groups() {
5196        let schema = schema();
5197        let groups = schema.action_groups().cloned().collect::<HashSet<_>>();
5198        let expected = ["Read", "Write", "Create"]
5199            .into_iter()
5200            .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5201            .collect::<HashSet<EntityUID>>();
5202        assert_eq!(groups, expected);
5203    }
5204
5205    #[test]
5206    fn actions() {
5207        let schema = schema();
5208        let actions = schema.actions().cloned().collect::<HashSet<_>>();
5209        let expected = [
5210            "Read",
5211            "Write",
5212            "Create",
5213            "DeleteList",
5214            "EditShare",
5215            "UpdateList",
5216            "CreateTask",
5217            "UpdateTask",
5218            "DeleteTask",
5219            "GetList",
5220            "GetLists",
5221            "CreateList",
5222        ]
5223        .into_iter()
5224        .map(|ty| format!("Foo::Action::\"{ty}\"").parse().unwrap())
5225        .collect::<HashSet<EntityUID>>();
5226        assert_eq!(actions, expected);
5227    }
5228
5229    #[test]
5230    fn entities() {
5231        let schema = schema();
5232        let entities = schema
5233            .entity_types()
5234            .map(|(ty, _)| ty)
5235            .cloned()
5236            .collect::<HashSet<_>>();
5237        let expected = [
5238            "Foo::List",
5239            "Foo::Application",
5240            "Foo::User",
5241            "Foo::CoolList",
5242            "Foo::Team",
5243        ]
5244        .into_iter()
5245        .map(|ty| ty.parse().unwrap())
5246        .collect::<HashSet<EntityType>>();
5247        assert_eq!(entities, expected);
5248    }
5249}