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