Skip to main content

cedar_policy_core/validator/
schema.rs

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