cedar_policy_validator/
schema.rs

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