Skip to main content

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