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