cedar_policy_validator/
schema.rs

1/*
2 * Copyright 2022-2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
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 std::collections::{hash_map::Entry, HashMap, HashSet};
24
25use cedar_policy_core::{
26    ast::{Entity, EntityType, EntityUID, Name},
27    entities::{Entities, EntitiesError, TCComputation},
28    extensions::Extensions,
29    transitive_closure::compute_tc,
30};
31use serde::{Deserialize, Serialize};
32use serde_with::serde_as;
33
34use super::NamespaceDefinition;
35use crate::{
36    err::*,
37    types::{Attributes, EntityRecordKind, OpenTag, Type},
38    SchemaFragment,
39};
40
41mod action;
42pub use action::ValidatorActionId;
43pub(crate) use action::ValidatorApplySpec;
44mod entity_type;
45pub use entity_type::ValidatorEntityType;
46mod namespace_def;
47pub(crate) use namespace_def::is_action_entity_type;
48pub use namespace_def::ValidatorNamespaceDef;
49#[cfg(test)]
50pub(crate) use namespace_def::ACTION_ENTITY_TYPE;
51
52// We do not have a dafny model for action attributes, so we disable them by defualt.
53#[derive(Eq, PartialEq, Copy, Clone, Default)]
54pub enum ActionBehavior {
55    /// Action entities cannot have attributes. Attempting to declare attributes
56    /// will result in a error when constructing the schema.
57    #[default]
58    ProhibitAttributes,
59    /// Action entities may have attributes.
60    PermitAttributes,
61}
62
63#[derive(Debug)]
64pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
65
66impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
67    type Error = SchemaError;
68
69    fn try_into(self) -> Result<ValidatorSchemaFragment> {
70        ValidatorSchemaFragment::from_schema_fragment(
71            self,
72            ActionBehavior::default(),
73            Extensions::all_available(),
74        )
75    }
76}
77
78impl ValidatorSchemaFragment {
79    pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
80        Self(namespaces.into_iter().collect())
81    }
82
83    pub fn from_schema_fragment(
84        fragment: SchemaFragment,
85        action_behavior: ActionBehavior,
86        extensions: Extensions<'_>,
87    ) -> Result<Self> {
88        Ok(Self(
89            fragment
90                .0
91                .into_iter()
92                .map(|(fragment_ns, ns_def)| {
93                    ValidatorNamespaceDef::from_namespace_definition(
94                        Some(fragment_ns),
95                        ns_def,
96                        action_behavior,
97                        extensions,
98                    )
99                })
100                .collect::<Result<Vec<_>>>()?,
101        ))
102    }
103
104    /// Access the `Name`s for the namespaces in this fragment.
105    pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
106        self.0.iter().map(|d| d.namespace())
107    }
108}
109
110#[serde_as]
111#[derive(Clone, Debug, Serialize)]
112pub struct ValidatorSchema {
113    /// Map from entity type names to the ValidatorEntityType object.
114    #[serde(rename = "entityTypes")]
115    #[serde_as(as = "Vec<(_, _)>")]
116    entity_types: HashMap<Name, ValidatorEntityType>,
117
118    /// Map from action id names to the ValidatorActionId object.
119    #[serde(rename = "actionIds")]
120    #[serde_as(as = "Vec<(_, _)>")]
121    action_ids: HashMap<EntityUID, ValidatorActionId>,
122}
123
124impl std::str::FromStr for ValidatorSchema {
125    type Err = SchemaError;
126
127    fn from_str(s: &str) -> Result<Self> {
128        serde_json::from_str::<SchemaFragment>(s)?.try_into()
129    }
130}
131
132impl TryFrom<NamespaceDefinition> for ValidatorSchema {
133    type Error = SchemaError;
134
135    fn try_from(nsd: NamespaceDefinition) -> Result<ValidatorSchema> {
136        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
137            nsd.try_into()?
138        ])])
139    }
140}
141
142impl TryFrom<SchemaFragment> for ValidatorSchema {
143    type Error = SchemaError;
144
145    fn try_from(frag: SchemaFragment) -> Result<ValidatorSchema> {
146        ValidatorSchema::from_schema_fragments([frag.try_into()?])
147    }
148}
149
150impl ValidatorSchema {
151    // Create a ValidatorSchema without any entity types or actions ids.
152    pub fn empty() -> ValidatorSchema {
153        Self {
154            entity_types: HashMap::new(),
155            action_ids: HashMap::new(),
156        }
157    }
158
159    /// Construct a `ValidatorSchema` from a JSON value (which should be an
160    /// object matching the `SchemaFileFormat` shape).
161    pub fn from_json_value(json: serde_json::Value, extensions: Extensions<'_>) -> Result<Self> {
162        Self::from_schema_file(
163            SchemaFragment::from_json_value(json)?,
164            ActionBehavior::default(),
165            extensions,
166        )
167    }
168
169    /// Construct a `ValidatorSchema` directly from a file.
170    pub fn from_file(file: impl std::io::Read, extensions: Extensions<'_>) -> Result<Self> {
171        Self::from_schema_file(
172            SchemaFragment::from_file(file)?,
173            ActionBehavior::default(),
174            extensions,
175        )
176    }
177
178    pub fn from_schema_file(
179        schema_file: SchemaFragment,
180        action_behavior: ActionBehavior,
181        extensions: Extensions<'_>,
182    ) -> Result<ValidatorSchema> {
183        Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
184            schema_file,
185            action_behavior,
186            extensions,
187        )?])
188    }
189
190    /// Construct a new `ValidatorSchema` from some number of schema fragments.
191    pub fn from_schema_fragments(
192        fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
193    ) -> Result<ValidatorSchema> {
194        let mut type_defs = HashMap::new();
195        let mut entity_type_fragments = HashMap::new();
196        let mut action_fragments = HashMap::new();
197
198        for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
199            // Build aggregate maps for the declared typedefs, entity types, and
200            // actions, checking that nothing is defined twice.  Namespaces were
201            // already added by the `ValidatorNamespaceDef`, so the same base
202            // type name may appear multiple times so long as the namespaces are
203            // different.
204            for (name, ty) in ns_def.type_defs.type_defs {
205                match type_defs.entry(name) {
206                    Entry::Vacant(v) => v.insert(ty),
207                    Entry::Occupied(o) => {
208                        return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
209                    }
210                };
211            }
212
213            for (name, entity_type) in ns_def.entity_types.entity_types {
214                match entity_type_fragments.entry(name) {
215                    Entry::Vacant(v) => v.insert(entity_type),
216                    Entry::Occupied(o) => {
217                        return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
218                    }
219                };
220            }
221
222            for (action_euid, action) in ns_def.actions.actions {
223                match action_fragments.entry(action_euid) {
224                    Entry::Vacant(v) => v.insert(action),
225                    Entry::Occupied(o) => {
226                        return Err(SchemaError::DuplicateAction(o.key().to_string()))
227                    }
228                };
229            }
230        }
231
232        // Invert the `parents` relation defined by entities and action so far
233        // to get a `children` relation.
234        let mut entity_children = HashMap::new();
235        for (name, entity_type) in entity_type_fragments.iter() {
236            for parent in entity_type.parents.iter() {
237                entity_children
238                    .entry(parent.clone())
239                    .or_insert_with(HashSet::new)
240                    .insert(name.clone());
241            }
242        }
243
244        let mut entity_types = entity_type_fragments
245            .into_iter()
246            .map(|(name, entity_type)| -> Result<_> {
247                // Keys of the `entity_children` map were values of an
248                // `memberOfTypes` list, so they might not have been declared in
249                // their fragment.  By removing entries from `entity_children`
250                // where the key is a declared name, we will be left with a map
251                // where the keys are undeclared. These keys are used to report
252                // an error when undeclared entity types are referenced inside a
253                // `memberOfTypes` list. The error is reported alongside the
254                // error for any other undeclared entity types by
255                // `check_for_undeclared`.
256                let descendants = entity_children.remove(&name).unwrap_or_default();
257                let (attributes, open_attributes) = Self::record_attributes_or_none(
258                    entity_type.attributes.resolve_type_defs(&type_defs)?,
259                )
260                .ok_or(SchemaError::ContextOrShapeNotRecord(
261                    ContextOrShape::EntityTypeShape(name.clone()),
262                ))?;
263                Ok((
264                    name.clone(),
265                    ValidatorEntityType {
266                        name,
267                        descendants,
268                        attributes,
269                        open_attributes,
270                    },
271                ))
272            })
273            .collect::<Result<HashMap<_, _>>>()?;
274
275        let mut action_children = HashMap::new();
276        for (euid, action) in action_fragments.iter() {
277            for parent in action.parents.iter() {
278                action_children
279                    .entry(parent.clone())
280                    .or_insert_with(HashSet::new)
281                    .insert(euid.clone());
282            }
283        }
284        let mut action_ids = action_fragments
285            .into_iter()
286            .map(|(name, action)| -> Result<_> {
287                let descendants = action_children.remove(&name).unwrap_or_default();
288                let (context, open_context_attributes) =
289                    Self::record_attributes_or_none(action.context.resolve_type_defs(&type_defs)?)
290                        .ok_or(SchemaError::ContextOrShapeNotRecord(
291                            ContextOrShape::ActionContext(name.clone()),
292                        ))?;
293                Ok((
294                    name.clone(),
295                    ValidatorActionId {
296                        name,
297                        applies_to: action.applies_to,
298                        descendants,
299                        context: Type::record_with_attributes(
300                            context.attrs,
301                            open_context_attributes,
302                        ),
303                        attribute_types: action.attribute_types,
304                        attributes: action.attributes,
305                    },
306                ))
307            })
308            .collect::<Result<HashMap<_, _>>>()?;
309
310        // We constructed entity types and actions with child maps, but we need
311        // transitively closed descendants.
312        compute_tc(&mut entity_types, false)?;
313        // Pass `true` here so that we also check that the action hierarchy does
314        // not contain cycles.
315        compute_tc(&mut action_ids, true)?;
316
317        // Return with an error if there is an undeclared entity or action
318        // referenced in any fragment. `{entity,action}_children` are provided
319        // for the `undeclared_parent_{entities,actions}` arguments because
320        // removed keys from these maps as we encountered declarations for the
321        // entity types or actions. Any keys left in the map are therefore
322        // undeclared.
323        Self::check_for_undeclared(
324            &entity_types,
325            entity_children.into_keys(),
326            &action_ids,
327            action_children.into_keys(),
328        )?;
329
330        Ok(ValidatorSchema {
331            entity_types,
332            action_ids,
333        })
334    }
335
336    /// Check that all entity types and actions referenced in the schema are in
337    /// the set of declared entity type or action names. Point of caution: this
338    /// function assumes that all entity types are fully qualified. This is
339    /// handled by the `SchemaFragment` constructor.
340    fn check_for_undeclared(
341        entity_types: &HashMap<Name, ValidatorEntityType>,
342        undeclared_parent_entities: impl IntoIterator<Item = Name>,
343        action_ids: &HashMap<EntityUID, ValidatorActionId>,
344        undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
345    ) -> Result<()> {
346        // When we constructed `entity_types`, we removed entity types from  the
347        // `entity_children` map as we encountered a declaration for that type.
348        // Any entity types left in the map are therefore undeclared. These are
349        // any undeclared entity types which appeared in a `memberOf` list.
350        let mut undeclared_e = undeclared_parent_entities
351            .into_iter()
352            .map(|n| n.to_string())
353            .collect::<HashSet<_>>();
354        // Looking at entity types, we need to check entity references in
355        // attribute types. We already know that all elements of the
356        // `descendants` list were declared because the list is a result of
357        // inverting the `memberOf` relationship which mapped declared entity
358        // types to their parent entity types.
359        for entity_type in entity_types.values() {
360            for (_, attr_typ) in entity_type.attributes() {
361                Self::check_undeclared_in_type(
362                    &attr_typ.attr_type,
363                    entity_types,
364                    &mut undeclared_e,
365                );
366            }
367        }
368
369        // Undeclared actions in a `memberOf` list.
370        let undeclared_a = undeclared_parent_actions
371            .into_iter()
372            .map(|n| n.to_string())
373            .collect::<HashSet<_>>();
374        // For actions, we check entity references in the context attribute
375        // types and `appliesTo` lists. See the `entity_types` loop for why the
376        // `descendants` list is not checked.
377        for action in action_ids.values() {
378            Self::check_undeclared_in_type(&action.context, entity_types, &mut undeclared_e);
379
380            for p_entity in action.applies_to.applicable_principal_types() {
381                match p_entity {
382                    EntityType::Specified(p_entity) => {
383                        if !entity_types.contains_key(&p_entity) {
384                            undeclared_e.insert(p_entity.to_string());
385                        }
386                    }
387                    EntityType::Unspecified => (),
388                }
389            }
390
391            for r_entity in action.applies_to.applicable_resource_types() {
392                match r_entity {
393                    EntityType::Specified(r_entity) => {
394                        if !entity_types.contains_key(&r_entity) {
395                            undeclared_e.insert(r_entity.to_string());
396                        }
397                    }
398                    EntityType::Unspecified => (),
399                }
400            }
401        }
402        if !undeclared_e.is_empty() {
403            return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
404        }
405        if !undeclared_a.is_empty() {
406            return Err(SchemaError::UndeclaredActions(undeclared_a));
407        }
408
409        Ok(())
410    }
411
412    fn record_attributes_or_none(ty: Type) -> Option<(Attributes, OpenTag)> {
413        match ty {
414            Type::EntityOrRecord(EntityRecordKind::Record {
415                attrs,
416                open_attributes,
417            }) => Some((attrs, open_attributes)),
418            _ => None,
419        }
420    }
421
422    // Check that all entity types appearing inside a type are in the set of
423    // declared entity types, adding any undeclared entity types to the
424    // `undeclared_types` set.
425    fn check_undeclared_in_type(
426        ty: &Type,
427        entity_types: &HashMap<Name, ValidatorEntityType>,
428        undeclared_types: &mut HashSet<String>,
429    ) {
430        match ty {
431            Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
432                for name in lub.iter() {
433                    if !entity_types.contains_key(name) {
434                        undeclared_types.insert(name.to_string());
435                    }
436                }
437            }
438
439            Type::EntityOrRecord(EntityRecordKind::Record { attrs, .. }) => {
440                for (_, attr_ty) in attrs.iter() {
441                    Self::check_undeclared_in_type(
442                        &attr_ty.attr_type,
443                        entity_types,
444                        undeclared_types,
445                    );
446                }
447            }
448
449            Type::Set {
450                element_type: Some(element_type),
451            } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
452
453            _ => (),
454        }
455    }
456
457    /// Lookup the ValidatorActionId object in the schema with the given name.
458    pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
459        self.action_ids.get(action_id)
460    }
461
462    /// Lookup the ValidatorEntityType object in the schema with the given name.
463    pub fn get_entity_type<'a>(&'a self, entity_type_id: &Name) -> Option<&'a ValidatorEntityType> {
464        self.entity_types.get(entity_type_id)
465    }
466
467    /// Return true when the entity_type_id corresponds to a valid entity type.
468    pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
469        self.action_ids.contains_key(action_id)
470    }
471
472    /// Return true when the entity_type_id corresponds to a valid entity type.
473    pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
474        is_action_entity_type(entity_type) || self.entity_types.contains_key(entity_type)
475    }
476
477    /// Return true when `euid` has an entity type declared by the schema. We
478    /// treat an Unspecified as "known" because it is always possible to declare
479    /// an action using an unspecified principal/resource type without first
480    /// declaring unspecified as an entity type in the entity types list.
481    pub(crate) fn euid_has_known_entity_type(&self, euid: &EntityUID) -> bool {
482        match euid.entity_type() {
483            EntityType::Specified(ety) => self.is_known_entity_type(ety),
484            EntityType::Unspecified => true,
485        }
486    }
487
488    /// An iterator over the action ids in the schema.
489    pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
490        self.action_ids.keys()
491    }
492
493    /// An iterator over the entity type names in the schema.
494    pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
495        self.entity_types.keys()
496    }
497
498    /// An iterator matching the entity Types to their Validator Types
499    pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
500        self.entity_types.iter()
501    }
502
503    /// Get all entity types in the schema where an `{entity0} in {entity}` can
504    /// evaluate to `true` for some `entity0` with that entity type. This
505    /// includes all entity types that are descendants of the type of `entity`
506    /// according  to the schema, and the type of `entity` itself because
507    /// `entity in entity` evaluates to `true`.
508    pub(crate) fn get_entity_types_in<'a>(&'a self, entity: &'a EntityUID) -> Vec<&Name> {
509        match entity.entity_type() {
510            EntityType::Specified(ety) => {
511                let mut descendants = self
512                    .get_entity_type(ety)
513                    .map(|v_ety| v_ety.descendants.iter().collect::<Vec<_>>())
514                    .unwrap_or_default();
515                descendants.push(ety);
516                descendants
517            }
518            EntityType::Unspecified => Vec::new(),
519        }
520    }
521
522    /// Get all entity types in the schema where an `{entity0} in {euids}` can
523    /// evaluate to `true` for some `entity0` with that entity type. See comment
524    /// on `get_entity_types_in`.
525    pub(crate) fn get_entity_types_in_set<'a>(
526        &'a self,
527        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
528    ) -> impl Iterator<Item = &Name> {
529        euids
530            .into_iter()
531            .map(|e| self.get_entity_types_in(e))
532            .flatten()
533    }
534
535    /// Get all action entities in the schema where `action in euids` evaluates
536    /// to `true`. This includes all actions which are descendants of some
537    /// element of `euids`, and all elements of `euids`.
538    pub(crate) fn get_actions_in_set<'a>(
539        &'a self,
540        euids: impl IntoIterator<Item = &'a EntityUID> + 'a,
541    ) -> Option<Vec<&'a EntityUID>> {
542        euids
543            .into_iter()
544            .map(|e| {
545                self.get_action_id(e).map(|action| {
546                    action
547                        .descendants
548                        .iter()
549                        .chain(std::iter::once(&action.name))
550                })
551            })
552            .collect::<Option<Vec<_>>>()
553            .map(|v| v.into_iter().flatten().collect::<Vec<_>>())
554    }
555
556    /// Get the `Type` of context expected for the given `action`.
557    /// This always reutrns a closed record type.
558    ///
559    /// Returns `None` if the action is not in the schema.
560    pub fn context_type(&self, action: &EntityUID) -> Option<Type> {
561        // INVARIANT: `ValidatorActionId::context_type` always returns a closed
562        // record type
563        self.get_action_id(action)
564            .map(ValidatorActionId::context_type)
565    }
566
567    /// Invert the action hierarchy to get the ancestor relation expected for
568    /// the `Entity` datatype instead of descendants as stored by the schema.
569    pub(crate) fn action_entities_iter(
570        &self,
571    ) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
572        // We could store the un-inverted `memberOf` relation for each action,
573        // but I [john-h-kastner-aws] judge that the current implementation is
574        // actually less error prone, as it minimizes the threading of data
575        // structures through some complicated bits of schema construction code,
576        // and avoids computing the TC twice.
577        let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
578        for (action_euid, action_def) in &self.action_ids {
579            for descendant in &action_def.descendants {
580                action_ancestors
581                    .entry(descendant)
582                    .or_default()
583                    .insert(action_euid.clone());
584            }
585        }
586        self.action_ids.iter().map(move |(action_id, action)| {
587            Entity::new_with_attr_partial_value_serialized_as_expr(
588                action_id.clone(),
589                action.attributes.clone(),
590                action_ancestors.remove(action_id).unwrap_or_default(),
591            )
592        })
593    }
594
595    /// Construct an `Entity` object for each action in the schema
596    pub fn action_entities(&self) -> std::result::Result<Entities, EntitiesError> {
597        let extensions = Extensions::all_available();
598        Entities::from_entities(
599            self.action_entities_iter(),
600            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
601            TCComputation::AssumeAlreadyComputed,
602            extensions,
603        )
604        .map_err(Into::into)
605    }
606}
607
608/// Used to write a schema implicitly overriding the default handling of action
609/// groups.
610#[derive(Debug, Clone, Deserialize)]
611#[serde(transparent)]
612pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
613
614impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
615    type Error = SchemaError;
616
617    fn try_into(self) -> Result<ValidatorSchema> {
618        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
619            ValidatorNamespaceDef::from_namespace_definition(
620                None,
621                self.0,
622                crate::ActionBehavior::PermitAttributes,
623                Extensions::all_available(),
624            )?,
625        ])])
626    }
627}
628
629// PANIC SAFETY unit tests
630#[allow(clippy::panic)]
631// PANIC SAFETY unit tests
632#[allow(clippy::indexing_slicing)]
633#[cfg(test)]
634mod test {
635    use std::{collections::BTreeMap, str::FromStr};
636
637    use crate::types::Type;
638    use crate::{SchemaType, SchemaTypeVariant};
639
640    use cedar_policy_core::ast::RestrictedExpr;
641    use cedar_policy_core::parser::err::{ParseError, ToASTError};
642    use cool_asserts::assert_matches;
643    use serde_json::json;
644
645    use super::*;
646
647    // Well-formed schema
648    #[test]
649    fn test_from_schema_file() {
650        let src = json!(
651        {
652            "entityTypes": {
653                "User": {
654                    "memberOfTypes": [ "Group" ]
655                },
656                "Group": {
657                    "memberOfTypes": []
658                },
659                "Photo": {
660                    "memberOfTypes": [ "Album" ]
661                },
662                "Album": {
663                    "memberOfTypes": []
664                }
665            },
666            "actions": {
667                "view_photo": {
668                    "appliesTo": {
669                        "principalTypes": ["User", "Group"],
670                        "resourceTypes": ["Photo"]
671                    }
672                }
673            }
674        });
675        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
676        let schema: Result<ValidatorSchema> = schema_file.try_into();
677        assert!(schema.is_ok());
678    }
679
680    // Duplicate entity "Photo"
681    #[test]
682    fn test_from_schema_file_duplicate_entity() {
683        // Test written using `from_str` instead of `from_value` because the
684        // `json!` macro silently ignores duplicate map keys.
685        let src = r#"
686        {"": {
687            "entityTypes": {
688                "User": {
689                    "memberOfTypes": [ "Group" ]
690                },
691                "Group": {
692                    "memberOfTypes": []
693                },
694                "Photo": {
695                    "memberOfTypes": [ "Album" ]
696                },
697                "Photo": {
698                    "memberOfTypes": []
699                }
700            },
701            "actions": {
702                "view_photo": {
703                    "memberOf": [],
704                    "appliesTo": {
705                        "principalTypes": ["User", "Group"],
706                        "resourceTypes": ["Photo"]
707                    }
708                }
709            }
710        }}"#;
711
712        match ValidatorSchema::from_str(src) {
713            Err(SchemaError::Serde(_)) => (),
714            _ => panic!("Expected serde error due to duplicate entity type."),
715        }
716    }
717
718    // Duplicate action "view_photo"
719    #[test]
720    fn test_from_schema_file_duplicate_action() {
721        // Test written using `from_str` instead of `from_value` because the
722        // `json!` macro silently ignores duplicate map keys.
723        let src = r#"
724        {"": {
725            "entityTypes": {
726                "User": {
727                    "memberOfTypes": [ "Group" ]
728                },
729                "Group": {
730                    "memberOfTypes": []
731                },
732                "Photo": {
733                    "memberOfTypes": []
734                }
735            },
736            "actions": {
737                "view_photo": {
738                    "memberOf": [],
739                    "appliesTo": {
740                        "principalTypes": ["User", "Group"],
741                        "resourceTypes": ["Photo"]
742                    }
743                },
744                "view_photo": { }
745            }
746        }"#;
747        match ValidatorSchema::from_str(src) {
748            Err(SchemaError::Serde(_)) => (),
749            _ => panic!("Expected serde error due to duplicate action type."),
750        }
751    }
752
753    // Undefined entity types "Grop", "Usr", "Phoot"
754    #[test]
755    fn test_from_schema_file_undefined_entities() {
756        let src = json!(
757        {
758            "entityTypes": {
759                "User": {
760                    "memberOfTypes": [ "Grop" ]
761                },
762                "Group": {
763                    "memberOfTypes": []
764                },
765                "Photo": {
766                    "memberOfTypes": []
767                }
768            },
769            "actions": {
770                "view_photo": {
771                    "appliesTo": {
772                        "principalTypes": ["Usr", "Group"],
773                        "resourceTypes": ["Phoot"]
774                    }
775                }
776            }
777        });
778        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
779        let schema: Result<ValidatorSchema> = schema_file.try_into();
780        match schema {
781            Ok(_) => panic!("from_schema_file should have failed"),
782            Err(SchemaError::UndeclaredEntityTypes(v)) => {
783                assert_eq!(v.len(), 3)
784            }
785            _ => panic!("Unexpected error from from_schema_file"),
786        }
787    }
788
789    #[test]
790    fn undefined_entity_namespace_member_of() {
791        let src = json!(
792        {"Foo": {
793            "entityTypes": {
794                "User": {
795                    "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
796                },
797                "Group": { }
798            },
799            "actions": {}
800        }});
801        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
802        let schema: Result<ValidatorSchema> = schema_file.try_into();
803        match schema {
804            Ok(_) => panic!("try_into should have failed"),
805            Err(SchemaError::UndeclaredEntityTypes(v)) => {
806                assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
807            }
808            _ => panic!("Unexpected error from try_into"),
809        }
810    }
811
812    #[test]
813    fn undefined_entity_namespace_applies_to() {
814        let src = json!(
815        {"Foo": {
816            "entityTypes": { "User": { }, "Photo": { } },
817            "actions": {
818                "view_photo": {
819                    "appliesTo": {
820                        "principalTypes": ["Foo::User", "Bar::User"],
821                        "resourceTypes": ["Photo", "Bar::Photo"],
822                    }
823                }
824            }
825        }});
826        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
827        let schema: Result<ValidatorSchema> = schema_file.try_into();
828        match schema {
829            Ok(_) => panic!("try_into should have failed"),
830            Err(SchemaError::UndeclaredEntityTypes(v)) => {
831                assert_eq!(
832                    v,
833                    HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
834                )
835            }
836            _ => panic!("Unexpected error from try_into"),
837        }
838    }
839
840    // Undefined action "photo_actions"
841    #[test]
842    fn test_from_schema_file_undefined_action() {
843        let src = json!(
844        {
845            "entityTypes": {
846                "User": {
847                    "memberOfTypes": [ "Group" ]
848                },
849                "Group": {
850                    "memberOfTypes": []
851                },
852                "Photo": {
853                    "memberOfTypes": []
854                }
855            },
856            "actions": {
857                "view_photo": {
858                    "memberOf": [ {"id": "photo_action"} ],
859                    "appliesTo": {
860                        "principalTypes": ["User", "Group"],
861                        "resourceTypes": ["Photo"]
862                    }
863                }
864            }
865        });
866        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
867        let schema: Result<ValidatorSchema> = schema_file.try_into();
868        match schema {
869            Ok(_) => panic!("from_schema_file should have failed"),
870            Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
871            _ => panic!("Unexpected error from from_schema_file"),
872        }
873    }
874
875    // Trivial cycle in action hierarchy
876    // view_photo -> view_photo
877    #[test]
878    fn test_from_schema_file_action_cycle1() {
879        let src = json!(
880        {
881            "entityTypes": {},
882            "actions": {
883                "view_photo": {
884                    "memberOf": [ {"id": "view_photo"} ]
885                }
886            }
887        });
888        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
889        let schema: Result<ValidatorSchema> = schema_file.try_into();
890        assert_matches!(
891            schema,
892            Err(SchemaError::CycleInActionHierarchy(euid)) => {
893                assert_eq!(euid, r#"Action::"view_photo""#.parse().unwrap());
894            }
895        )
896    }
897
898    // Slightly more complex cycle in action hierarchy
899    // view_photo -> edit_photo -> delete_photo -> view_photo
900    #[test]
901    fn test_from_schema_file_action_cycle2() {
902        let src = json!(
903        {
904            "entityTypes": {},
905            "actions": {
906                "view_photo": {
907                    "memberOf": [ {"id": "edit_photo"} ]
908                },
909                "edit_photo": {
910                    "memberOf": [ {"id": "delete_photo"} ]
911                },
912                "delete_photo": {
913                    "memberOf": [ {"id": "view_photo"} ]
914                },
915                "other_action": {
916                    "memberOf": [ {"id": "edit_photo"} ]
917                }
918            }
919        });
920        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
921        let schema: Result<ValidatorSchema> = schema_file.try_into();
922        assert_matches!(
923            schema,
924            // The exact action reported as being in the cycle isn't deterministic.
925            Err(SchemaError::CycleInActionHierarchy(_)),
926        )
927    }
928
929    #[test]
930    fn namespaced_schema() {
931        let src = r#"
932        { "N::S": {
933            "entityTypes": {
934                "User": {},
935                "Photo": {}
936            },
937            "actions": {
938                "view_photo": {
939                    "appliesTo": {
940                        "principalTypes": ["User"],
941                        "resourceTypes": ["Photo"]
942                    }
943                }
944            }
945        } }
946        "#;
947        let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
948        let schema: ValidatorSchema = schema_file
949            .try_into()
950            .expect("Namespaced schema failed to convert.");
951        dbg!(&schema);
952        let user_entity_type = &"N::S::User"
953            .parse()
954            .expect("Namespaced entity type should have parsed");
955        let photo_entity_type = &"N::S::Photo"
956            .parse()
957            .expect("Namespaced entity type should have parsed");
958        assert!(
959            schema.entity_types.contains_key(user_entity_type),
960            "Expected and entity type User."
961        );
962        assert!(
963            schema.entity_types.contains_key(photo_entity_type),
964            "Expected an entity type Photo."
965        );
966        assert_eq!(
967            schema.entity_types.len(),
968            2,
969            "Expected exactly 2 entity types."
970        );
971        assert!(
972            schema.action_ids.contains_key(
973                &"N::S::Action::\"view_photo\""
974                    .parse()
975                    .expect("Namespaced action should have parsed")
976            ),
977            "Expected an action \"view_photo\"."
978        );
979        assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
980
981        let apply_spec = &schema
982            .action_ids
983            .values()
984            .next()
985            .expect("Expected Action")
986            .applies_to;
987        assert_eq!(
988            apply_spec.applicable_principal_types().collect::<Vec<_>>(),
989            vec![&EntityType::Specified(user_entity_type.clone())]
990        );
991        assert_eq!(
992            apply_spec.applicable_resource_types().collect::<Vec<_>>(),
993            vec![&EntityType::Specified(photo_entity_type.clone())]
994        );
995    }
996
997    #[test]
998    fn cant_use_namespace_in_entity_type() {
999        let src = r#"
1000        {
1001            "entityTypes": { "NS::User": {} },
1002            "actions": {}
1003        }
1004        "#;
1005        let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
1006        assert!(
1007            matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::ParseEntityType(_))),
1008            "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
1009    }
1010
1011    #[test]
1012    fn entity_attribute_entity_type_with_namespace() {
1013        let schema_json: SchemaFragment = serde_json::from_str(
1014            r#"
1015            {"A::B": {
1016                "entityTypes": {
1017                    "Foo": {
1018                        "shape": {
1019                            "type": "Record",
1020                            "attributes": {
1021                                "name": { "type": "Entity", "name": "C::D::Foo" }
1022                            }
1023                        }
1024                    }
1025                },
1026                "actions": {}
1027              }}
1028            "#,
1029        )
1030        .expect("Expected valid schema");
1031
1032        let schema: Result<ValidatorSchema> = schema_json.try_into();
1033        match schema {
1034            Err(SchemaError::UndeclaredEntityTypes(tys)) => {
1035                assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
1036            }
1037            _ => panic!("Schema construction should have failed due to undeclared entity type."),
1038        }
1039    }
1040
1041    #[test]
1042    fn entity_attribute_entity_type_with_declared_namespace() {
1043        let schema_json: SchemaFragment = serde_json::from_str(
1044            r#"
1045            {"A::B": {
1046                "entityTypes": {
1047                    "Foo": {
1048                        "shape": {
1049                            "type": "Record",
1050                            "attributes": {
1051                                "name": { "type": "Entity", "name": "A::B::Foo" }
1052                            }
1053                        }
1054                    }
1055                },
1056                "actions": {}
1057              }}
1058            "#,
1059        )
1060        .expect("Expected valid schema");
1061
1062        let schema: ValidatorSchema = schema_json
1063            .try_into()
1064            .expect("Expected schema to construct without error.");
1065
1066        let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
1067        let foo_type = schema
1068            .entity_types
1069            .get(&foo_name)
1070            .expect("Expected to find entity");
1071        let name_type = foo_type
1072            .attr("name")
1073            .expect("Expected attribute name")
1074            .attr_type
1075            .clone();
1076        let expected_name_type = Type::named_entity_reference(foo_name);
1077        assert_eq!(name_type, expected_name_type);
1078    }
1079
1080    #[test]
1081    fn cannot_declare_action_type_when_prohibited() {
1082        let schema_json: NamespaceDefinition = serde_json::from_str(
1083            r#"
1084            {
1085                "entityTypes": { "Action": {} },
1086                "actions": {}
1087              }
1088            "#,
1089        )
1090        .expect("Expected valid schema");
1091
1092        let schema: Result<ValidatorSchema> = schema_json.try_into();
1093        assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
1094    }
1095
1096    #[test]
1097    fn can_declare_other_type_when_action_type_prohibited() {
1098        let schema_json: NamespaceDefinition = serde_json::from_str(
1099            r#"
1100            {
1101                "entityTypes": { "Foo": { } },
1102                "actions": {}
1103              }
1104            "#,
1105        )
1106        .expect("Expected valid schema");
1107
1108        TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
1109    }
1110
1111    #[test]
1112    fn cannot_declare_action_in_group_when_prohibited() {
1113        let schema_json: SchemaFragment = serde_json::from_str(
1114            r#"
1115            {"": {
1116                "entityTypes": {},
1117                "actions": {
1118                    "universe": { },
1119                    "view_photo": {
1120                        "attributes": {"id": "universe"}
1121                    },
1122                    "edit_photo": {
1123                        "attributes": {"id": "universe"}
1124                    },
1125                    "delete_photo": {
1126                        "attributes": {"id": "universe"}
1127                    }
1128                }
1129              }}
1130            "#,
1131        )
1132        .expect("Expected valid schema");
1133
1134        let schema = ValidatorSchemaFragment::from_schema_fragment(
1135            schema_json,
1136            ActionBehavior::ProhibitAttributes,
1137            Extensions::all_available(),
1138        );
1139        match schema {
1140            Err(SchemaError::UnsupportedFeature(UnsupportedFeature::ActionAttributes(actions))) => {
1141                assert_eq!(
1142                    actions.into_iter().collect::<HashSet<_>>(),
1143                    HashSet::from([
1144                        "view_photo".to_string(),
1145                        "edit_photo".to_string(),
1146                        "delete_photo".to_string(),
1147                    ])
1148                )
1149            }
1150            _ => panic!("Did not see expected error."),
1151        }
1152    }
1153
1154    #[test]
1155    fn test_entity_type_no_namespace() {
1156        let src = json!({"type": "Entity", "name": "Foo"});
1157        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1158        assert_eq!(
1159            schema_ty,
1160            SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
1161        );
1162        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1163            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1164            schema_ty,
1165        )
1166        .expect("Error converting schema type to type.")
1167        .resolve_type_defs(&HashMap::new())
1168        .unwrap();
1169        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1170    }
1171
1172    #[test]
1173    fn test_entity_type_namespace() {
1174        let src = json!({"type": "Entity", "name": "NS::Foo"});
1175        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1176        assert_eq!(
1177            schema_ty,
1178            SchemaType::Type(SchemaTypeVariant::Entity {
1179                name: "NS::Foo".into()
1180            })
1181        );
1182        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
1183            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1184            schema_ty,
1185        )
1186        .expect("Error converting schema type to type.")
1187        .resolve_type_defs(&HashMap::new())
1188        .unwrap();
1189        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
1190    }
1191
1192    #[test]
1193    fn test_entity_type_namespace_parse_error() {
1194        let src = json!({"type": "Entity", "name": "::Foo"});
1195        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1196        assert_eq!(
1197            schema_ty,
1198            SchemaType::Type(SchemaTypeVariant::Entity {
1199                name: "::Foo".into()
1200            })
1201        );
1202        match ValidatorNamespaceDef::try_schema_type_into_validator_type(
1203            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
1204            schema_ty,
1205        ) {
1206            Err(SchemaError::ParseEntityType(_)) => (),
1207            _ => panic!("Did not see expected entity type parse error."),
1208        }
1209    }
1210
1211    #[test]
1212    fn schema_type_record_is_validator_type_record() {
1213        let src = json!({"type": "Record", "attributes": {}});
1214        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
1215        assert_eq!(
1216            schema_ty,
1217            SchemaType::Type(SchemaTypeVariant::Record {
1218                attributes: BTreeMap::new(),
1219                additional_attributes: false,
1220            }),
1221        );
1222        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
1223            .expect("Error converting schema type to type.")
1224            .resolve_type_defs(&HashMap::new())
1225            .unwrap();
1226        assert_eq!(ty, Type::closed_record_with_attributes(None));
1227    }
1228
1229    #[test]
1230    fn get_namespaces() {
1231        let fragment: SchemaFragment = serde_json::from_value(json!({
1232            "Foo::Bar::Baz": {
1233                "entityTypes": {},
1234                "actions": {}
1235            },
1236            "Foo": {
1237                "entityTypes": {},
1238                "actions": {}
1239            },
1240            "Bar": {
1241                "entityTypes": {},
1242                "actions": {}
1243            },
1244        }))
1245        .unwrap();
1246
1247        let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
1248        assert_eq!(
1249            schema_fragment
1250                .0
1251                .iter()
1252                .map(|f| f.namespace())
1253                .collect::<HashSet<_>>(),
1254            HashSet::from([
1255                &Some("Foo::Bar::Baz".parse().unwrap()),
1256                &Some("Foo".parse().unwrap()),
1257                &Some("Bar".parse().unwrap())
1258            ])
1259        );
1260    }
1261
1262    #[test]
1263    fn schema_no_fragments() {
1264        let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
1265        assert!(schema.entity_types.is_empty());
1266        assert!(schema.action_ids.is_empty());
1267    }
1268
1269    #[test]
1270    fn same_action_different_namespace() {
1271        let fragment: SchemaFragment = serde_json::from_value(json!({
1272            "Foo::Bar": {
1273                "entityTypes": {},
1274                "actions": {
1275                    "Baz": {}
1276                }
1277            },
1278            "Bar::Foo": {
1279                "entityTypes": {},
1280                "actions": {
1281                    "Baz": { }
1282                }
1283            },
1284            "Biz": {
1285                "entityTypes": {},
1286                "actions": {
1287                    "Baz": { }
1288                }
1289            }
1290        }))
1291        .unwrap();
1292
1293        let schema: ValidatorSchema = fragment.try_into().unwrap();
1294        assert!(schema
1295            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1296            .is_some());
1297        assert!(schema
1298            .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
1299            .is_some());
1300        assert!(schema
1301            .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
1302            .is_some());
1303    }
1304
1305    #[test]
1306    fn same_type_different_namespace() {
1307        let fragment: SchemaFragment = serde_json::from_value(json!({
1308            "Foo::Bar": {
1309                "entityTypes": {"Baz" : {}},
1310                "actions": { }
1311            },
1312            "Bar::Foo": {
1313                "entityTypes": {"Baz" : {}},
1314                "actions": { }
1315            },
1316            "Biz": {
1317                "entityTypes": {"Baz" : {}},
1318                "actions": { }
1319            }
1320        }))
1321        .unwrap();
1322        let schema: ValidatorSchema = fragment.try_into().unwrap();
1323
1324        assert!(schema
1325            .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
1326            .is_some());
1327        assert!(schema
1328            .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
1329            .is_some());
1330        assert!(schema
1331            .get_entity_type(&"Biz::Baz".parse().unwrap())
1332            .is_some());
1333    }
1334
1335    #[test]
1336    fn member_of_different_namespace() {
1337        let fragment: SchemaFragment = serde_json::from_value(json!({
1338            "Bar": {
1339                "entityTypes": {
1340                    "Baz": {
1341                        "memberOfTypes": ["Foo::Buz"]
1342                    }
1343                },
1344                "actions": {}
1345            },
1346            "Foo": {
1347                "entityTypes": { "Buz": {} },
1348                "actions": { }
1349            }
1350        }))
1351        .unwrap();
1352        let schema: ValidatorSchema = fragment.try_into().unwrap();
1353
1354        let buz = schema
1355            .get_entity_type(&"Foo::Buz".parse().unwrap())
1356            .unwrap();
1357        assert_eq!(
1358            buz.descendants,
1359            HashSet::from(["Bar::Baz".parse().unwrap()])
1360        );
1361    }
1362
1363    #[test]
1364    fn attribute_different_namespace() {
1365        let fragment: SchemaFragment = serde_json::from_value(json!({
1366            "Bar": {
1367                "entityTypes": {
1368                    "Baz": {
1369                        "shape": {
1370                            "type": "Record",
1371                            "attributes": {
1372                                "fiz": {
1373                                    "type": "Entity",
1374                                    "name": "Foo::Buz"
1375                                }
1376                            }
1377                        }
1378                    }
1379                },
1380                "actions": {}
1381            },
1382            "Foo": {
1383                "entityTypes": { "Buz": {} },
1384                "actions": { }
1385            }
1386        }))
1387        .unwrap();
1388
1389        let schema: ValidatorSchema = fragment.try_into().unwrap();
1390        let baz = schema
1391            .get_entity_type(&"Bar::Baz".parse().unwrap())
1392            .unwrap();
1393        assert_eq!(
1394            baz.attr("fiz").unwrap().attr_type,
1395            Type::named_entity_reference_from_str("Foo::Buz"),
1396        );
1397    }
1398
1399    #[test]
1400    fn applies_to_different_namespace() {
1401        let fragment: SchemaFragment = serde_json::from_value(json!({
1402            "Foo::Bar": {
1403                "entityTypes": { },
1404                "actions": {
1405                    "Baz": {
1406                        "appliesTo": {
1407                            "principalTypes": [ "Fiz::Buz" ],
1408                            "resourceTypes": [ "Fiz::Baz" ],
1409                        }
1410                    }
1411                }
1412            },
1413            "Fiz": {
1414                "entityTypes": {
1415                    "Buz": {},
1416                    "Baz": {}
1417                },
1418                "actions": { }
1419            }
1420        }))
1421        .unwrap();
1422        let schema: ValidatorSchema = fragment.try_into().unwrap();
1423
1424        let baz = schema
1425            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
1426            .unwrap();
1427        assert_eq!(
1428            baz.applies_to
1429                .applicable_principal_types()
1430                .collect::<HashSet<_>>(),
1431            HashSet::from([&EntityType::Specified("Fiz::Buz".parse().unwrap())])
1432        );
1433        assert_eq!(
1434            baz.applies_to
1435                .applicable_resource_types()
1436                .collect::<HashSet<_>>(),
1437            HashSet::from([&EntityType::Specified("Fiz::Baz".parse().unwrap())])
1438        );
1439    }
1440
1441    #[test]
1442    fn simple_defined_type() {
1443        let fragment: SchemaFragment = serde_json::from_value(json!({
1444            "": {
1445                "commonTypes": {
1446                    "MyLong": {"type": "Long"}
1447                },
1448                "entityTypes": {
1449                    "User": {
1450                        "shape": {
1451                            "type": "Record",
1452                            "attributes": {
1453                                "a": {"type": "MyLong"}
1454                            }
1455                        }
1456                    }
1457                },
1458                "actions": {}
1459            }
1460        }))
1461        .unwrap();
1462        let schema: ValidatorSchema = fragment.try_into().unwrap();
1463        assert_eq!(
1464            schema.entity_types.iter().next().unwrap().1.attributes,
1465            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1466        );
1467    }
1468
1469    #[test]
1470    fn defined_record_as_attrs() {
1471        let fragment: SchemaFragment = serde_json::from_value(json!({
1472            "": {
1473                "commonTypes": {
1474                    "MyRecord": {
1475                        "type": "Record",
1476                        "attributes":  {
1477                            "a": {"type": "Long"}
1478                        }
1479                    }
1480                },
1481                "entityTypes": {
1482                    "User": { "shape": { "type": "MyRecord", } }
1483                },
1484                "actions": {}
1485            }
1486        }))
1487        .unwrap();
1488        let schema: ValidatorSchema = fragment.try_into().unwrap();
1489        assert_eq!(
1490            schema.entity_types.iter().next().unwrap().1.attributes,
1491            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1492        );
1493    }
1494
1495    #[test]
1496    fn cross_namespace_type() {
1497        let fragment: SchemaFragment = serde_json::from_value(json!({
1498            "A": {
1499                "commonTypes": {
1500                    "MyLong": {"type": "Long"}
1501                },
1502                "entityTypes": { },
1503                "actions": {}
1504            },
1505            "B": {
1506                "entityTypes": {
1507                    "User": {
1508                        "shape": {
1509                            "type": "Record",
1510                            "attributes": {
1511                                "a": {"type": "A::MyLong"}
1512                            }
1513                        }
1514                    }
1515                },
1516                "actions": {}
1517            }
1518        }))
1519        .unwrap();
1520        let schema: ValidatorSchema = fragment.try_into().unwrap();
1521        assert_eq!(
1522            schema.entity_types.iter().next().unwrap().1.attributes,
1523            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1524        );
1525    }
1526
1527    #[test]
1528    fn cross_fragment_type() {
1529        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1530            "A": {
1531                "commonTypes": {
1532                    "MyLong": {"type": "Long"}
1533                },
1534                "entityTypes": { },
1535                "actions": {}
1536            }
1537        }))
1538        .unwrap()
1539        .try_into()
1540        .unwrap();
1541        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1542            "A": {
1543                "entityTypes": {
1544                    "User": {
1545                        "shape": {
1546                            "type": "Record",
1547                            "attributes": {
1548                                "a": {"type": "MyLong"}
1549                            }
1550                        }
1551                    }
1552                },
1553                "actions": {}
1554            }
1555        }))
1556        .unwrap()
1557        .try_into()
1558        .unwrap();
1559        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
1560
1561        assert_eq!(
1562            schema.entity_types.iter().next().unwrap().1.attributes,
1563            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
1564        );
1565    }
1566
1567    #[test]
1568    fn cross_fragment_duplicate_type() {
1569        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1570            "A": {
1571                "commonTypes": {
1572                    "MyLong": {"type": "Long"}
1573                },
1574                "entityTypes": {},
1575                "actions": {}
1576            }
1577        }))
1578        .unwrap()
1579        .try_into()
1580        .unwrap();
1581        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
1582            "A": {
1583                "commonTypes": {
1584                    "MyLong": {"type": "Long"}
1585                },
1586                "entityTypes": {},
1587                "actions": {}
1588            }
1589        }))
1590        .unwrap()
1591        .try_into()
1592        .unwrap();
1593
1594        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]);
1595
1596        match schema {
1597            Err(SchemaError::DuplicateCommonType(s)) if s.contains("A::MyLong") => (),
1598            _ => panic!("should have errored because schema fragments have duplicate types"),
1599        };
1600    }
1601
1602    #[test]
1603    fn undeclared_type_in_attr() {
1604        let fragment: SchemaFragment = serde_json::from_value(json!({
1605            "": {
1606                "commonTypes": { },
1607                "entityTypes": {
1608                    "User": {
1609                        "shape": {
1610                            "type": "Record",
1611                            "attributes": {
1612                                "a": {"type": "MyLong"}
1613                            }
1614                        }
1615                    }
1616                },
1617                "actions": {}
1618            }
1619        }))
1620        .unwrap();
1621        match TryInto::<ValidatorSchema>::try_into(fragment) {
1622            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1623            s => panic!(
1624                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1625                s
1626            ),
1627        }
1628    }
1629
1630    #[test]
1631    fn undeclared_type_in_type_def() {
1632        let fragment: SchemaFragment = serde_json::from_value(json!({
1633            "": {
1634                "commonTypes": {
1635                    "a": { "type": "b" }
1636                },
1637                "entityTypes": { },
1638                "actions": {}
1639            }
1640        }))
1641        .unwrap();
1642        match TryInto::<ValidatorSchema>::try_into(fragment) {
1643            Err(SchemaError::UndeclaredCommonTypes(_)) => (),
1644            s => panic!(
1645                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
1646                s
1647            ),
1648        }
1649    }
1650
1651    #[test]
1652    fn shape_not_record() {
1653        let fragment: SchemaFragment = serde_json::from_value(json!({
1654            "": {
1655                "commonTypes": {
1656                    "MyLong": { "type": "Long" }
1657                },
1658                "entityTypes": {
1659                    "User": {
1660                        "shape": { "type": "MyLong" }
1661                    }
1662                },
1663                "actions": {}
1664            }
1665        }))
1666        .unwrap();
1667        match TryInto::<ValidatorSchema>::try_into(fragment) {
1668            Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
1669            s => panic!(
1670                "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
1671                s
1672            ),
1673        }
1674    }
1675
1676    /// This test checks for regressions on (adapted versions of) the examples
1677    /// mentioned in the thread at
1678    /// [cedar#134](https://github.com/cedar-policy/cedar/pull/134)
1679    #[test]
1680    fn counterexamples_from_cedar_134() {
1681        // non-normalized entity type name
1682        let bad1 = json!({
1683            "": {
1684                "entityTypes": {
1685                    "User // comment": {
1686                        "memberOfTypes": [
1687                            "UserGroup"
1688                        ]
1689                    },
1690                    "User": {
1691                        "memberOfTypes": [
1692                            "UserGroup"
1693                        ]
1694                    },
1695                    "UserGroup": {}
1696                },
1697                "actions": {}
1698            }
1699        });
1700        let fragment = serde_json::from_value::<SchemaFragment>(bad1)
1701            .expect("constructing the fragment itself should succeed"); // should this fail in the future?
1702        let err = ValidatorSchema::try_from(fragment)
1703            .expect_err("should error due to invalid entity type name");
1704        let expected_err = ParseError::ToAST(ToASTError::NonNormalizedString {
1705            kind: "Id",
1706            src: "User // comment".to_string(),
1707            normalized_src: "User".to_string(),
1708        })
1709        .into();
1710
1711        match err {
1712            SchemaError::ParseEntityType(parse_error) => assert_eq!(parse_error, expected_err),
1713            err => panic!("Incorrect error {err}"),
1714        }
1715
1716        // non-normalized schema namespace
1717        let bad2 = json!({
1718            "ABC     :: //comment \n XYZ  ": {
1719                "entityTypes": {
1720                    "User": {
1721                        "memberOfTypes": []
1722                    }
1723                },
1724                "actions": {}
1725            }
1726        });
1727        let fragment = serde_json::from_value::<SchemaFragment>(bad2)
1728            .expect("constructing the fragment itself should succeed"); // should this fail in the future?
1729        let err = ValidatorSchema::try_from(fragment)
1730            .expect_err("should error due to invalid schema namespace");
1731        let expected_err = ParseError::ToAST(ToASTError::NonNormalizedString {
1732            kind: "Name",
1733            src: "ABC     :: //comment \n XYZ  ".to_string(),
1734            normalized_src: "ABC::XYZ".to_string(),
1735        })
1736        .into();
1737        match err {
1738            SchemaError::ParseNamespace(parse_error) => assert_eq!(parse_error, expected_err),
1739            err => panic!("Incorrect error {:?}", err),
1740        };
1741    }
1742
1743    #[test]
1744    fn simple_action_entity() {
1745        let src = json!(
1746        {
1747            "entityTypes": { },
1748            "actions": {
1749                "view_photo": { },
1750            }
1751        });
1752
1753        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1754        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1755        let actions = schema.action_entities().expect("Entity Construct Error");
1756
1757        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1758        let view_photo = actions.entity(&action_uid);
1759        assert_eq!(
1760            view_photo.unwrap(),
1761            &Entity::new_with_attr_partial_value(action_uid, HashMap::new(), HashSet::new())
1762        );
1763    }
1764
1765    #[test]
1766    fn action_entity_hierarchy() {
1767        let src = json!(
1768        {
1769            "entityTypes": { },
1770            "actions": {
1771                "read": {},
1772                "view": {
1773                    "memberOf": [{"id": "read"}]
1774                },
1775                "view_photo": {
1776                    "memberOf": [{"id": "view"}]
1777                },
1778            }
1779        });
1780
1781        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1782        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1783        let actions = schema.action_entities().expect("Entity Construct Error");
1784
1785        let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1786        let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
1787        let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
1788
1789        let view_photo_entity = actions.entity(&view_photo_uid);
1790        assert_eq!(
1791            view_photo_entity.unwrap(),
1792            &Entity::new_with_attr_partial_value(
1793                view_photo_uid,
1794                HashMap::new(),
1795                HashSet::from([view_uid.clone(), read_uid.clone()])
1796            )
1797        );
1798
1799        let view_entity = actions.entity(&view_uid);
1800        assert_eq!(
1801            view_entity.unwrap(),
1802            &Entity::new_with_attr_partial_value(
1803                view_uid,
1804                HashMap::new(),
1805                HashSet::from([read_uid.clone()])
1806            )
1807        );
1808
1809        let read_entity = actions.entity(&read_uid);
1810        assert_eq!(
1811            read_entity.unwrap(),
1812            &Entity::new_with_attr_partial_value(read_uid, HashMap::new(), HashSet::new())
1813        );
1814    }
1815
1816    #[test]
1817    fn action_entity_attribute() {
1818        let src = json!(
1819        {
1820            "entityTypes": { },
1821            "actions": {
1822                "view_photo": {
1823                    "attributes": { "attr": "foo" }
1824                },
1825            }
1826        });
1827
1828        let schema_file: NamespaceDefinitionWithActionAttributes =
1829            serde_json::from_value(src).expect("Parse Error");
1830        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
1831        let actions = schema.action_entities().expect("Entity Construct Error");
1832
1833        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
1834        let view_photo = actions.entity(&action_uid);
1835        assert_eq!(
1836            view_photo.unwrap(),
1837            &Entity::new(
1838                action_uid,
1839                HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
1840                HashSet::new(),
1841                &Extensions::none(),
1842            )
1843            .unwrap(),
1844        );
1845    }
1846
1847    #[test]
1848    fn test_action_namespace_inference_multi_success() {
1849        let src = json!({
1850            "Foo" : {
1851                "entityTypes" : {},
1852                "actions" : {
1853                    "read" : {}
1854                }
1855            },
1856            "ExampleCo::Personnel" : {
1857                "entityTypes" : {},
1858                "actions" : {
1859                    "viewPhoto" : {
1860                        "memberOf" : [
1861                            {
1862                                "id" : "read",
1863                                "type" : "Foo::Action"
1864                            }
1865                        ]
1866                    }
1867                }
1868            },
1869        });
1870        let schema_fragment =
1871            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1872        let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
1873        let view_photo = schema
1874            .action_entities_iter()
1875            .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
1876            .unwrap();
1877        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
1878        let read = ancestors[0];
1879        assert_eq!(read.eid().to_string(), "read");
1880        assert_eq!(read.entity_type().to_string(), "Foo::Action");
1881    }
1882
1883    #[test]
1884    fn test_action_namespace_inference_multi() {
1885        let src = json!({
1886            "ExampleCo::Personnel::Foo" : {
1887                "entityTypes" : {},
1888                "actions" : {
1889                    "read" : {}
1890                }
1891            },
1892            "ExampleCo::Personnel" : {
1893                "entityTypes" : {},
1894                "actions" : {
1895                    "viewPhoto" : {
1896                        "memberOf" : [
1897                            {
1898                                "id" : "read",
1899                                "type" : "Foo::Action"
1900                            }
1901                        ]
1902                    }
1903                }
1904            },
1905        });
1906        let schema_fragment =
1907            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1908        let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
1909        schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
1910    }
1911
1912    #[test]
1913    fn test_action_namespace_inference() {
1914        let src = json!({
1915            "ExampleCo::Personnel" : {
1916                "entityTypes" : { },
1917                "actions" : {
1918                    "read" : {},
1919                    "viewPhoto" : {
1920                        "memberOf" : [
1921                            {
1922                                "id" :  "read",
1923                                "type" : "Action"
1924                            }
1925                        ]
1926                    }
1927                }
1928            }
1929        });
1930        let schema_fragment =
1931            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
1932        let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
1933        let view_photo = schema
1934            .action_entities_iter()
1935            .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
1936            .unwrap();
1937        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
1938        let read = ancestors[0];
1939        assert_eq!(read.eid().to_string(), "read");
1940        assert_eq!(
1941            read.entity_type().to_string(),
1942            "ExampleCo::Personnel::Action"
1943        );
1944    }
1945}