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