cedar_policy_validator/
schema.rs

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