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};
24use std::sync::Arc;
25
26use cedar_policy_core::{
27    ast::{Eid, Entity, EntityType, EntityUID, Id, Name, RestrictedExpr},
28    entities::{Entities, JSONValue, TCComputation},
29    parser::err::ParseError,
30    transitive_closure::{compute_tc, TCNode},
31    FromNormalizedStr,
32};
33use serde::{Deserialize, Serialize};
34use serde_with::serde_as;
35use smol_str::SmolStr;
36
37use crate::{
38    schema_file_format,
39    types::{AttributeType, Attributes, EntityRecordKind, Type},
40    ActionEntityUID, ActionType, SchemaFragment, SchemaType, SchemaTypeVariant, TypeOfAttribute,
41    SCHEMA_TYPE_VARIANT_TAGS,
42};
43
44use super::err::*;
45use super::NamespaceDefinition;
46
47/// The current schema format specification does not include multiple action entity
48/// types. All action entities are required to use a single `Action` entity
49/// type. However, the action entity type may be namespaced, so an action entity
50/// may have a fully qualified entity type `My::Namespace::Action`.
51pub(crate) static ACTION_ENTITY_TYPE: &str = "Action";
52
53/// Return true when an entity type is an action entity type. This compares the
54/// base name for the type, so this will return true for any entity type named
55/// `Action` regardless of namespaces.
56pub(crate) fn is_action_entity_type(ty: &Name) -> bool {
57    ty.basename().as_ref() == ACTION_ENTITY_TYPE
58}
59
60// We do not have a dafny model for action attributes, so we disable them by defualt.
61#[derive(Eq, PartialEq, Copy, Clone, Default)]
62pub enum ActionBehavior {
63    /// Action entities cannot have attributes. Attempting to declare attributes
64    /// will result in a error when constructing the schema.
65    #[default]
66    ProhibitAttributes,
67    /// Action entities may have attributes.
68    PermitAttributes,
69}
70
71/// A single namespace definition from the schema json processed into a form
72/// which is closer to that used by the validator. The processing includes
73/// detection of some errors, for example, parse errors in entity type names or
74/// entity type which are declared multiple times. This does not detect
75/// references to undeclared entity types because any entity type may be
76/// declared in a different fragment that will only be known about when building
77/// the complete `ValidatorSchema`.
78#[derive(Debug)]
79pub struct ValidatorNamespaceDef {
80    /// The namespace declared for the schema fragment. We track a namespace for
81    /// fragments because they have at most one namespace that is applied
82    /// everywhere. It would be less useful to track all namespaces for a
83    /// complete schema.
84    namespace: Option<Name>,
85    /// Preprocessed common type definitions which can be used to define entity
86    /// type attributes and action contexts.
87    type_defs: TypeDefs,
88    /// The preprocessed entity type declarations from the schema fragment json.
89    entity_types: EntityTypesDef,
90    /// The preprocessed action declarations from the schema fragment json.
91    actions: ActionsDef,
92}
93
94/// Holds a map from `Name`s of common type definitions to their corresponding
95/// `Type`.
96#[derive(Debug)]
97pub struct TypeDefs {
98    type_defs: HashMap<Name, Type>,
99}
100
101/// Entity type declarations held in a `ValidatorNamespaceDef`. Entity type
102/// parents and attributes may reference undeclared entity types.
103#[derive(Debug)]
104pub struct EntityTypesDef {
105    entity_types: HashMap<Name, EntityTypeFragment>,
106}
107
108/// Defines an EntityType where we have not resolved typedefs occurring in the
109/// attributes or verified that the parent entity types and entity types
110/// occurring in attributes are defined.
111#[derive(Debug)]
112pub struct EntityTypeFragment {
113    /// The attributes record type for this entity type.  The type is wrapped in
114    /// a `WithUnresolvedTypeDefs` because it may contain typedefs which are not
115    /// defined in this schema fragment. All entity type `Name` keys in this map
116    /// are declared in this schema fragment.
117    attributes: WithUnresolvedTypeDefs<Type>,
118    /// The direct parent entity types for this entity type come from the
119    /// `memberOfTypes` list. These types might be declared in a different
120    /// namespace, so we will check if they are declared in any fragment when
121    /// constructing a `ValidatorSchema`.
122    parents: HashSet<Name>,
123}
124
125/// Action declarations held in a `ValidatorNamespaceDef`. Entity types
126/// referenced here do not need to be declared in the schema.
127#[derive(Debug)]
128pub struct ActionsDef {
129    actions: HashMap<EntityUID, ActionFragment>,
130}
131
132#[derive(Debug)]
133pub struct ActionFragment {
134    /// The type of the context record for this actions. The types is wrapped in
135    /// a `WithUnresolvedTypeDefs` because it may refer to common types which
136    /// are not defined in this fragment.
137    context: WithUnresolvedTypeDefs<Type>,
138    /// The principals and resources that an action can be applied to.
139    applies_to: ValidatorApplySpec,
140    /// The direct parent action entities for this action.
141    parents: HashSet<EntityUID>,
142    /// The types for the attributes defined for this actions entity.
143    attribute_types: Attributes,
144    /// The values for the attributes defined for this actions entity, stored
145    /// separately so that we can later extract use these values to construct
146    /// the actual `Entity` objects defined by the schema.
147    attributes: HashMap<SmolStr, RestrictedExpr>,
148}
149
150type ResolveFunc<T> = dyn FnOnce(&HashMap<Name, Type>) -> Result<T>;
151/// Represent a type that might be defined in terms of some type definitions
152/// which are not necessarily available in the current namespace.
153pub enum WithUnresolvedTypeDefs<T> {
154    WithUnresolved(Box<ResolveFunc<T>>),
155    WithoutUnresolved(T),
156}
157
158impl<T: 'static> WithUnresolvedTypeDefs<T> {
159    pub fn new(f: impl FnOnce(&HashMap<Name, Type>) -> Result<T> + 'static) -> Self {
160        Self::WithUnresolved(Box::new(f))
161    }
162
163    pub fn map<U: 'static>(self, f: impl FnOnce(T) -> U + 'static) -> WithUnresolvedTypeDefs<U> {
164        match self {
165            Self::WithUnresolved(_) => {
166                WithUnresolvedTypeDefs::new(|type_defs| self.resolve_type_defs(type_defs).map(f))
167            }
168            Self::WithoutUnresolved(v) => WithUnresolvedTypeDefs::WithoutUnresolved(f(v)),
169        }
170    }
171
172    /// Instantiate any names referencing types with the definition of the type
173    /// from the input HashMap.
174    pub fn resolve_type_defs(self, type_defs: &HashMap<Name, Type>) -> Result<T> {
175        match self {
176            WithUnresolvedTypeDefs::WithUnresolved(f) => f(type_defs),
177            WithUnresolvedTypeDefs::WithoutUnresolved(v) => Ok(v),
178        }
179    }
180}
181
182impl<T: 'static> From<T> for WithUnresolvedTypeDefs<T> {
183    fn from(value: T) -> Self {
184        Self::WithoutUnresolved(value)
185    }
186}
187
188impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedTypeDefs<T> {
189    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
190        match self {
191            WithUnresolvedTypeDefs::WithUnresolved(_) => f.debug_tuple("WithUnresolved").finish(),
192            WithUnresolvedTypeDefs::WithoutUnresolved(v) => {
193                f.debug_tuple("WithoutUnresolved").field(v).finish()
194            }
195        }
196    }
197}
198
199impl TryInto<ValidatorNamespaceDef> for NamespaceDefinition {
200    type Error = SchemaError;
201
202    fn try_into(self) -> Result<ValidatorNamespaceDef> {
203        ValidatorNamespaceDef::from_namespace_definition(None, self, ActionBehavior::default())
204    }
205}
206
207impl ValidatorNamespaceDef {
208    // We need to treat this as if it had `pub(crate)` visibility to avoid sharing
209    // the file format. However, our fuzzing library currently needs it to be public.
210    /// Construct a new `ValidatorSchema` from the underlying `SchemaFragment`.
211    pub fn from_namespace_definition(
212        namespace: Option<SmolStr>,
213        namespace_def: NamespaceDefinition,
214        action_behavior: ActionBehavior,
215    ) -> Result<ValidatorNamespaceDef> {
216        // Check that each entity types and action is only declared once.
217        let mut e_types_ids: HashSet<SmolStr> = HashSet::new();
218        for name in namespace_def.entity_types.keys() {
219            if !e_types_ids.insert(name.clone()) {
220                // insert returns false for duplicates
221                return Err(SchemaError::DuplicateEntityType(name.to_string()));
222            }
223        }
224        let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
225        for name in namespace_def.actions.keys() {
226            if !a_name_eids.insert(name.clone()) {
227                // insert returns false for duplicates
228                return Err(SchemaError::DuplicateAction(name.to_string()));
229            }
230        }
231
232        let schema_namespace = match namespace.as_deref() {
233            None => None,
234            Some("") => None, // we consider "" to be the same as the empty namespace for this purpose
235            Some(ns) => {
236                Some(Name::from_normalized_str(ns).map_err(SchemaError::NamespaceParseError)?)
237            }
238        };
239
240        // Return early with an error if actions cannot be in groups or have
241        // attributes, but the schema contains action groups or attributes.
242        Self::check_action_behavior(&namespace_def, action_behavior)?;
243
244        // Convert the type defs, actions and entity types from the schema file
245        // into the representation used by the validator.
246        let type_defs =
247            Self::build_type_defs(namespace_def.common_types, schema_namespace.as_ref())?;
248        let actions = Self::build_action_ids(namespace_def.actions, schema_namespace.as_ref())?;
249        let entity_types =
250            Self::build_entity_types(namespace_def.entity_types, schema_namespace.as_ref())?;
251
252        Ok(ValidatorNamespaceDef {
253            namespace: schema_namespace,
254            type_defs,
255            entity_types,
256            actions,
257        })
258    }
259
260    fn is_builtin_type_name(name: &SmolStr) -> bool {
261        SCHEMA_TYPE_VARIANT_TAGS
262            .iter()
263            .any(|type_name| name == type_name)
264    }
265
266    fn build_type_defs(
267        schema_file_type_def: HashMap<SmolStr, SchemaType>,
268        schema_namespace: Option<&Name>,
269    ) -> Result<TypeDefs> {
270        let type_defs = schema_file_type_def
271            .into_iter()
272            .map(|(name_str, schema_ty)| -> Result<_> {
273                if Self::is_builtin_type_name(&name_str) {
274                    return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
275                }
276                let name = Self::parse_unqualified_name_with_namespace(
277                    &name_str,
278                    schema_namespace.cloned(),
279                )
280                .map_err(SchemaError::CommonTypeParseError)?;
281                let ty = Self::try_schema_type_into_validator_type(schema_namespace, schema_ty)?
282                    .resolve_type_defs(&HashMap::new())?;
283                Ok((name, ty))
284            })
285            .collect::<Result<HashMap<_, _>>>()?;
286        Ok(TypeDefs { type_defs })
287    }
288
289    // Transform the schema data structures for entity types into the structures
290    // used internally by the validator. This is mostly accomplished by directly
291    // copying data between fields.
292    fn build_entity_types(
293        schema_files_types: HashMap<SmolStr, schema_file_format::EntityType>,
294        schema_namespace: Option<&Name>,
295    ) -> Result<EntityTypesDef> {
296        Ok(EntityTypesDef {
297            entity_types: schema_files_types
298                .into_iter()
299                .map(|(name_str, entity_type)| -> Result<_> {
300                    let name = Self::parse_unqualified_name_with_namespace(
301                        &name_str,
302                        schema_namespace.cloned(),
303                    )
304                    .map_err(SchemaError::EntityTypeParseError)?;
305
306                    let parents = entity_type
307                        .member_of_types
308                        .iter()
309                        .map(|parent| -> Result<_> {
310                            Self::parse_possibly_qualified_name_with_default_namespace(
311                                parent,
312                                schema_namespace,
313                            )
314                            .map_err(SchemaError::EntityTypeParseError)
315                        })
316                        .collect::<Result<HashSet<_>>>()?;
317
318                    let attributes = Self::try_schema_type_into_validator_type(
319                        schema_namespace,
320                        entity_type.shape.into_inner(),
321                    )?;
322
323                    Ok((
324                        name,
325                        EntityTypeFragment {
326                            attributes,
327                            parents,
328                        },
329                    ))
330                })
331                .collect::<Result<HashMap<_, _>>>()?,
332        })
333    }
334
335    // Helper to get types from JSONValues. Currently doesn't support all
336    // JSONValue types. Note: If this function is extended to cover move
337    // `JSONValue`s, we must update `convert_attr_jsonval_map_to_attributes` to
338    // handle errors that may occur when parsing these values. This will require
339    // a breaking change in the `SchemaError` type in the public API.
340    fn jsonval_to_type_helper(v: &JSONValue) -> Result<Type> {
341        match v {
342            JSONValue::Bool(_) => Ok(Type::primitive_boolean()),
343            JSONValue::Long(_) => Ok(Type::primitive_long()),
344            JSONValue::String(_) => Ok(Type::primitive_string()),
345            JSONValue::Record(r) => {
346                let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
347                for (k, v_prime) in r {
348                    let t = Self::jsonval_to_type_helper(v_prime);
349                    match t {
350                        Ok(ty) => required_attrs.insert(k.clone(), ty),
351                        Err(e) => return Err(e),
352                    };
353                }
354                Ok(Type::EntityOrRecord(EntityRecordKind::Record {
355                    attrs: Attributes::with_required_attributes(required_attrs),
356                }))
357            }
358            JSONValue::Set(v) => match v.get(0) {
359                //sets with elements of different types will be rejected elsewhere
360                None => Err(SchemaError::ActionEntityAttributeEmptySet),
361                Some(element) => {
362                    let element_type = Self::jsonval_to_type_helper(element);
363                    match element_type {
364                        Ok(t) => Ok(Type::Set {
365                            element_type: Some(Box::new(t)),
366                        }),
367                        Err(_) => element_type,
368                    }
369                }
370            },
371            _ => Err(SchemaError::ActionEntityAttributeUnsupportedType),
372        }
373    }
374
375    //Convert jsonval map to attributes
376    fn convert_attr_jsonval_map_to_attributes(
377        m: HashMap<SmolStr, JSONValue>,
378    ) -> Result<(Attributes, HashMap<SmolStr, RestrictedExpr>)> {
379        let mut attr_types: HashMap<SmolStr, Type> = HashMap::new();
380        let mut attr_values: HashMap<SmolStr, RestrictedExpr> = HashMap::new();
381
382        for (k, v) in m {
383            let t = Self::jsonval_to_type_helper(&v);
384            match t {
385                Ok(ty) => attr_types.insert(k.clone(), ty),
386                Err(e) => return Err(e),
387            };
388
389            // As an artifact of the limited `JSONValue` variants accepted by
390            // `Self::jsonval_to_type_helper`, we know that this function will
391            // never error. Also note that this is only ever executed when
392            // action attributes are enabled, but they cannot be enabled when
393            // using Cedar through the public API. This is fortunate because
394            // handling an error here would mean adding a new error variant to
395            // `SchemaError` in the public API, but we didn't make that enum
396            // `non_exhaustive`, so any new variants are a breaking change.
397            let e = v.into_expr().expect("`Self::jsonval_to_type_helper` will always return `Err` for a `JSONValue` that might make `into_expr` return `Err`");
398            attr_values.insert(k.clone(), e);
399        }
400        Ok((
401            Attributes::with_required_attributes(attr_types),
402            attr_values,
403        ))
404    }
405
406    // Transform the schema data structures for actions into the structures used
407    // internally by the validator. This is mostly accomplished by directly
408    // copying data between fields.
409    fn build_action_ids(
410        schema_file_actions: HashMap<SmolStr, ActionType>,
411        schema_namespace: Option<&Name>,
412    ) -> Result<ActionsDef> {
413        Ok(ActionsDef {
414            actions: schema_file_actions
415                .into_iter()
416                .map(|(action_id_str, action_type)| -> Result<_> {
417                    let action_id = Self::parse_action_id_with_namespace(
418                        &ActionEntityUID::default_type(action_id_str),
419                        schema_namespace,
420                    )?;
421
422                    let (principal_types, resource_types, context) = action_type
423                        .applies_to
424                        .map(|applies_to| {
425                            (
426                                applies_to.principal_types,
427                                applies_to.resource_types,
428                                applies_to.context,
429                            )
430                        })
431                        .unwrap_or_default();
432
433                    // Convert the entries in the `appliesTo` lists into sets of
434                    // `EntityTypes`. If one of the lists is `None` (absent from the
435                    // schema), then the specification is undefined.
436                    let applies_to = ValidatorApplySpec::new(
437                        Self::parse_apply_spec_type_list(principal_types, schema_namespace)?,
438                        Self::parse_apply_spec_type_list(resource_types, schema_namespace)?,
439                    );
440
441                    let context = Self::try_schema_type_into_validator_type(
442                        schema_namespace,
443                        context.into_inner(),
444                    )?;
445
446                    let parents = action_type
447                        .member_of
448                        .unwrap_or_default()
449                        .iter()
450                        .map(|parent| -> Result<_> {
451                            Self::parse_action_id_with_namespace(parent, schema_namespace)
452                        })
453                        .collect::<Result<HashSet<_>>>()?;
454
455                    let (attribute_types, attributes) =
456                        Self::convert_attr_jsonval_map_to_attributes(
457                            action_type.attributes.unwrap_or_default(),
458                        )?;
459
460                    Ok((
461                        action_id,
462                        ActionFragment {
463                            context,
464                            applies_to,
465                            parents,
466                            attribute_types,
467                            attributes,
468                        },
469                    ))
470                })
471                .collect::<Result<HashMap<_, _>>>()?,
472        })
473    }
474
475    // Check that `schema_file` uses actions in a way consistent with the
476    // specified `action_behavior`. When the behavior specifies that actions
477    // should not be used in groups and should not have attributes, then this
478    // function will return `Err` if it sees any action groups or attributes
479    // declared in the schema.
480    fn check_action_behavior(
481        schema_file: &NamespaceDefinition,
482        action_behavior: ActionBehavior,
483    ) -> Result<()> {
484        if schema_file
485            .entity_types
486            .iter()
487            // The `name` in an entity type declaration cannot be qualified
488            // with a namespace (it always implicitly takes the schema
489            // namespace), so we do this comparison directly.
490            .any(|(name, _)| name == ACTION_ENTITY_TYPE)
491        {
492            return Err(SchemaError::ActionEntityTypeDeclared);
493        }
494        if action_behavior == ActionBehavior::ProhibitAttributes {
495            let mut actions_with_attributes: Vec<String> = Vec::new();
496            for (name, a) in &schema_file.actions {
497                if a.attributes.is_some() {
498                    actions_with_attributes.push(name.to_string());
499                }
500            }
501            if !actions_with_attributes.is_empty() {
502                return Err(SchemaError::ActionEntityAttributes(actions_with_attributes));
503            }
504        }
505
506        Ok(())
507    }
508
509    /// Given the attributes for an entity type or action context as written in
510    /// a schema file, convert the types of the attributes into the `Type` data
511    /// structure used by the typechecker, and return the result as a map from
512    /// attribute name to type.
513    fn parse_record_attributes(
514        schema_namespace: Option<&Name>,
515        attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
516    ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
517        let attrs_with_type_defs = attrs
518            .into_iter()
519            .map(|(attr, ty)| -> Result<_> {
520                Ok((
521                    attr,
522                    (
523                        Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
524                        ty.required,
525                    ),
526                ))
527            })
528            .collect::<Result<Vec<_>>>()?;
529        Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
530            attrs_with_type_defs
531                .into_iter()
532                .map(|(s, (attr_ty, is_req))| {
533                    attr_ty
534                        .resolve_type_defs(typ_defs)
535                        .map(|ty| (s, AttributeType::new(ty, is_req)))
536                })
537                .collect::<Result<Vec<_>>>()
538                .map(Attributes::with_attributes)
539        }))
540    }
541
542    /// Take an optional list of entity type name strings from an action apply
543    /// spec and parse it into a set of `Name`s for those entity types. If any
544    /// of the entity type names cannot be parsed, then the `Err` case is
545    /// returned, and it will indicate which name did not parse.
546    fn parse_apply_spec_type_list(
547        types: Option<Vec<SmolStr>>,
548        namespace: Option<&Name>,
549    ) -> Result<HashSet<EntityType>> {
550        types
551            .map(|types| {
552                types
553                    .iter()
554                    // Parse each type name string into a `Name`, generating an
555                    // `EntityTypeParseError` when the string is not a valid
556                    // name.
557                    .map(|ty_str| {
558                        Ok(EntityType::Concrete(
559                            Self::parse_possibly_qualified_name_with_default_namespace(
560                                ty_str, namespace,
561                            )
562                            .map_err(SchemaError::EntityTypeParseError)?,
563                        ))
564                    })
565                    // Fail if any of the types failed.
566                    .collect::<Result<HashSet<_>>>()
567            })
568            .unwrap_or_else(|| Ok(HashSet::from([EntityType::Unspecified])))
569    }
570
571    // Parse a `Name` from a string (possibly including namespaces). If it is
572    // not qualified with any namespace, then apply the  default namespace to
573    // create a qualified name.  Do not modify any existing namespace on the
574    // type.
575    pub(crate) fn parse_possibly_qualified_name_with_default_namespace(
576        name_str: &SmolStr,
577        default_namespace: Option<&Name>,
578    ) -> std::result::Result<Name, Vec<ParseError>> {
579        let name = Name::from_normalized_str(name_str)?;
580
581        let qualified_name = if name.namespace_components().next().is_some() {
582            // The name is already qualified. Don't touch it.
583            name
584        } else {
585            // The name does not have a namespace, so qualify the type to
586            // use the default.
587            match default_namespace {
588                Some(namespace) => {
589                    Name::type_in_namespace(name.basename().clone(), namespace.clone())
590                }
591                None => name,
592            }
593        };
594
595        Ok(qualified_name)
596    }
597
598    /// Parse a name from a string into the `Id` (basename only).  Then
599    /// initialize the namespace for this type with the provided namespace vec
600    /// to create the qualified `Name`.
601    fn parse_unqualified_name_with_namespace(
602        type_name: impl AsRef<str>,
603        namespace: Option<Name>,
604    ) -> std::result::Result<Name, Vec<ParseError>> {
605        let type_name = Id::from_normalized_str(type_name.as_ref())?;
606        match namespace {
607            Some(namespace) => Ok(Name::type_in_namespace(type_name, namespace)),
608            None => Ok(Name::unqualified_name(type_name)),
609        }
610    }
611
612    /// Take an action identifier as a string and use it to construct an
613    /// EntityUID for that action. The entity type of the action will always
614    /// have the base type `Action`. The type will be qualified with any
615    /// namespace provided in the `namespace` argument or with the namespace
616    /// inside the ActionEntityUID if one is present.
617    fn parse_action_id_with_namespace(
618        action_id: &ActionEntityUID,
619        namespace: Option<&Name>,
620    ) -> Result<EntityUID> {
621        let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
622            Self::parse_possibly_qualified_name_with_default_namespace(action_ty, namespace)
623                .map_err(SchemaError::EntityTypeParseError)?
624        } else {
625            let id = Id::from_normalized_str(ACTION_ENTITY_TYPE).expect(
626                "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
627            );
628            match namespace {
629                Some(namespace) => Name::type_in_namespace(id, namespace.clone()),
630                None => Name::unqualified_name(id),
631            }
632        };
633        Ok(EntityUID::from_components(
634            namespaced_action_type,
635            Eid::new(action_id.id.clone()),
636        ))
637    }
638
639    /// Implemented to convert a type as written in the schema json format into the
640    /// `Type` type used by the validator. Conversion can fail if an entity or
641    /// record attribute name is invalid. It will also fail for some types that can
642    /// be written in the schema, but are not yet implemented in the typechecking
643    /// logic.
644    pub(crate) fn try_schema_type_into_validator_type(
645        default_namespace: Option<&Name>,
646        schema_ty: SchemaType,
647    ) -> Result<WithUnresolvedTypeDefs<Type>> {
648        match schema_ty {
649            SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
650            SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
651            SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
652            SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
653                Self::try_schema_type_into_validator_type(default_namespace, *element)?
654                    .map(Type::set),
655            ),
656            SchemaType::Type(SchemaTypeVariant::Record {
657                attributes,
658                additional_attributes,
659            }) => {
660                if additional_attributes {
661                    Err(SchemaError::UnsupportedSchemaFeature(
662                        UnsupportedFeature::OpenRecordsAndEntities,
663                    ))
664                } else {
665                    Ok(
666                        Self::parse_record_attributes(default_namespace, attributes)?
667                            .map(Type::record_with_attributes),
668                    )
669                }
670            }
671            SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
672                let entity_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
673                    &name,
674                    default_namespace,
675                )
676                .map_err(SchemaError::EntityTypeParseError)?;
677                Ok(Type::named_entity_reference(entity_type_name).into())
678            }
679            SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
680                let extension_type_name = Name::from_normalized_str(&name)
681                    .map_err(SchemaError::ExtensionTypeParseError)?;
682                Ok(Type::extension(extension_type_name).into())
683            }
684            SchemaType::TypeDef { type_name } => {
685                let defined_type_name = Self::parse_possibly_qualified_name_with_default_namespace(
686                    &type_name,
687                    default_namespace,
688                )
689                .map_err(SchemaError::CommonTypeParseError)?;
690                Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
691                    typ_defs.get(&defined_type_name).cloned().ok_or(
692                        SchemaError::UndeclaredCommonType(HashSet::from([type_name.to_string()])),
693                    )
694                }))
695            }
696        }
697    }
698
699    /// Access the `Name` for the namespace of this definition.
700    pub fn namespace(&self) -> &Option<Name> {
701        &self.namespace
702    }
703}
704
705#[derive(Debug)]
706pub struct ValidatorSchemaFragment(Vec<ValidatorNamespaceDef>);
707
708impl TryInto<ValidatorSchemaFragment> for SchemaFragment {
709    type Error = SchemaError;
710
711    fn try_into(self) -> Result<ValidatorSchemaFragment> {
712        ValidatorSchemaFragment::from_schema_fragment(self, ActionBehavior::default())
713    }
714}
715
716impl ValidatorSchemaFragment {
717    pub fn from_namespaces(namespaces: impl IntoIterator<Item = ValidatorNamespaceDef>) -> Self {
718        Self(namespaces.into_iter().collect())
719    }
720
721    pub fn from_schema_fragment(
722        fragment: SchemaFragment,
723        action_behavior: ActionBehavior,
724    ) -> Result<Self> {
725        Ok(Self(
726            fragment
727                .0
728                .into_iter()
729                .map(|(fragment_ns, ns_def)| {
730                    ValidatorNamespaceDef::from_namespace_definition(
731                        Some(fragment_ns),
732                        ns_def,
733                        action_behavior,
734                    )
735                })
736                .collect::<Result<Vec<_>>>()?,
737        ))
738    }
739
740    /// Access the `Name`s for the namespaces in this fragment.
741    pub fn namespaces(&self) -> impl Iterator<Item = &Option<Name>> {
742        self.0.iter().map(|d| d.namespace())
743    }
744}
745
746#[serde_as]
747#[derive(Clone, Debug, Serialize)]
748pub struct ValidatorSchema {
749    /// Map from entity type names to the ValidatorEntityType object.
750    #[serde(rename = "entityTypes")]
751    #[serde_as(as = "Vec<(_, _)>")]
752    entity_types: HashMap<Name, ValidatorEntityType>,
753
754    /// Map from action id names to the ValidatorActionId object.
755    #[serde(rename = "actionIds")]
756    #[serde_as(as = "Vec<(_, _)>")]
757    action_ids: HashMap<EntityUID, ValidatorActionId>,
758}
759
760impl std::str::FromStr for ValidatorSchema {
761    type Err = SchemaError;
762
763    fn from_str(s: &str) -> Result<Self> {
764        serde_json::from_str::<SchemaFragment>(s)?.try_into()
765    }
766}
767
768impl TryFrom<NamespaceDefinition> for ValidatorSchema {
769    type Error = SchemaError;
770
771    fn try_from(nsd: NamespaceDefinition) -> Result<ValidatorSchema> {
772        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
773            nsd.try_into()?
774        ])])
775    }
776}
777
778impl TryFrom<SchemaFragment> for ValidatorSchema {
779    type Error = SchemaError;
780
781    fn try_from(frag: SchemaFragment) -> Result<ValidatorSchema> {
782        ValidatorSchema::from_schema_fragments([frag.try_into()?])
783    }
784}
785
786impl ValidatorSchema {
787    // Create a ValidatorSchema without any entity types or actions ids.
788    pub fn empty() -> ValidatorSchema {
789        Self {
790            entity_types: HashMap::new(),
791            action_ids: HashMap::new(),
792        }
793    }
794
795    /// Construct a `ValidatorSchema` from a JSON value (which should be an
796    /// object matching the `SchemaFileFormat` shape).
797    pub fn from_json_value(json: serde_json::Value) -> Result<Self> {
798        Self::from_schema_file(
799            SchemaFragment::from_json_value(json)?,
800            ActionBehavior::default(),
801        )
802    }
803
804    /// Construct a `ValidatorSchema` directly from a file.
805    pub fn from_file(file: impl std::io::Read) -> Result<Self> {
806        Self::from_schema_file(SchemaFragment::from_file(file)?, ActionBehavior::default())
807    }
808
809    pub fn from_schema_file(
810        schema_file: SchemaFragment,
811        action_behavior: ActionBehavior,
812    ) -> Result<ValidatorSchema> {
813        Self::from_schema_fragments([ValidatorSchemaFragment::from_schema_fragment(
814            schema_file,
815            action_behavior,
816        )?])
817    }
818
819    /// Construct a new `ValidatorSchema` from some number of schema fragments.
820    pub fn from_schema_fragments(
821        fragments: impl IntoIterator<Item = ValidatorSchemaFragment>,
822    ) -> Result<ValidatorSchema> {
823        let mut type_defs = HashMap::new();
824        let mut entity_type_fragments = HashMap::new();
825        let mut action_fragments = HashMap::new();
826
827        for ns_def in fragments.into_iter().flat_map(|f| f.0.into_iter()) {
828            // Build aggregate maps for the declared typedefs, entity types, and
829            // actions, checking that nothing is defined twice.  Namespaces were
830            // already added by the `ValidatorNamespaceDef`, so the same base
831            // type name may appear multiple times so long as the namespaces are
832            // different.
833            for (name, ty) in ns_def.type_defs.type_defs {
834                match type_defs.entry(name) {
835                    Entry::Vacant(v) => v.insert(ty),
836                    Entry::Occupied(o) => {
837                        return Err(SchemaError::DuplicateCommonType(o.key().to_string()));
838                    }
839                };
840            }
841
842            for (name, entity_type) in ns_def.entity_types.entity_types {
843                match entity_type_fragments.entry(name) {
844                    Entry::Vacant(v) => v.insert(entity_type),
845                    Entry::Occupied(o) => {
846                        return Err(SchemaError::DuplicateEntityType(o.key().to_string()))
847                    }
848                };
849            }
850
851            for (action_euid, action) in ns_def.actions.actions {
852                match action_fragments.entry(action_euid) {
853                    Entry::Vacant(v) => v.insert(action),
854                    Entry::Occupied(o) => {
855                        return Err(SchemaError::DuplicateAction(o.key().to_string()))
856                    }
857                };
858            }
859        }
860
861        // Invert the `parents` relation defined by entities and action so far
862        // to get a `children` relation.
863        let mut entity_children = HashMap::new();
864        for (name, entity_type) in entity_type_fragments.iter() {
865            for parent in entity_type.parents.iter() {
866                entity_children
867                    .entry(parent.clone())
868                    .or_insert_with(HashSet::new)
869                    .insert(name.clone());
870            }
871        }
872
873        let mut entity_types = entity_type_fragments
874            .into_iter()
875            .map(|(name, entity_type)| -> Result<_> {
876                // Keys of the `entity_children` map were values of an
877                // `memberOfTypes` list, so they might not have been declared in
878                // their fragment.  By removing entries from `entity_children`
879                // where the key is a declared name, we will be left with a map
880                // where the keys are undeclared. These keys are used to report
881                // an error when undeclared entity types are referenced inside a
882                // `memberOfTypes` list. The error is reported alongside the
883                // error for any other undeclared entity types by
884                // `check_for_undeclared`.
885                let descendants = entity_children.remove(&name).unwrap_or_default();
886                Ok((
887                    name.clone(),
888                    ValidatorEntityType {
889                        name: name.clone(),
890                        descendants,
891                        attributes: Self::record_attributes_or_none(
892                            entity_type.attributes.resolve_type_defs(&type_defs)?,
893                        )
894                        .ok_or(SchemaError::ContextOrShapeNotRecord(
895                            ContextOrShape::EntityTypeShape(name),
896                        ))?,
897                    },
898                ))
899            })
900            .collect::<Result<HashMap<_, _>>>()?;
901
902        let mut action_children = HashMap::new();
903        for (euid, action) in action_fragments.iter() {
904            for parent in action.parents.iter() {
905                action_children
906                    .entry(parent.clone())
907                    .or_insert_with(HashSet::new)
908                    .insert(euid.clone());
909            }
910        }
911        let mut action_ids = action_fragments
912            .into_iter()
913            .map(|(name, action)| -> Result<_> {
914                let descendants = action_children.remove(&name).unwrap_or_default();
915
916                Ok((
917                    name.clone(),
918                    ValidatorActionId {
919                        name: name.clone(),
920                        applies_to: action.applies_to,
921                        descendants,
922                        context: Self::record_attributes_or_none(
923                            action.context.resolve_type_defs(&type_defs)?,
924                        )
925                        .ok_or(SchemaError::ContextOrShapeNotRecord(
926                            ContextOrShape::ActionContext(name),
927                        ))?,
928                        attribute_types: action.attribute_types,
929                        attributes: action.attributes,
930                    },
931                ))
932            })
933            .collect::<Result<HashMap<_, _>>>()?;
934
935        // We constructed entity types and actions with child maps, but we need
936        // transitively closed descendants.
937        compute_tc(&mut entity_types, false)?;
938        // Pass `true` here so that we also check that the action hierarchy does
939        // not contain cycles.
940        compute_tc(&mut action_ids, true)?;
941
942        // Return with an error if there is an undeclared entity or action
943        // referenced in any fragment. `{entity,action}_children` are provided
944        // for the `undeclared_parent_{entities,actions}` arguments because
945        // removed keys from these maps as we encountered declarations for the
946        // entity types or actions. Any keys left in the map are therefore
947        // undeclared.
948        Self::check_for_undeclared(
949            &entity_types,
950            entity_children.into_keys(),
951            &action_ids,
952            action_children.into_keys(),
953        )?;
954
955        Ok(ValidatorSchema {
956            entity_types,
957            action_ids,
958        })
959    }
960
961    /// Check that all entity types and actions referenced in the schema are in
962    /// the set of declared entity type or action names. Point of caution: this
963    /// function assumes that all entity types are fully qualified. This is
964    /// handled by the `SchemaFragment` constructor.
965    fn check_for_undeclared(
966        entity_types: &HashMap<Name, ValidatorEntityType>,
967        undeclared_parent_entities: impl IntoIterator<Item = Name>,
968        action_ids: &HashMap<EntityUID, ValidatorActionId>,
969        undeclared_parent_actions: impl IntoIterator<Item = EntityUID>,
970    ) -> Result<()> {
971        // When we constructed `entity_types`, we removed entity types from  the
972        // `entity_children` map as we encountered a declaration for that type.
973        // Any entity types left in the map are therefore undeclared. These are
974        // any undeclared entity types which appeared in a `memberOf` list.
975        let mut undeclared_e = undeclared_parent_entities
976            .into_iter()
977            .map(|n| n.to_string())
978            .collect::<HashSet<_>>();
979        // Looking at entity types, we need to check entity references in
980        // attribute types. We already know that all elements of the
981        // `descendants` list were declared because the list is a result of
982        // inverting the `memberOf` relationship which mapped declared entity
983        // types to their parent entity types.
984        for entity_type in entity_types.values() {
985            for (_, attr_typ) in entity_type.attributes() {
986                Self::check_undeclared_in_type(
987                    &attr_typ.attr_type,
988                    entity_types,
989                    &mut undeclared_e,
990                );
991            }
992        }
993
994        // Undeclared actions in a `memberOf` list.
995        let undeclared_a = undeclared_parent_actions
996            .into_iter()
997            .map(|n| n.to_string())
998            .collect::<HashSet<_>>();
999        // For actions, we check entity references in the context attribute
1000        // types and `appliesTo` lists. See the `entity_types` loop for why the
1001        // `descendants` list is not checked.
1002        for action in action_ids.values() {
1003            for (_, attr_typ) in action.context.iter() {
1004                Self::check_undeclared_in_type(
1005                    &attr_typ.attr_type,
1006                    entity_types,
1007                    &mut undeclared_e,
1008                );
1009            }
1010
1011            for p_entity in action.applies_to.applicable_principal_types() {
1012                match p_entity {
1013                    EntityType::Concrete(p_entity) => {
1014                        if !entity_types.contains_key(p_entity) {
1015                            undeclared_e.insert(p_entity.to_string());
1016                        }
1017                    }
1018                    EntityType::Unspecified => (),
1019                }
1020            }
1021
1022            for r_entity in action.applies_to.applicable_resource_types() {
1023                match r_entity {
1024                    EntityType::Concrete(r_entity) => {
1025                        if !entity_types.contains_key(r_entity) {
1026                            undeclared_e.insert(r_entity.to_string());
1027                        }
1028                    }
1029                    EntityType::Unspecified => (),
1030                }
1031            }
1032        }
1033        if !undeclared_e.is_empty() {
1034            return Err(SchemaError::UndeclaredEntityTypes(undeclared_e));
1035        }
1036        if !undeclared_a.is_empty() {
1037            return Err(SchemaError::UndeclaredActions(undeclared_a));
1038        }
1039
1040        Ok(())
1041    }
1042
1043    fn record_attributes_or_none(ty: Type) -> Option<Attributes> {
1044        match ty {
1045            Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => Some(attrs),
1046            _ => None,
1047        }
1048    }
1049
1050    // Check that all entity types appearing inside a type are in the set of
1051    // declared entity types, adding any undeclared entity types to the
1052    // `undeclared_types` set.
1053    fn check_undeclared_in_type(
1054        ty: &Type,
1055        entity_types: &HashMap<Name, ValidatorEntityType>,
1056        undeclared_types: &mut HashSet<String>,
1057    ) {
1058        match ty {
1059            Type::EntityOrRecord(EntityRecordKind::Entity(lub)) => {
1060                for name in lub.iter() {
1061                    if !entity_types.contains_key(name) {
1062                        undeclared_types.insert(name.to_string());
1063                    }
1064                }
1065            }
1066
1067            Type::EntityOrRecord(EntityRecordKind::Record { attrs }) => {
1068                for (_, attr_ty) in attrs.iter() {
1069                    Self::check_undeclared_in_type(
1070                        &attr_ty.attr_type,
1071                        entity_types,
1072                        undeclared_types,
1073                    );
1074                }
1075            }
1076
1077            Type::Set {
1078                element_type: Some(element_type),
1079            } => Self::check_undeclared_in_type(element_type, entity_types, undeclared_types),
1080
1081            _ => (),
1082        }
1083    }
1084
1085    /// Lookup the ValidatorActionId object in the schema with the given name.
1086    pub fn get_action_id(&self, action_id: &EntityUID) -> Option<&ValidatorActionId> {
1087        self.action_ids.get(action_id)
1088    }
1089
1090    /// Lookup the ValidatorEntityType object in the schema with the given name.
1091    pub fn get_entity_type(&self, entity_type_id: &Name) -> Option<&ValidatorEntityType> {
1092        self.entity_types.get(entity_type_id)
1093    }
1094
1095    /// Return true when the entity_type_id corresponds to a valid entity type.
1096    pub(crate) fn is_known_action_id(&self, action_id: &EntityUID) -> bool {
1097        self.action_ids.contains_key(action_id)
1098    }
1099
1100    /// Return true when the entity_type_id corresponds to a valid entity type.
1101    pub(crate) fn is_known_entity_type(&self, entity_type: &Name) -> bool {
1102        self.entity_types.contains_key(entity_type)
1103    }
1104
1105    /// An iterator over the action ids in the schema.
1106    pub(crate) fn known_action_ids(&self) -> impl Iterator<Item = &EntityUID> {
1107        self.action_ids.keys()
1108    }
1109
1110    /// An iterator over the entity type names in the schema.
1111    pub(crate) fn known_entity_types(&self) -> impl Iterator<Item = &Name> {
1112        self.entity_types.keys()
1113    }
1114
1115    /// An iterator matching the entity Types to their Validator Types
1116    pub fn entity_types(&self) -> impl Iterator<Item = (&Name, &ValidatorEntityType)> {
1117        self.entity_types.iter()
1118    }
1119
1120    /// Get the validator entity equal to an EUID using the component for a head
1121    /// var kind.
1122    pub(crate) fn get_entity_eq<'a, H, K>(&self, var: H, euid: EntityUID) -> Option<K>
1123    where
1124        H: 'a + HeadVar<K>,
1125        K: 'a,
1126    {
1127        var.get_euid_component(euid)
1128    }
1129
1130    /// Get the validator entities that are in the descendants of an EUID using
1131    /// the component for a head var kind.
1132    pub(crate) fn get_entities_in<'a, H, K>(
1133        &'a self,
1134        var: H,
1135        euid: EntityUID,
1136    ) -> impl Iterator<Item = K> + 'a
1137    where
1138        H: 'a + HeadVar<K>,
1139        K: 'a + Clone,
1140    {
1141        var.get_descendants_if_present(self, euid.clone())
1142            .into_iter()
1143            .flatten()
1144            .map(Clone::clone)
1145            .chain(var.get_euid_component_if_present(self, euid).into_iter())
1146    }
1147
1148    /// Get the validator entities that are in the descendants of any of the
1149    /// entities in a set of EUID using the component for a head var kind.
1150    pub(crate) fn get_entities_in_set<'a, H, K>(
1151        &'a self,
1152        var: H,
1153        euids: impl IntoIterator<Item = EntityUID> + 'a,
1154    ) -> impl Iterator<Item = K> + 'a
1155    where
1156        H: 'a + HeadVar<K>,
1157        K: 'a + Clone,
1158    {
1159        euids
1160            .into_iter()
1161            .flat_map(move |e| self.get_entities_in(var, e))
1162    }
1163
1164    /// Since different Actions have different schemas for `Context`, you must
1165    /// specify the `Action` in order to get a `ContextSchema`.
1166    ///
1167    /// Returns `None` if the action is not in the schema.
1168    pub fn get_context_schema(
1169        &self,
1170        action: &EntityUID,
1171    ) -> Option<impl cedar_policy_core::entities::ContextSchema> {
1172        self.get_action_id(action).map(|action_id| {
1173            crate::types::Type::record_with_attributes(
1174                action_id
1175                    .context
1176                    .iter()
1177                    .map(|(k, v)| (k.clone(), v.clone())),
1178            )
1179        })
1180    }
1181
1182    /// Construct an `Entity` object for each action in the schema
1183    fn action_entities_iter(&self) -> impl Iterator<Item = cedar_policy_core::ast::Entity> + '_ {
1184        // We could store the un-inverted `memberOf` relation for each action,
1185        // but I [john-h-kastner-aws] judge that the current implementation is
1186        // actually less error prone, as it minimizes the threading of data
1187        // structures through some complicated bits of schema construction code,
1188        // and avoids computing the TC twice.
1189        let mut action_ancestors: HashMap<&EntityUID, HashSet<EntityUID>> = HashMap::new();
1190        for (action_euid, action_def) in &self.action_ids {
1191            for descendant in &action_def.descendants {
1192                action_ancestors
1193                    .entry(descendant)
1194                    .or_default()
1195                    .insert(action_euid.clone());
1196            }
1197        }
1198        self.action_ids.iter().map(move |(action_id, action)| {
1199            Entity::new(
1200                action_id.clone(),
1201                action.attributes.clone(),
1202                action_ancestors.remove(action_id).unwrap_or_default(),
1203            )
1204        })
1205    }
1206
1207    /// Invert the action hierarchy to get the ancestor relation expected for
1208    /// the `Entity` datatype instead of descendant as stored by the schema.
1209    pub fn action_entities(&self) -> cedar_policy_core::entities::Result<Entities> {
1210        Entities::from_entities(
1211            self.action_entities_iter(),
1212            TCComputation::AssumeAlreadyComputed,
1213        )
1214    }
1215}
1216
1217/// Struct which carries enough information that it can (efficiently) impl Core's `Schema`
1218pub struct CoreSchema<'a> {
1219    /// Contains all the information
1220    schema: &'a ValidatorSchema,
1221    /// For easy lookup, this is a map from action name to `Entity` object
1222    /// for each action in the schema. This information is contained in the
1223    /// `ValidatorSchema`, but not efficient to extract -- getting the `Entity`
1224    /// from the `ValidatorSchema` is O(N) as of this writing, but with this
1225    /// cache it's O(1).
1226    actions: HashMap<EntityUID, Arc<Entity>>,
1227}
1228
1229impl<'a> CoreSchema<'a> {
1230    pub fn new(schema: &'a ValidatorSchema) -> Self {
1231        Self {
1232            actions: schema
1233                .action_entities_iter()
1234                .map(|e| (e.uid(), Arc::new(e)))
1235                .collect(),
1236            schema,
1237        }
1238    }
1239}
1240
1241impl<'a> cedar_policy_core::entities::Schema for CoreSchema<'a> {
1242    type EntityTypeDescription = EntityTypeDescription;
1243
1244    fn entity_type(
1245        &self,
1246        entity_type: &cedar_policy_core::ast::EntityType,
1247    ) -> Option<EntityTypeDescription> {
1248        match entity_type {
1249            cedar_policy_core::ast::EntityType::Unspecified => None, // Unspecified entities cannot be declared in the schema and should not appear in JSON data
1250            cedar_policy_core::ast::EntityType::Concrete(name) => {
1251                EntityTypeDescription::new(self.schema, name)
1252            }
1253        }
1254    }
1255
1256    fn action(&self, action: &EntityUID) -> Option<Arc<cedar_policy_core::ast::Entity>> {
1257        self.actions.get(action).map(Arc::clone)
1258    }
1259
1260    fn entity_types_with_basename<'b>(
1261        &'b self,
1262        basename: &'b Id,
1263    ) -> Box<dyn Iterator<Item = EntityType> + 'b> {
1264        Box::new(self.schema.entity_types().filter_map(move |(name, _)| {
1265            if name.basename() == basename {
1266                Some(EntityType::Concrete(name.clone()))
1267            } else {
1268                None
1269            }
1270        }))
1271    }
1272}
1273
1274/// Struct which carries enough information that it can impl Core's `EntityTypeDescription`
1275pub struct EntityTypeDescription {
1276    /// Core `EntityType` this is describing
1277    core_type: cedar_policy_core::ast::EntityType,
1278    /// Contains most of the schema information for this entity type
1279    validator_type: ValidatorEntityType,
1280    /// Allowed parent types for this entity type. (As of this writing, this
1281    /// information is not contained in the `validator_type` by itself.)
1282    allowed_parent_types: Arc<HashSet<cedar_policy_core::ast::EntityType>>,
1283}
1284
1285impl EntityTypeDescription {
1286    /// Create a description of the given type in the given schema.
1287    /// Returns `None` if the given type is not in the given schema.
1288    pub fn new(schema: &ValidatorSchema, type_name: &Name) -> Option<Self> {
1289        Some(Self {
1290            core_type: cedar_policy_core::ast::EntityType::Concrete(type_name.clone()),
1291            validator_type: schema.get_entity_type(type_name).cloned()?,
1292            allowed_parent_types: {
1293                let mut set = HashSet::new();
1294                for (possible_parent_typename, possible_parent_et) in &schema.entity_types {
1295                    if possible_parent_et.descendants.contains(type_name) {
1296                        set.insert(cedar_policy_core::ast::EntityType::Concrete(
1297                            possible_parent_typename.clone(),
1298                        ));
1299                    }
1300                }
1301                Arc::new(set)
1302            },
1303        })
1304    }
1305}
1306
1307impl cedar_policy_core::entities::EntityTypeDescription for EntityTypeDescription {
1308    fn entity_type(&self) -> cedar_policy_core::ast::EntityType {
1309        self.core_type.clone()
1310    }
1311
1312    fn attr_type(&self, attr: &str) -> Option<cedar_policy_core::entities::SchemaType> {
1313        let attr_type: &crate::types::Type = &self.validator_type.attr(attr)?.attr_type;
1314        let core_schema_type: cedar_policy_core::entities::SchemaType = attr_type
1315            .clone()
1316            .try_into()
1317            .expect("failed to convert validator type into Core SchemaType");
1318        debug_assert!(attr_type.is_consistent_with(&core_schema_type));
1319        Some(core_schema_type)
1320    }
1321
1322    fn required_attrs<'s>(&'s self) -> Box<dyn Iterator<Item = SmolStr> + 's> {
1323        Box::new(
1324            self.validator_type
1325                .attributes
1326                .iter()
1327                .filter(|(_, ty)| ty.is_required)
1328                .map(|(attr, _)| attr.clone()),
1329        )
1330    }
1331
1332    fn allowed_parent_types(&self) -> Arc<HashSet<cedar_policy_core::ast::EntityType>> {
1333        Arc::clone(&self.allowed_parent_types)
1334    }
1335}
1336
1337/// A `Type` contains all the information we need for a Core `ContextSchema`.
1338impl cedar_policy_core::entities::ContextSchema for crate::types::Type {
1339    fn context_type(&self) -> cedar_policy_core::entities::SchemaType {
1340        self.clone()
1341            .try_into()
1342            .expect("failed to convert validator type into Core SchemaType")
1343    }
1344}
1345
1346/// Contains entity type information for use by the validator. The contents of
1347/// the struct are the same as the schema entity type structure, but the
1348/// `member_of` relation is reversed to instead be `descendants`.
1349#[derive(Clone, Debug, Serialize)]
1350pub struct ValidatorEntityType {
1351    /// The name of the entity type.
1352    pub(crate) name: Name,
1353
1354    /// The set of entity types that can be members of this entity type. When
1355    /// this structure is initially constructed, the field will contain direct
1356    /// children, but it will be updated to contain the closure of all
1357    /// descendants before it is used in any validation.
1358    pub descendants: HashSet<Name>,
1359
1360    /// The attributes associated with this entity. Keys are the attribute
1361    /// identifiers while the values are the type of the attribute.
1362    pub(crate) attributes: Attributes,
1363}
1364
1365impl ValidatorEntityType {
1366    /// Get the type of the attribute with the given name, if it exists
1367    pub fn attr(&self, attr: &str) -> Option<&AttributeType> {
1368        self.attributes.get_attr(attr)
1369    }
1370
1371    /// An iterator over the attributes of this entity
1372    pub fn attributes(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1373        self.attributes.iter()
1374    }
1375}
1376
1377impl TCNode<Name> for ValidatorEntityType {
1378    fn get_key(&self) -> Name {
1379        self.name.clone()
1380    }
1381
1382    fn add_edge_to(&mut self, k: Name) {
1383        self.descendants.insert(k);
1384    }
1385
1386    fn out_edges(&self) -> Box<dyn Iterator<Item = &Name> + '_> {
1387        Box::new(self.descendants.iter())
1388    }
1389
1390    fn has_edge_to(&self, e: &Name) -> bool {
1391        self.descendants.contains(e)
1392    }
1393}
1394
1395/// Contains information about actions used by the validator.  The contents of
1396/// the struct are the same as the schema entity type structure, but the
1397/// `member_of` relation is reversed to instead be `descendants`.
1398#[derive(Clone, Debug, Serialize)]
1399pub struct ValidatorActionId {
1400    /// The name of the action.
1401    pub(crate) name: EntityUID,
1402
1403    /// The principals and resources that the action can be applied to.
1404    #[serde(rename = "appliesTo")]
1405    pub(crate) applies_to: ValidatorApplySpec,
1406
1407    /// The set of actions that can be members of this action. When this
1408    /// structure is initially constructed, the field will contain direct
1409    /// children, but it will be updated to contain the closure of all
1410    /// descendants before it is used in any validation.
1411    pub(crate) descendants: HashSet<EntityUID>,
1412
1413    /// The context attributes associated with this action. Keys are the context
1414    /// attribute identifiers while the values are the type of the attribute.
1415    pub(crate) context: Attributes,
1416
1417    /// The attribute types for this action, used for typechecking.
1418    pub(crate) attribute_types: Attributes,
1419
1420    /// The actual attribute value for this action, used to construct an
1421    /// `Entity` for this action. Could also be used for more precise
1422    /// typechecking by partial evaluation.
1423    pub(crate) attributes: HashMap<SmolStr, RestrictedExpr>,
1424}
1425
1426impl ValidatorActionId {
1427    /// An iterator over the attributes of this action's required context
1428    pub fn context(&self) -> impl Iterator<Item = (&SmolStr, &AttributeType)> {
1429        self.context.iter()
1430    }
1431}
1432
1433impl TCNode<EntityUID> for ValidatorActionId {
1434    fn get_key(&self) -> EntityUID {
1435        self.name.clone()
1436    }
1437
1438    fn add_edge_to(&mut self, k: EntityUID) {
1439        self.descendants.insert(k);
1440    }
1441
1442    fn out_edges(&self) -> Box<dyn Iterator<Item = &EntityUID> + '_> {
1443        Box::new(self.descendants.iter())
1444    }
1445
1446    fn has_edge_to(&self, e: &EntityUID) -> bool {
1447        self.descendants.contains(e)
1448    }
1449}
1450
1451/// The principals and resources that an action can be applied to.
1452#[derive(Clone, Debug, Serialize)]
1453pub(crate) struct ValidatorApplySpec {
1454    /// The principal entity types the action can be applied to. This set may
1455    /// be a singleton set containing the unspecified entity type when the
1456    /// `principalTypes` list is omitted in the schema. A non-singleton set
1457    /// shouldn't contain the unspecified entity type, but validation will give
1458    /// the same success/failure result as when it is the only element of the
1459    /// set, perhaps with extra type errors.
1460    #[serde(rename = "principalApplySpec")]
1461    principal_apply_spec: HashSet<EntityType>,
1462
1463    /// The resource entity types the action can be applied to. See comments on
1464    /// `principal_apply_spec` about the unspecified entity type.
1465    #[serde(rename = "resourceApplySpec")]
1466    resource_apply_spec: HashSet<EntityType>,
1467}
1468
1469impl ValidatorApplySpec {
1470    /// Create an apply spec for an action that can only be applied to some
1471    /// specific entities.
1472    pub(crate) fn new(
1473        principal_apply_spec: HashSet<EntityType>,
1474        resource_apply_spec: HashSet<EntityType>,
1475    ) -> Self {
1476        Self {
1477            principal_apply_spec,
1478            resource_apply_spec,
1479        }
1480    }
1481
1482    /// Get the applicable principal types for this spec.
1483    pub(crate) fn applicable_principal_types(&self) -> impl Iterator<Item = &EntityType> {
1484        self.principal_apply_spec.iter()
1485    }
1486
1487    /// Get the applicable resource types for this spec.
1488    pub(crate) fn applicable_resource_types(&self) -> impl Iterator<Item = &EntityType> {
1489        self.resource_apply_spec.iter()
1490    }
1491}
1492
1493/// This trait configures what sort of entity (principals, actions, or resources)
1494/// are returned by the function `get_entities_satisfying_constraint`.
1495pub(crate) trait HeadVar<K>: Copy {
1496    /// For a validator, get the known entities for this sort of head variable.
1497    /// This is all entity types (for principals and resources), or actions ids
1498    /// (for actions) that appear in the service description.
1499    fn get_known_vars<'a>(
1500        &self,
1501        schema: &'a ValidatorSchema,
1502    ) -> Box<dyn Iterator<Item = &'a K> + 'a>;
1503
1504    /// Extract the relevant component of an entity uid. This is the entity type
1505    /// for principals and resources, and the entity id for actions.
1506    fn get_euid_component(&self, euid: EntityUID) -> Option<K>;
1507
1508    /// Extract the relevant component of an entity uid if the entity uid is in
1509    /// the schema. Otherwise return None.
1510    fn get_euid_component_if_present(&self, schema: &ValidatorSchema, euid: EntityUID)
1511        -> Option<K>;
1512
1513    /// Get and iterator containing the valid descendants of an entity, if that
1514    /// entity exists in the schema. Otherwise None.
1515    fn get_descendants_if_present<'a>(
1516        &self,
1517        schema: &'a ValidatorSchema,
1518        euid: EntityUID,
1519    ) -> Option<Box<dyn Iterator<Item = &'a K> + 'a>>;
1520}
1521
1522/// Used to have `get_entities_satisfying_constraint` return the
1523/// `EntityTypeNames` for either principals or resources satisfying the head
1524/// constraints.
1525#[derive(Debug, Clone, Copy)]
1526pub(crate) enum PrincipalOrResourceHeadVar {
1527    PrincipalOrResource,
1528}
1529
1530impl HeadVar<Name> for PrincipalOrResourceHeadVar {
1531    fn get_known_vars<'a>(
1532        &self,
1533        schema: &'a ValidatorSchema,
1534    ) -> Box<dyn Iterator<Item = &'a Name> + 'a> {
1535        Box::new(schema.known_entity_types())
1536    }
1537
1538    fn get_euid_component(&self, euid: EntityUID) -> Option<Name> {
1539        let (ty, _) = euid.components();
1540        match ty {
1541            EntityType::Unspecified => None,
1542            EntityType::Concrete(name) => Some(name),
1543        }
1544    }
1545
1546    fn get_euid_component_if_present(
1547        &self,
1548        schema: &ValidatorSchema,
1549        euid: EntityUID,
1550    ) -> Option<Name> {
1551        let euid_component = self.get_euid_component(euid)?;
1552        if schema.is_known_entity_type(&euid_component) {
1553            Some(euid_component)
1554        } else {
1555            None
1556        }
1557    }
1558
1559    fn get_descendants_if_present<'a>(
1560        &self,
1561        schema: &'a ValidatorSchema,
1562        euid: EntityUID,
1563    ) -> Option<Box<dyn Iterator<Item = &'a Name> + 'a>> {
1564        let euid_component = self.get_euid_component(euid)?;
1565        match schema.get_entity_type(&euid_component) {
1566            Some(entity_type) => Some(Box::new(entity_type.descendants.iter())),
1567            None => None,
1568        }
1569    }
1570}
1571
1572/// Used to have `get_entities_satisfying_constraint` return the
1573/// `ActionIdNames` for actions satisfying the head constraints
1574#[derive(Debug, Clone, Copy)]
1575pub(crate) enum ActionHeadVar {
1576    Action,
1577}
1578
1579impl HeadVar<EntityUID> for ActionHeadVar {
1580    fn get_known_vars<'a>(
1581        &self,
1582        schema: &'a ValidatorSchema,
1583    ) -> Box<dyn Iterator<Item = &'a EntityUID> + 'a> {
1584        Box::new(schema.known_action_ids())
1585    }
1586
1587    fn get_euid_component(&self, euid: EntityUID) -> Option<EntityUID> {
1588        Some(euid)
1589    }
1590
1591    fn get_euid_component_if_present(
1592        &self,
1593        schema: &ValidatorSchema,
1594        euid: EntityUID,
1595    ) -> Option<EntityUID> {
1596        let euid_component = self.get_euid_component(euid)?;
1597        if schema.is_known_action_id(&euid_component) {
1598            Some(euid_component)
1599        } else {
1600            None
1601        }
1602    }
1603
1604    fn get_descendants_if_present<'a>(
1605        &self,
1606        schema: &'a ValidatorSchema,
1607        euid: EntityUID,
1608    ) -> Option<Box<dyn Iterator<Item = &'a EntityUID> + 'a>> {
1609        let euid_component = self.get_euid_component(euid)?;
1610        match schema.get_action_id(&euid_component) {
1611            Some(action_id) => Some(Box::new(action_id.descendants.iter())),
1612            None => None,
1613        }
1614    }
1615}
1616
1617/// Used to write a schema implicitly overriding the default handling of action
1618/// groups.
1619#[derive(Debug, Clone, Deserialize)]
1620#[serde(transparent)]
1621pub(crate) struct NamespaceDefinitionWithActionAttributes(pub(crate) NamespaceDefinition);
1622
1623impl TryInto<ValidatorSchema> for NamespaceDefinitionWithActionAttributes {
1624    type Error = SchemaError;
1625
1626    fn try_into(self) -> Result<ValidatorSchema> {
1627        ValidatorSchema::from_schema_fragments([ValidatorSchemaFragment::from_namespaces([
1628            ValidatorNamespaceDef::from_namespace_definition(
1629                None,
1630                self.0,
1631                crate::ActionBehavior::PermitAttributes,
1632            )?,
1633        ])])
1634    }
1635}
1636
1637#[cfg(test)]
1638mod test {
1639    use std::{collections::BTreeMap, str::FromStr};
1640
1641    use crate::types::Type;
1642
1643    use serde_json::json;
1644
1645    use super::*;
1646
1647    // Well-formed schema
1648    #[test]
1649    fn test_from_schema_file() {
1650        let src = json!(
1651        {
1652            "entityTypes": {
1653                "User": {
1654                    "memberOfTypes": [ "Group" ]
1655                },
1656                "Group": {
1657                    "memberOfTypes": []
1658                },
1659                "Photo": {
1660                    "memberOfTypes": [ "Album" ]
1661                },
1662                "Album": {
1663                    "memberOfTypes": []
1664                }
1665            },
1666            "actions": {
1667                "view_photo": {
1668                    "appliesTo": {
1669                        "principalTypes": ["User", "Group"],
1670                        "resourceTypes": ["Photo"]
1671                    }
1672                }
1673            }
1674        });
1675        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1676        let schema: Result<ValidatorSchema> = schema_file.try_into();
1677        assert!(schema.is_ok());
1678    }
1679
1680    // Duplicate entity "Photo"
1681    #[test]
1682    fn test_from_schema_file_duplicate_entity() {
1683        // Test written using `from_str` instead of `from_value` because the
1684        // `json!` macro silently ignores duplicate map keys.
1685        let src = r#"
1686        {"": {
1687            "entityTypes": {
1688                "User": {
1689                    "memberOfTypes": [ "Group" ]
1690                },
1691                "Group": {
1692                    "memberOfTypes": []
1693                },
1694                "Photo": {
1695                    "memberOfTypes": [ "Album" ]
1696                },
1697                "Photo": {
1698                    "memberOfTypes": []
1699                }
1700            },
1701            "actions": {
1702                "view_photo": {
1703                    "memberOf": [],
1704                    "appliesTo": {
1705                        "principalTypes": ["User", "Group"],
1706                        "resourceTypes": ["Photo"]
1707                    }
1708                }
1709            }
1710        }}"#;
1711
1712        match ValidatorSchema::from_str(src) {
1713            Err(SchemaError::ParseFileFormat(_)) => (),
1714            _ => panic!("Expected serde error due to duplicate entity type."),
1715        }
1716    }
1717
1718    // Duplicate action "view_photo"
1719    #[test]
1720    fn test_from_schema_file_duplicate_action() {
1721        // Test written using `from_str` instead of `from_value` because the
1722        // `json!` macro silently ignores duplicate map keys.
1723        let src = r#"
1724        {"": {
1725            "entityTypes": {
1726                "User": {
1727                    "memberOfTypes": [ "Group" ]
1728                },
1729                "Group": {
1730                    "memberOfTypes": []
1731                },
1732                "Photo": {
1733                    "memberOfTypes": []
1734                }
1735            },
1736            "actions": {
1737                "view_photo": {
1738                    "memberOf": [],
1739                    "appliesTo": {
1740                        "principalTypes": ["User", "Group"],
1741                        "resourceTypes": ["Photo"]
1742                    }
1743                },
1744                "view_photo": { }
1745            }
1746        }"#;
1747        match ValidatorSchema::from_str(src) {
1748            Err(SchemaError::ParseFileFormat(_)) => (),
1749            _ => panic!("Expected serde error due to duplicate action type."),
1750        }
1751    }
1752
1753    // Undefined entity types "Grop", "Usr", "Phoot"
1754    #[test]
1755    fn test_from_schema_file_undefined_entities() {
1756        let src = json!(
1757        {
1758            "entityTypes": {
1759                "User": {
1760                    "memberOfTypes": [ "Grop" ]
1761                },
1762                "Group": {
1763                    "memberOfTypes": []
1764                },
1765                "Photo": {
1766                    "memberOfTypes": []
1767                }
1768            },
1769            "actions": {
1770                "view_photo": {
1771                    "appliesTo": {
1772                        "principalTypes": ["Usr", "Group"],
1773                        "resourceTypes": ["Phoot"]
1774                    }
1775                }
1776            }
1777        });
1778        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1779        let schema: Result<ValidatorSchema> = schema_file.try_into();
1780        match schema {
1781            Ok(_) => panic!("from_schema_file should have failed"),
1782            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1783                assert_eq!(v.len(), 3)
1784            }
1785            _ => panic!("Unexpected error from from_schema_file"),
1786        }
1787    }
1788
1789    #[test]
1790    fn undefined_entity_namespace_member_of() {
1791        let src = json!(
1792        {"Foo": {
1793            "entityTypes": {
1794                "User": {
1795                    "memberOfTypes": [ "Foo::Group", "Bar::Group" ]
1796                },
1797                "Group": { }
1798            },
1799            "actions": {}
1800        }});
1801        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1802        let schema: Result<ValidatorSchema> = schema_file.try_into();
1803        match schema {
1804            Ok(_) => panic!("try_into should have failed"),
1805            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1806                assert_eq!(v, HashSet::from(["Bar::Group".to_string()]))
1807            }
1808            _ => panic!("Unexpected error from try_into"),
1809        }
1810    }
1811
1812    #[test]
1813    fn undefined_entity_namespace_applies_to() {
1814        let src = json!(
1815        {"Foo": {
1816            "entityTypes": { "User": { }, "Photo": { } },
1817            "actions": {
1818                "view_photo": {
1819                    "appliesTo": {
1820                        "principalTypes": ["Foo::User", "Bar::User"],
1821                        "resourceTypes": ["Photo", "Bar::Photo"],
1822                    }
1823                }
1824            }
1825        }});
1826        let schema_file: SchemaFragment = serde_json::from_value(src).expect("Parse Error");
1827        let schema: Result<ValidatorSchema> = schema_file.try_into();
1828        match schema {
1829            Ok(_) => panic!("try_into should have failed"),
1830            Err(SchemaError::UndeclaredEntityTypes(v)) => {
1831                assert_eq!(
1832                    v,
1833                    HashSet::from(["Bar::Photo".to_string(), "Bar::User".to_string()])
1834                )
1835            }
1836            _ => panic!("Unexpected error from try_into"),
1837        }
1838    }
1839
1840    // Undefined action "photo_actions"
1841    #[test]
1842    fn test_from_schema_file_undefined_action() {
1843        let src = json!(
1844        {
1845            "entityTypes": {
1846                "User": {
1847                    "memberOfTypes": [ "Group" ]
1848                },
1849                "Group": {
1850                    "memberOfTypes": []
1851                },
1852                "Photo": {
1853                    "memberOfTypes": []
1854                }
1855            },
1856            "actions": {
1857                "view_photo": {
1858                    "memberOf": [ {"id": "photo_action"} ],
1859                    "appliesTo": {
1860                        "principalTypes": ["User", "Group"],
1861                        "resourceTypes": ["Photo"]
1862                    }
1863                }
1864            }
1865        });
1866        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1867        let schema: Result<ValidatorSchema> = schema_file.try_into();
1868        match schema {
1869            Ok(_) => panic!("from_schema_file should have failed"),
1870            Err(SchemaError::UndeclaredActions(v)) => assert_eq!(v.len(), 1),
1871            _ => panic!("Unexpected error from from_schema_file"),
1872        }
1873    }
1874
1875    // Trivial cycle in action hierarchy
1876    // view_photo -> view_photo
1877    #[test]
1878    fn test_from_schema_file_action_cycle1() {
1879        let src = json!(
1880        {
1881            "entityTypes": {},
1882            "actions": {
1883                "view_photo": {
1884                    "memberOf": [ {"id": "view_photo"} ]
1885                }
1886            }
1887        });
1888        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1889        let schema: Result<ValidatorSchema> = schema_file.try_into();
1890        match schema {
1891            Ok(_) => panic!("from_schema_file should have failed"),
1892            Err(SchemaError::CycleInActionHierarchy) => (), // expected result
1893            e => panic!("Unexpected error from from_schema_file: {:?}", e),
1894        }
1895    }
1896
1897    // Slightly more complex cycle in action hierarchy
1898    // view_photo -> edit_photo -> delete_photo -> view_photo
1899    #[test]
1900    fn test_from_schema_file_action_cycle2() {
1901        let src = json!(
1902        {
1903            "entityTypes": {},
1904            "actions": {
1905                "view_photo": {
1906                    "memberOf": [ {"id": "edit_photo"} ]
1907                },
1908                "edit_photo": {
1909                    "memberOf": [ {"id": "delete_photo"} ]
1910                },
1911                "delete_photo": {
1912                    "memberOf": [ {"id": "view_photo"} ]
1913                },
1914                "other_action": {
1915                    "memberOf": [ {"id": "edit_photo"} ]
1916                }
1917            }
1918        });
1919        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
1920        let schema: Result<ValidatorSchema> = schema_file.try_into();
1921        match schema {
1922            Ok(x) => {
1923                println!("{:?}", x);
1924                panic!("from_schema_file should have failed");
1925            }
1926            Err(SchemaError::CycleInActionHierarchy) => (), // expected result
1927            e => panic!("Unexpected error from from_schema_file: {:?}", e),
1928        }
1929    }
1930
1931    #[test]
1932    fn namespaced_schema() {
1933        let src = r#"
1934        { "N::S": {
1935            "entityTypes": {
1936                "User": {},
1937                "Photo": {}
1938            },
1939            "actions": {
1940                "view_photo": {
1941                    "appliesTo": {
1942                        "principalTypes": ["User"],
1943                        "resourceTypes": ["Photo"]
1944                    }
1945                }
1946            }
1947        } }
1948        "#;
1949        let schema_file: SchemaFragment = serde_json::from_str(src).expect("Parse Error");
1950        let schema: ValidatorSchema = schema_file
1951            .try_into()
1952            .expect("Namespaced schema failed to convert.");
1953        dbg!(&schema);
1954        let user_entity_type = &"N::S::User"
1955            .parse()
1956            .expect("Namespaced entity type should have parsed");
1957        let photo_entity_type = &"N::S::Photo"
1958            .parse()
1959            .expect("Namespaced entity type should have parsed");
1960        assert!(
1961            schema.entity_types.contains_key(user_entity_type),
1962            "Expected and entity type User."
1963        );
1964        assert!(
1965            schema.entity_types.contains_key(photo_entity_type),
1966            "Expected an entity type Photo."
1967        );
1968        assert_eq!(
1969            schema.entity_types.len(),
1970            2,
1971            "Expected exactly 2 entity types."
1972        );
1973        assert!(
1974            schema.action_ids.contains_key(
1975                &"N::S::Action::\"view_photo\""
1976                    .parse()
1977                    .expect("Namespaced action should have parsed")
1978            ),
1979            "Expected an action \"view_photo\"."
1980        );
1981        assert_eq!(schema.action_ids.len(), 1, "Expected exactly 1 action.");
1982
1983        let apply_spec = &schema
1984            .action_ids
1985            .values()
1986            .next()
1987            .expect("Expected Action")
1988            .applies_to;
1989        assert_eq!(
1990            apply_spec.applicable_principal_types().collect::<Vec<_>>(),
1991            vec![&EntityType::Concrete(user_entity_type.clone())]
1992        );
1993        assert_eq!(
1994            apply_spec.applicable_resource_types().collect::<Vec<_>>(),
1995            vec![&EntityType::Concrete(photo_entity_type.clone())]
1996        );
1997    }
1998
1999    #[test]
2000    fn cant_use_namespace_in_entity_type() {
2001        let src = r#"
2002        {
2003            "entityTypes": { "NS::User": {} },
2004            "actions": {}
2005        }
2006        "#;
2007        let schema_file: NamespaceDefinition = serde_json::from_str(src).expect("Parse Error");
2008        assert!(
2009            matches!(TryInto::<ValidatorSchema>::try_into(schema_file), Err(SchemaError::EntityTypeParseError(_))),
2010            "Expected that namespace in the entity type NS::User would cause a EntityType parse error.");
2011    }
2012
2013    #[test]
2014    fn entity_attribute_entity_type_with_namespace() {
2015        let schema_json: SchemaFragment = serde_json::from_str(
2016            r#"
2017            {"A::B": {
2018                "entityTypes": {
2019                    "Foo": {
2020                        "shape": {
2021                            "type": "Record",
2022                            "attributes": {
2023                                "name": { "type": "Entity", "name": "C::D::Foo" }
2024                            }
2025                        }
2026                    }
2027                },
2028                "actions": {}
2029              }}
2030            "#,
2031        )
2032        .expect("Expected valid schema");
2033
2034        let schema: Result<ValidatorSchema> = schema_json.try_into();
2035        match schema {
2036            Err(SchemaError::UndeclaredEntityTypes(tys)) => {
2037                assert_eq!(tys, HashSet::from(["C::D::Foo".to_string()]))
2038            }
2039            _ => panic!("Schema construction should have failed due to undeclared entity type."),
2040        }
2041    }
2042
2043    #[test]
2044    fn entity_attribute_entity_type_with_declared_namespace() {
2045        let schema_json: SchemaFragment = serde_json::from_str(
2046            r#"
2047            {"A::B": {
2048                "entityTypes": {
2049                    "Foo": {
2050                        "shape": {
2051                            "type": "Record",
2052                            "attributes": {
2053                                "name": { "type": "Entity", "name": "A::B::Foo" }
2054                            }
2055                        }
2056                    }
2057                },
2058                "actions": {}
2059              }}
2060            "#,
2061        )
2062        .expect("Expected valid schema");
2063
2064        let schema: ValidatorSchema = schema_json
2065            .try_into()
2066            .expect("Expected schema to construct without error.");
2067
2068        let foo_name: Name = "A::B::Foo".parse().expect("Expected entity type name");
2069        let foo_type = schema
2070            .entity_types
2071            .get(&foo_name)
2072            .expect("Expected to find entity");
2073        let name_type = foo_type
2074            .attr("name")
2075            .expect("Expected attribute name")
2076            .attr_type
2077            .clone();
2078        let expected_name_type = Type::named_entity_reference(foo_name);
2079        assert_eq!(name_type, expected_name_type);
2080    }
2081
2082    #[test]
2083    fn cannot_declare_action_type_when_prohibited() {
2084        let schema_json: NamespaceDefinition = serde_json::from_str(
2085            r#"
2086            {
2087                "entityTypes": { "Action": {} },
2088                "actions": {}
2089              }
2090            "#,
2091        )
2092        .expect("Expected valid schema");
2093
2094        let schema: Result<ValidatorSchema> = schema_json.try_into();
2095        assert!(matches!(schema, Err(SchemaError::ActionEntityTypeDeclared)));
2096    }
2097
2098    #[test]
2099    fn can_declare_other_type_when_action_type_prohibited() {
2100        let schema_json: NamespaceDefinition = serde_json::from_str(
2101            r#"
2102            {
2103                "entityTypes": { "Foo": { } },
2104                "actions": {}
2105              }
2106            "#,
2107        )
2108        .expect("Expected valid schema");
2109
2110        TryInto::<ValidatorSchema>::try_into(schema_json).expect("Did not expect any errors.");
2111    }
2112
2113    #[test]
2114    fn cannot_declare_action_in_group_when_prohibited() {
2115        let schema_json: SchemaFragment = serde_json::from_str(
2116            r#"
2117            {"": {
2118                "entityTypes": {},
2119                "actions": {
2120                    "universe": { },
2121                    "view_photo": {
2122                        "attributes": {"id": "universe"}
2123                    },
2124                    "edit_photo": {
2125                        "attributes": {"id": "universe"}
2126                    },
2127                    "delete_photo": {
2128                        "attributes": {"id": "universe"}
2129                    }
2130                }
2131              }}
2132            "#,
2133        )
2134        .expect("Expected valid schema");
2135
2136        let schema = ValidatorSchemaFragment::from_schema_fragment(
2137            schema_json,
2138            ActionBehavior::ProhibitAttributes,
2139        );
2140        match schema {
2141            Err(SchemaError::ActionEntityAttributes(actions)) => {
2142                assert_eq!(
2143                    actions.into_iter().collect::<HashSet<_>>(),
2144                    HashSet::from([
2145                        "view_photo".to_string(),
2146                        "edit_photo".to_string(),
2147                        "delete_photo".to_string(),
2148                    ])
2149                )
2150            }
2151            _ => panic!("Did not see expected error."),
2152        }
2153    }
2154
2155    #[test]
2156    fn test_entity_type_no_namespace() {
2157        let src = json!({"type": "Entity", "name": "Foo"});
2158        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2159        assert_eq!(
2160            schema_ty,
2161            SchemaType::Type(SchemaTypeVariant::Entity { name: "Foo".into() })
2162        );
2163        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2164            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
2165            schema_ty,
2166        )
2167        .expect("Error converting schema type to type.")
2168        .resolve_type_defs(&HashMap::new())
2169        .unwrap();
2170        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2171    }
2172
2173    #[test]
2174    fn test_entity_type_namespace() {
2175        let src = json!({"type": "Entity", "name": "NS::Foo"});
2176        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2177        assert_eq!(
2178            schema_ty,
2179            SchemaType::Type(SchemaTypeVariant::Entity {
2180                name: "NS::Foo".into()
2181            })
2182        );
2183        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(
2184            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
2185            schema_ty,
2186        )
2187        .expect("Error converting schema type to type.")
2188        .resolve_type_defs(&HashMap::new())
2189        .unwrap();
2190        assert_eq!(ty, Type::named_entity_reference_from_str("NS::Foo"));
2191    }
2192
2193    #[test]
2194    fn test_entity_type_namespace_parse_error() {
2195        let src = json!({"type": "Entity", "name": "::Foo"});
2196        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2197        assert_eq!(
2198            schema_ty,
2199            SchemaType::Type(SchemaTypeVariant::Entity {
2200                name: "::Foo".into()
2201            })
2202        );
2203        match ValidatorNamespaceDef::try_schema_type_into_validator_type(
2204            Some(&Name::parse_unqualified_name("NS").expect("Expected namespace.")),
2205            schema_ty,
2206        ) {
2207            Err(SchemaError::EntityTypeParseError(_)) => (),
2208            _ => panic!("Did not see expected EntityTypeParseError."),
2209        }
2210    }
2211
2212    #[test]
2213    fn schema_type_record_is_validator_type_record() {
2214        let src = json!({"type": "Record", "attributes": {}});
2215        let schema_ty: SchemaType = serde_json::from_value(src).expect("Parse Error");
2216        assert_eq!(
2217            schema_ty,
2218            SchemaType::Type(SchemaTypeVariant::Record {
2219                attributes: BTreeMap::new(),
2220                additional_attributes: false,
2221            }),
2222        );
2223        let ty: Type = ValidatorNamespaceDef::try_schema_type_into_validator_type(None, schema_ty)
2224            .expect("Error converting schema type to type.")
2225            .resolve_type_defs(&HashMap::new())
2226            .unwrap();
2227        assert_eq!(ty, Type::record_with_attributes(None));
2228    }
2229
2230    #[test]
2231    fn get_namespaces() {
2232        let fragment: SchemaFragment = serde_json::from_value(json!({
2233            "Foo::Bar::Baz": {
2234                "entityTypes": {},
2235                "actions": {}
2236            },
2237            "Foo": {
2238                "entityTypes": {},
2239                "actions": {}
2240            },
2241            "Bar": {
2242                "entityTypes": {},
2243                "actions": {}
2244            },
2245        }))
2246        .unwrap();
2247
2248        let schema_fragment: ValidatorSchemaFragment = fragment.try_into().unwrap();
2249        assert_eq!(
2250            schema_fragment
2251                .0
2252                .iter()
2253                .map(|f| f.namespace())
2254                .collect::<HashSet<_>>(),
2255            HashSet::from([
2256                &Some("Foo::Bar::Baz".parse().unwrap()),
2257                &Some("Foo".parse().unwrap()),
2258                &Some("Bar".parse().unwrap())
2259            ])
2260        );
2261    }
2262
2263    #[test]
2264    fn schema_no_fragments() {
2265        let schema = ValidatorSchema::from_schema_fragments([]).unwrap();
2266        assert!(schema.entity_types.is_empty());
2267        assert!(schema.action_ids.is_empty());
2268    }
2269
2270    #[test]
2271    fn same_action_different_namespace() {
2272        let fragment: SchemaFragment = serde_json::from_value(json!({
2273            "Foo::Bar": {
2274                "entityTypes": {},
2275                "actions": {
2276                    "Baz": {}
2277                }
2278            },
2279            "Bar::Foo": {
2280                "entityTypes": {},
2281                "actions": {
2282                    "Baz": { }
2283                }
2284            },
2285            "Biz": {
2286                "entityTypes": {},
2287                "actions": {
2288                    "Baz": { }
2289                }
2290            }
2291        }))
2292        .unwrap();
2293
2294        let schema: ValidatorSchema = fragment.try_into().unwrap();
2295        assert!(schema
2296            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2297            .is_some());
2298        assert!(schema
2299            .get_action_id(&"Bar::Foo::Action::\"Baz\"".parse().unwrap())
2300            .is_some());
2301        assert!(schema
2302            .get_action_id(&"Biz::Action::\"Baz\"".parse().unwrap())
2303            .is_some());
2304    }
2305
2306    #[test]
2307    fn same_type_different_namespace() {
2308        let fragment: SchemaFragment = serde_json::from_value(json!({
2309            "Foo::Bar": {
2310                "entityTypes": {"Baz" : {}},
2311                "actions": { }
2312            },
2313            "Bar::Foo": {
2314                "entityTypes": {"Baz" : {}},
2315                "actions": { }
2316            },
2317            "Biz": {
2318                "entityTypes": {"Baz" : {}},
2319                "actions": { }
2320            }
2321        }))
2322        .unwrap();
2323        let schema: ValidatorSchema = fragment.try_into().unwrap();
2324
2325        assert!(schema
2326            .get_entity_type(&"Foo::Bar::Baz".parse().unwrap())
2327            .is_some());
2328        assert!(schema
2329            .get_entity_type(&"Bar::Foo::Baz".parse().unwrap())
2330            .is_some());
2331        assert!(schema
2332            .get_entity_type(&"Biz::Baz".parse().unwrap())
2333            .is_some());
2334    }
2335
2336    #[test]
2337    fn member_of_different_namespace() {
2338        let fragment: SchemaFragment = serde_json::from_value(json!({
2339            "Bar": {
2340                "entityTypes": {
2341                    "Baz": {
2342                        "memberOfTypes": ["Foo::Buz"]
2343                    }
2344                },
2345                "actions": {}
2346            },
2347            "Foo": {
2348                "entityTypes": { "Buz": {} },
2349                "actions": { }
2350            }
2351        }))
2352        .unwrap();
2353        let schema: ValidatorSchema = fragment.try_into().unwrap();
2354
2355        let buz = schema
2356            .get_entity_type(&"Foo::Buz".parse().unwrap())
2357            .unwrap();
2358        assert_eq!(
2359            buz.descendants,
2360            HashSet::from(["Bar::Baz".parse().unwrap()])
2361        );
2362    }
2363
2364    #[test]
2365    fn attribute_different_namespace() {
2366        let fragment: SchemaFragment = serde_json::from_value(json!({
2367            "Bar": {
2368                "entityTypes": {
2369                    "Baz": {
2370                        "shape": {
2371                            "type": "Record",
2372                            "attributes": {
2373                                "fiz": {
2374                                    "type": "Entity",
2375                                    "name": "Foo::Buz"
2376                                }
2377                            }
2378                        }
2379                    }
2380                },
2381                "actions": {}
2382            },
2383            "Foo": {
2384                "entityTypes": { "Buz": {} },
2385                "actions": { }
2386            }
2387        }))
2388        .unwrap();
2389
2390        let schema: ValidatorSchema = fragment.try_into().unwrap();
2391        let baz = schema
2392            .get_entity_type(&"Bar::Baz".parse().unwrap())
2393            .unwrap();
2394        assert_eq!(
2395            baz.attr("fiz").unwrap().attr_type,
2396            Type::named_entity_reference_from_str("Foo::Buz"),
2397        );
2398    }
2399
2400    #[test]
2401    fn applies_to_different_namespace() {
2402        let fragment: SchemaFragment = serde_json::from_value(json!({
2403            "Foo::Bar": {
2404                "entityTypes": { },
2405                "actions": {
2406                    "Baz": {
2407                        "appliesTo": {
2408                            "principalTypes": [ "Fiz::Buz" ],
2409                            "resourceTypes": [ "Fiz::Baz" ],
2410                        }
2411                    }
2412                }
2413            },
2414            "Fiz": {
2415                "entityTypes": {
2416                    "Buz": {},
2417                    "Baz": {}
2418                },
2419                "actions": { }
2420            }
2421        }))
2422        .unwrap();
2423        let schema: ValidatorSchema = fragment.try_into().unwrap();
2424
2425        let baz = schema
2426            .get_action_id(&"Foo::Bar::Action::\"Baz\"".parse().unwrap())
2427            .unwrap();
2428        assert_eq!(
2429            baz.applies_to
2430                .applicable_principal_types()
2431                .collect::<HashSet<_>>(),
2432            HashSet::from([&EntityType::Concrete("Fiz::Buz".parse().unwrap())])
2433        );
2434        assert_eq!(
2435            baz.applies_to
2436                .applicable_resource_types()
2437                .collect::<HashSet<_>>(),
2438            HashSet::from([&EntityType::Concrete("Fiz::Baz".parse().unwrap())])
2439        );
2440    }
2441
2442    #[test]
2443    fn simple_defined_type() {
2444        let fragment: SchemaFragment = serde_json::from_value(json!({
2445            "": {
2446                "commonTypes": {
2447                    "MyLong": {"type": "Long"}
2448                },
2449                "entityTypes": {
2450                    "User": {
2451                        "shape": {
2452                            "type": "Record",
2453                            "attributes": {
2454                                "a": {"type": "MyLong"}
2455                            }
2456                        }
2457                    }
2458                },
2459                "actions": {}
2460            }
2461        }))
2462        .unwrap();
2463        let schema: ValidatorSchema = fragment.try_into().unwrap();
2464        assert_eq!(
2465            schema.entity_types.iter().next().unwrap().1.attributes,
2466            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2467        );
2468    }
2469
2470    #[test]
2471    fn defined_record_as_attrs() {
2472        let fragment: SchemaFragment = serde_json::from_value(json!({
2473            "": {
2474                "commonTypes": {
2475                    "MyRecord": {
2476                        "type": "Record",
2477                        "attributes":  {
2478                            "a": {"type": "Long"}
2479                        }
2480                    }
2481                },
2482                "entityTypes": {
2483                    "User": { "shape": { "type": "MyRecord", } }
2484                },
2485                "actions": {}
2486            }
2487        }))
2488        .unwrap();
2489        let schema: ValidatorSchema = fragment.try_into().unwrap();
2490        assert_eq!(
2491            schema.entity_types.iter().next().unwrap().1.attributes,
2492            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2493        );
2494    }
2495
2496    #[test]
2497    fn cross_namespace_type() {
2498        let fragment: SchemaFragment = serde_json::from_value(json!({
2499            "A": {
2500                "commonTypes": {
2501                    "MyLong": {"type": "Long"}
2502                },
2503                "entityTypes": { },
2504                "actions": {}
2505            },
2506            "B": {
2507                "entityTypes": {
2508                    "User": {
2509                        "shape": {
2510                            "type": "Record",
2511                            "attributes": {
2512                                "a": {"type": "A::MyLong"}
2513                            }
2514                        }
2515                    }
2516                },
2517                "actions": {}
2518            }
2519        }))
2520        .unwrap();
2521        let schema: ValidatorSchema = fragment.try_into().unwrap();
2522        assert_eq!(
2523            schema.entity_types.iter().next().unwrap().1.attributes,
2524            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2525        );
2526    }
2527
2528    #[test]
2529    fn cross_fragment_type() {
2530        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2531            "A": {
2532                "commonTypes": {
2533                    "MyLong": {"type": "Long"}
2534                },
2535                "entityTypes": { },
2536                "actions": {}
2537            }
2538        }))
2539        .unwrap()
2540        .try_into()
2541        .unwrap();
2542        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2543            "A": {
2544                "entityTypes": {
2545                    "User": {
2546                        "shape": {
2547                            "type": "Record",
2548                            "attributes": {
2549                                "a": {"type": "MyLong"}
2550                            }
2551                        }
2552                    }
2553                },
2554                "actions": {}
2555            }
2556        }))
2557        .unwrap()
2558        .try_into()
2559        .unwrap();
2560        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2561
2562        assert_eq!(
2563            schema.entity_types.iter().next().unwrap().1.attributes,
2564            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2565        );
2566    }
2567
2568    #[test]
2569    #[should_panic]
2570    fn cross_fragment_duplicate_type() {
2571        let fragment1: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2572            "A": {
2573                "commonTypes": {
2574                    "MyLong": {"type": "Long"}
2575                },
2576                "entityTypes": {},
2577                "actions": {}
2578            }
2579        }))
2580        .unwrap()
2581        .try_into()
2582        .unwrap();
2583        let fragment2: ValidatorSchemaFragment = serde_json::from_value::<SchemaFragment>(json!({
2584            "A": {
2585                "commonTypes": {
2586                    "MyLong": {"type": "Long"}
2587                },
2588                "entityTypes": {},
2589                "actions": {}
2590            }
2591        }))
2592        .unwrap()
2593        .try_into()
2594        .unwrap();
2595        let schema = ValidatorSchema::from_schema_fragments([fragment1, fragment2]).unwrap();
2596
2597        assert_eq!(
2598            schema.entity_types.iter().next().unwrap().1.attributes,
2599            Attributes::with_required_attributes([("a".into(), Type::primitive_long())])
2600        );
2601    }
2602
2603    #[test]
2604    fn undeclared_type_in_attr() {
2605        let fragment: SchemaFragment = serde_json::from_value(json!({
2606            "": {
2607                "commonTypes": { },
2608                "entityTypes": {
2609                    "User": {
2610                        "shape": {
2611                            "type": "Record",
2612                            "attributes": {
2613                                "a": {"type": "MyLong"}
2614                            }
2615                        }
2616                    }
2617                },
2618                "actions": {}
2619            }
2620        }))
2621        .unwrap();
2622        match TryInto::<ValidatorSchema>::try_into(fragment) {
2623            Err(SchemaError::UndeclaredCommonType(_)) => (),
2624            s => panic!(
2625                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2626                s
2627            ),
2628        }
2629    }
2630
2631    #[test]
2632    fn undeclared_type_in_type_def() {
2633        let fragment: SchemaFragment = serde_json::from_value(json!({
2634            "": {
2635                "commonTypes": {
2636                    "a": { "type": "b" }
2637                },
2638                "entityTypes": { },
2639                "actions": {}
2640            }
2641        }))
2642        .unwrap();
2643        match TryInto::<ValidatorSchema>::try_into(fragment) {
2644            Err(SchemaError::UndeclaredCommonType(_)) => (),
2645            s => panic!(
2646                "Expected Err(SchemaError::UndeclaredCommonType), got {:?}",
2647                s
2648            ),
2649        }
2650    }
2651
2652    #[test]
2653    fn shape_not_record() {
2654        let fragment: SchemaFragment = serde_json::from_value(json!({
2655            "": {
2656                "commonTypes": {
2657                    "MyLong": { "type": "Long" }
2658                },
2659                "entityTypes": {
2660                    "User": {
2661                        "shape": { "type": "MyLong" }
2662                    }
2663                },
2664                "actions": {}
2665            }
2666        }))
2667        .unwrap();
2668        match TryInto::<ValidatorSchema>::try_into(fragment) {
2669            Err(SchemaError::ContextOrShapeNotRecord(_)) => (),
2670            s => panic!(
2671                "Expected Err(SchemaError::ContextOrShapeNotRecord), got {:?}",
2672                s
2673            ),
2674        }
2675    }
2676
2677    /// This test checks for regressions on (adapted versions of) the examples
2678    /// mentioned in the thread at
2679    /// [cedar#134](https://github.com/cedar-policy/cedar/pull/134)
2680    #[test]
2681    fn counterexamples_from_cedar_134() {
2682        // non-normalized entity type name
2683        let bad1 = json!({
2684            "": {
2685                "entityTypes": {
2686                    "User // comment": {
2687                        "memberOfTypes": [
2688                            "UserGroup"
2689                        ]
2690                    },
2691                    "User": {
2692                        "memberOfTypes": [
2693                            "UserGroup"
2694                        ]
2695                    },
2696                    "UserGroup": {}
2697                },
2698                "actions": {}
2699            }
2700        });
2701        let fragment = serde_json::from_value::<SchemaFragment>(bad1)
2702            .expect("constructing the fragment itself should succeed"); // should this fail in the future?
2703        let err = ValidatorSchema::try_from(fragment)
2704            .expect_err("should error due to invalid entity type name");
2705        assert!(
2706            err.to_string()
2707                .contains("needs to be normalized (e.g., whitespace removed): User // comment"),
2708            "actual error message was {err}"
2709        );
2710
2711        // non-normalized schema namespace
2712        let bad2 = json!({
2713            "ABC     :: //comment \n XYZ  ": {
2714                "entityTypes": {
2715                    "User": {
2716                        "memberOfTypes": []
2717                    }
2718                },
2719                "actions": {}
2720            }
2721        });
2722        let fragment = serde_json::from_value::<SchemaFragment>(bad2)
2723            .expect("constructing the fragment itself should succeed"); // should this fail in the future?
2724        let err = ValidatorSchema::try_from(fragment)
2725            .expect_err("should error due to invalid schema namespace");
2726        assert!(
2727            err.to_string().contains(
2728                "needs to be normalized (e.g., whitespace removed): ABC     :: //comment "
2729            ),
2730            "actual error message was {err}"
2731        );
2732    }
2733
2734    #[test]
2735    fn simple_action_entity() {
2736        let src = json!(
2737        {
2738            "entityTypes": { },
2739            "actions": {
2740                "view_photo": { },
2741            }
2742        });
2743
2744        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2745        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2746        let actions = schema.action_entities().expect("Entity Construct Error");
2747
2748        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2749        let view_photo = actions.entity(&action_uid);
2750        assert_eq!(
2751            view_photo.unwrap(),
2752            &Entity::new(action_uid, HashMap::new(), HashSet::new())
2753        );
2754    }
2755
2756    #[test]
2757    fn action_entity_hierarchy() {
2758        let src = json!(
2759        {
2760            "entityTypes": { },
2761            "actions": {
2762                "read": {},
2763                "view": {
2764                    "memberOf": [{"id": "read"}]
2765                },
2766                "view_photo": {
2767                    "memberOf": [{"id": "view"}]
2768                },
2769            }
2770        });
2771
2772        let schema_file: NamespaceDefinition = serde_json::from_value(src).expect("Parse Error");
2773        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2774        let actions = schema.action_entities().expect("Entity Construct Error");
2775
2776        let view_photo_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2777        let view_uid = EntityUID::from_str("Action::\"view\"").unwrap();
2778        let read_uid = EntityUID::from_str("Action::\"read\"").unwrap();
2779
2780        let view_photo_entity = actions.entity(&view_photo_uid);
2781        assert_eq!(
2782            view_photo_entity.unwrap(),
2783            &Entity::new(
2784                view_photo_uid,
2785                HashMap::new(),
2786                HashSet::from([view_uid.clone(), read_uid.clone()])
2787            )
2788        );
2789
2790        let view_entity = actions.entity(&view_uid);
2791        assert_eq!(
2792            view_entity.unwrap(),
2793            &Entity::new(view_uid, HashMap::new(), HashSet::from([read_uid.clone()]))
2794        );
2795
2796        let read_entity = actions.entity(&read_uid);
2797        assert_eq!(
2798            read_entity.unwrap(),
2799            &Entity::new(read_uid, HashMap::new(), HashSet::new())
2800        );
2801    }
2802
2803    #[test]
2804    fn action_entity_attribute() {
2805        let src = json!(
2806        {
2807            "entityTypes": { },
2808            "actions": {
2809                "view_photo": {
2810                    "attributes": { "attr": "foo" }
2811                },
2812            }
2813        });
2814
2815        let schema_file: NamespaceDefinitionWithActionAttributes =
2816            serde_json::from_value(src).expect("Parse Error");
2817        let schema: ValidatorSchema = schema_file.try_into().expect("Schema Error");
2818        let actions = schema.action_entities().expect("Entity Construct Error");
2819
2820        let action_uid = EntityUID::from_str("Action::\"view_photo\"").unwrap();
2821        let view_photo = actions.entity(&action_uid);
2822        assert_eq!(
2823            view_photo.unwrap(),
2824            &Entity::new(
2825                action_uid,
2826                HashMap::from([("attr".into(), RestrictedExpr::val("foo"))]),
2827                HashSet::new()
2828            )
2829        );
2830    }
2831
2832    #[test]
2833    fn test_action_namespace_inference_multi_success() {
2834        let src = json!({
2835            "Foo" : {
2836                "entityTypes" : {},
2837                "actions" : {
2838                    "read" : {}
2839                }
2840            },
2841            "ExampleCo::Personnel" : {
2842                "entityTypes" : {},
2843                "actions" : {
2844                    "viewPhoto" : {
2845                        "memberOf" : [
2846                            {
2847                                "id" : "read",
2848                                "type" : "Foo::Action"
2849                            }
2850                        ]
2851                    }
2852                }
2853            },
2854        });
2855        let schema_fragment =
2856            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2857        let schema: ValidatorSchema = schema_fragment.try_into().expect("Schema should construct");
2858        let view_photo = schema
2859            .action_entities_iter()
2860            .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2861            .unwrap();
2862        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2863        let read = ancestors[0];
2864        assert_eq!(read.eid().to_string(), "read");
2865        assert_eq!(read.entity_type().to_string(), "Foo::Action");
2866    }
2867
2868    #[test]
2869    fn test_action_namespace_inference_multi() {
2870        let src = json!({
2871            "ExampleCo::Personnel::Foo" : {
2872                "entityTypes" : {},
2873                "actions" : {
2874                    "read" : {}
2875                }
2876            },
2877            "ExampleCo::Personnel" : {
2878                "entityTypes" : {},
2879                "actions" : {
2880                    "viewPhoto" : {
2881                        "memberOf" : [
2882                            {
2883                                "id" : "read",
2884                                "type" : "Foo::Action"
2885                            }
2886                        ]
2887                    }
2888                }
2889            },
2890        });
2891        let schema_fragment =
2892            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2893        let schema: std::result::Result<ValidatorSchema, _> = schema_fragment.try_into();
2894        schema.expect_err("Schema should fail to construct as the normalization rules treat any qualification as starting from the root");
2895    }
2896
2897    #[test]
2898    fn test_action_namespace_inference() {
2899        let src = json!({
2900            "ExampleCo::Personnel" : {
2901                "entityTypes" : { },
2902                "actions" : {
2903                    "read" : {},
2904                    "viewPhoto" : {
2905                        "memberOf" : [
2906                            {
2907                                "id" :  "read",
2908                                "type" : "Action"
2909                            }
2910                        ]
2911                    }
2912                }
2913            }
2914        });
2915        let schema_fragment =
2916            serde_json::from_value::<SchemaFragment>(src).expect("Failed to parse schema");
2917        let schema: ValidatorSchema = schema_fragment.try_into().unwrap();
2918        let view_photo = schema
2919            .action_entities_iter()
2920            .find(|e| e.uid() == r#"ExampleCo::Personnel::Action::"viewPhoto""#.parse().unwrap())
2921            .unwrap();
2922        let ancestors = view_photo.ancestors().collect::<Vec<_>>();
2923        let read = ancestors[0];
2924        assert_eq!(read.eid().to_string(), "read");
2925        assert_eq!(
2926            read.entity_type().to_string(),
2927            "ExampleCo::Personnel::Action"
2928        );
2929    }
2930}