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