cedar_policy_validator/
schema.rs

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