cedar_policy_validator/schema/
namespace_def.rs

1/*
2 * Copyright Cedar Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17//! This module contains the definition of `ValidatorNamespaceDef` and of types
18//! it relies on
19
20use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
21
22use cedar_policy_core::{
23    ast::{
24        EntityAttrEvaluationError, EntityType, EntityUID, InternalName, Name,
25        PartialValueSerializedAsExpr, UnreservedId,
26    },
27    entities::{json::err::JsonDeserializationErrorContext, CedarValueJson},
28    evaluator::RestrictedEvaluator,
29    extensions::Extensions,
30    fuzzy_match::fuzzy_search,
31};
32use itertools::Itertools;
33use nonempty::{nonempty, NonEmpty};
34use smol_str::{SmolStr, ToSmolStr};
35
36use super::{internal_name_to_entity_type, AllDefs, ValidatorApplySpec};
37use crate::{
38    err::{schema_errors::*, SchemaError},
39    json_schema::{self, CommonTypeId},
40    types::{AttributeType, Attributes, OpenTag, Type},
41    ActionBehavior, ConditionalName, RawName, ReferenceType,
42};
43
44/// A single namespace definition from the schema JSON or Cedar syntax,
45/// processed into a form which is closer to that used by the validator.
46/// The processing includes detection of some errors, for example, parse errors
47/// in entity/common type names or entity/common types which are declared
48/// multiple times.
49///
50/// In this representation, there may still be references to undeclared
51/// entity/common types, because any entity/common type may be declared in a
52/// different fragment that will only be known about when building the complete
53/// [`crate::ValidatorSchema`].
54///
55/// The parameter `N` is the type of entity type names and common type names in
56/// attributes/parents fields in this [`ValidatorNamespaceDef`], including
57/// recursively. (It doesn't affect the type of common and entity type names
58/// _that are being declared here_, which are already fully-qualified in this
59/// representation. It only affects the type of common and entity type
60/// _references_.)
61/// For example:
62/// - `N` = [`ConditionalName`]: References to entity/common types are not
63///     yet fully qualified/disambiguated
64/// - `N` = [`InternalName`]: All references to entity/common types have been
65///     resolved into fully-qualified [`InternalName`]s
66///
67/// `A` is like `N`, but `A` governs typenames in `appliesTo` fields, while
68/// `N` governs all other type references.
69#[derive(Debug, Clone)]
70pub struct ValidatorNamespaceDef<N, A> {
71    /// The (fully-qualified) name of the namespace this is a definition of, or
72    /// `None` if this is a definition for the empty namespace.
73    ///
74    /// This is informational only; it does not change the semantics of any
75    /// definition in `common_types`, `entity_types`, or `actions`. All
76    /// entity/common type names in `common_types`, `entity_types`, and
77    /// `actions` are already either fully qualified/disambiguated, or stored in
78    /// [`ConditionalName`] format which does not require referencing the
79    /// implicit `namespace` directly any longer.
80    /// This `namespace` field is used only in tests and by the `cedar_policy`
81    /// function `SchemaFragment::namespaces()`.
82    namespace: Option<InternalName>,
83    /// Common type definitions, which can be used to define entity
84    /// type attributes, action contexts, and other common types.
85    pub(super) common_types: CommonTypeDefs<N>,
86    /// Entity type declarations.
87    pub(super) entity_types: EntityTypesDef<N>,
88    /// Action declarations.
89    pub(super) actions: ActionsDef<N, A>,
90}
91
92impl<N, A> ValidatorNamespaceDef<N, A> {
93    /// Get the fully-qualified [`InternalName`]s of all entity types declared
94    /// in this [`ValidatorNamespaceDef`].
95    pub fn all_declared_entity_type_names(&self) -> impl Iterator<Item = &InternalName> {
96        self.entity_types
97            .defs
98            .keys()
99            .map(|ety| ety.as_ref().as_ref())
100    }
101
102    /// Get the fully-qualified [`InternalName`]s of all common types declared
103    /// in this [`ValidatorNamespaceDef`].
104    pub fn all_declared_common_type_names(&self) -> impl Iterator<Item = &InternalName> {
105        self.common_types.defs.keys()
106    }
107
108    /// Get the fully-qualified [`EntityUID`]s of all actions declared in this
109    /// [`ValidatorNamespaceDef`].
110    pub fn all_declared_action_names(&self) -> impl Iterator<Item = &EntityUID> {
111        self.actions.actions.keys()
112    }
113
114    /// The fully-qualified [`InternalName`] of the namespace this is a definition of.
115    /// `None` indicates this definition is for the empty namespace.
116    pub fn namespace(&self) -> Option<&InternalName> {
117        self.namespace.as_ref()
118    }
119}
120
121impl ValidatorNamespaceDef<ConditionalName, ConditionalName> {
122    /// Construct a new [`ValidatorNamespaceDef<ConditionalName>`] from the raw [`json_schema::NamespaceDefinition`]
123    pub fn from_namespace_definition(
124        namespace: Option<InternalName>,
125        namespace_def: json_schema::NamespaceDefinition<RawName>,
126        action_behavior: ActionBehavior,
127        extensions: &Extensions<'_>,
128    ) -> crate::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>> {
129        // Return early with an error if actions cannot be in groups or have
130        // attributes, but the schema contains action groups or attributes.
131        Self::check_action_behavior(&namespace_def, action_behavior)?;
132
133        // Convert the common types, actions and entity types from the schema
134        // file into the representation used by the validator.
135        let common_types = CommonTypeDefs::from_raw_common_types(
136            namespace_def
137                .common_types
138                .into_iter()
139                .map(|(key, value)| (key, value.ty)),
140            namespace.as_ref(),
141        )?;
142        let actions =
143            ActionsDef::from_raw_actions(namespace_def.actions, namespace.as_ref(), extensions)?;
144        let entity_types =
145            EntityTypesDef::from_raw_entity_types(namespace_def.entity_types, namespace.as_ref())?;
146
147        Ok(ValidatorNamespaceDef {
148            namespace,
149            common_types,
150            entity_types,
151            actions,
152        })
153    }
154
155    /// Construct a new [`ValidatorNamespaceDef<ConditionalName>`] containing
156    /// only the given common-type definitions, which are already given in
157    /// terms of [`ConditionalName`]s.
158    pub fn from_common_type_defs(
159        namespace: Option<InternalName>,
160        defs: HashMap<UnreservedId, json_schema::Type<ConditionalName>>,
161    ) -> crate::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>> {
162        let common_types = CommonTypeDefs::from_conditionalname_typedefs(defs, namespace.as_ref())?;
163        Ok(ValidatorNamespaceDef {
164            namespace,
165            common_types,
166            entity_types: EntityTypesDef::new(),
167            actions: ActionsDef::new(),
168        })
169    }
170
171    /// Construct a new [`ValidatorNamespaceDef<ConditionalName>`] containing
172    /// only a single given common-type definition, which is already given in
173    /// terms of [`ConditionalName`]s.
174    ///
175    /// Unlike `from_common_type_defs()`, this function cannot fail, because
176    /// there is only one def so it cannot have a name collision with itself
177    pub fn from_common_type_def(
178        namespace: Option<InternalName>,
179        def: (UnreservedId, json_schema::Type<ConditionalName>),
180    ) -> ValidatorNamespaceDef<ConditionalName, ConditionalName> {
181        let common_types = CommonTypeDefs::from_conditionalname_typedef(def, namespace.as_ref());
182        ValidatorNamespaceDef {
183            namespace,
184            common_types,
185            entity_types: EntityTypesDef::new(),
186            actions: ActionsDef::new(),
187        }
188    }
189
190    /// Convert this [`ValidatorNamespaceDef<ConditionalName>`] into a
191    /// [`ValidatorNamespaceDef<InternalName>`] by fully-qualifying all
192    /// typenames that appear anywhere in any definitions.
193    ///
194    /// `all_defs` needs to contain the full set of all fully-qualified typenames
195    /// and actions that are defined in the schema (in all schema fragments).
196    pub fn fully_qualify_type_references(
197        self,
198        all_defs: &AllDefs,
199    ) -> Result<ValidatorNamespaceDef<InternalName, EntityType>, SchemaError> {
200        match (
201            self.common_types.fully_qualify_type_references(all_defs),
202            self.entity_types.fully_qualify_type_references(all_defs),
203            self.actions.fully_qualify_type_references(all_defs),
204        ) {
205            (Ok(common_types), Ok(entity_types), Ok(actions)) => Ok(ValidatorNamespaceDef {
206                namespace: self.namespace,
207                common_types,
208                entity_types,
209                actions,
210            }),
211            (res1, res2, res3) => {
212                // PANIC SAFETY: at least one of the results is `Err`, so the input to `NonEmpty::collect()` cannot be an empty iterator
213                #[allow(clippy::expect_used)]
214                let errs = NonEmpty::collect(
215                    res1.err()
216                        .into_iter()
217                        .map(SchemaError::from)
218                        .chain(res2.err().map(SchemaError::from))
219                        .chain(res3.err().map(SchemaError::from)),
220                )
221                .expect("there must be an error");
222                Err(SchemaError::join_nonempty(errs))
223            }
224        }
225    }
226
227    /// Check that `schema_nsdef` uses actions in a way consistent with the
228    /// specified `action_behavior`. When the behavior specifies that actions
229    /// should not be used in groups and should not have attributes, then this
230    /// function will return `Err` if it sees any action groups or attributes
231    /// declared in the schema.
232    fn check_action_behavior<N>(
233        schema_nsdef: &json_schema::NamespaceDefinition<N>,
234        action_behavior: ActionBehavior,
235    ) -> crate::err::Result<()> {
236        if schema_nsdef
237            .entity_types
238            .iter()
239            // The `name` in an entity type declaration cannot be qualified
240            // with a namespace (it always implicitly takes the schema
241            // namespace), so we do this comparison directly.
242            .any(|(name, _)| name.to_smolstr() == cedar_policy_core::ast::ACTION_ENTITY_TYPE)
243        {
244            return Err(ActionEntityTypeDeclaredError {}.into());
245        }
246        if action_behavior == ActionBehavior::ProhibitAttributes {
247            let mut actions_with_attributes: Vec<String> = Vec::new();
248            for (name, a) in &schema_nsdef.actions {
249                if a.attributes.is_some() {
250                    actions_with_attributes.push(name.to_string());
251                }
252            }
253            if !actions_with_attributes.is_empty() {
254                actions_with_attributes.sort(); // TODO(#833): sort required for deterministic error messages
255                return Err(
256                    UnsupportedFeatureError(UnsupportedFeature::ActionAttributes(
257                        actions_with_attributes,
258                    ))
259                    .into(),
260                );
261            }
262        }
263
264        Ok(())
265    }
266}
267
268/// Holds a map from (fully qualified) [`InternalName`]s of common type
269/// definitions to their corresponding [`json_schema::Type`]. The common type
270/// [`InternalName`]s (keys in the map) are fully qualified, but inside the
271/// [`json_schema::Type`]s (values in the map), entity/common type references may or
272/// may not be fully qualified yet, depending on `N`; see notes on
273/// [`json_schema::Type`].
274#[derive(Debug, Clone)]
275pub struct CommonTypeDefs<N> {
276    pub(super) defs: HashMap<InternalName, json_schema::Type<N>>,
277}
278
279impl CommonTypeDefs<ConditionalName> {
280    /// Construct a [`CommonTypeDefs<ConditionalName>`] by converting the
281    /// structures used by the schema format to those used internally by the
282    /// validator.
283    pub(crate) fn from_raw_common_types(
284        schema_file_type_def: impl IntoIterator<Item = (CommonTypeId, json_schema::Type<RawName>)>,
285        schema_namespace: Option<&InternalName>,
286    ) -> crate::err::Result<Self> {
287        let mut defs = HashMap::new();
288        for (id, schema_ty) in schema_file_type_def {
289            let name = RawName::new_from_unreserved(id.into()).qualify_with(schema_namespace); // the declaration name is always (unconditionally) prefixed by the current/active namespace
290            match defs.entry(name) {
291                Entry::Vacant(ventry) => {
292                    ventry
293                        .insert(schema_ty.conditionally_qualify_type_references(schema_namespace));
294                }
295                Entry::Occupied(oentry) => {
296                    return Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError {
297                        ty: oentry.key().clone(),
298                    }));
299                }
300            }
301        }
302        Ok(Self { defs })
303    }
304
305    /// Construct a [`CommonTypeDefs<ConditionalName>`] by converting the
306    /// structures used by the schema format to those used internally by the
307    /// validator; but unlike `from_raw_common_types()`, this function allows you to
308    /// directly supply [`ConditionalName`]s in the typedefs
309    pub(crate) fn from_conditionalname_typedefs(
310        input_type_defs: HashMap<UnreservedId, json_schema::Type<ConditionalName>>,
311        schema_namespace: Option<&InternalName>,
312    ) -> crate::err::Result<Self> {
313        let mut defs = HashMap::with_capacity(input_type_defs.len());
314        for (id, schema_ty) in input_type_defs {
315            let name = RawName::new_from_unreserved(id).qualify_with(schema_namespace); // the declaration name is always (unconditionally) prefixed by the current/active namespace
316            match defs.entry(name) {
317                Entry::Vacant(ventry) => {
318                    ventry.insert(schema_ty);
319                }
320                Entry::Occupied(oentry) => {
321                    return Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError {
322                        ty: oentry.key().clone(),
323                    }));
324                }
325            }
326        }
327        Ok(Self { defs })
328    }
329
330    /// Construct a [`CommonTypeDefs<ConditionalName>`] representing a single
331    /// typedef in the given namespace.
332    ///
333    /// Unlike [`from_conditionalname_typedefs()`], this function cannot fail,
334    /// because there is only one typedef so it cannot have a name collision
335    /// with itself
336    pub(crate) fn from_conditionalname_typedef(
337        (id, schema_ty): (UnreservedId, json_schema::Type<ConditionalName>),
338        schema_namespace: Option<&InternalName>,
339    ) -> Self {
340        Self {
341            defs: HashMap::from_iter([(
342                RawName::new_from_unreserved(id).qualify_with(schema_namespace),
343                schema_ty,
344            )]),
345        }
346    }
347
348    /// Convert this [`CommonTypeDefs<ConditionalName>`] into a
349    /// [`CommonTypeDefs<InternalName>`] by fully-qualifying all typenames that
350    /// appear anywhere in any definitions.
351    ///
352    /// `all_defs` needs to contain the full set of all fully-qualified typenames
353    /// and actions that are defined in the schema (in all schema fragments).
354    pub fn fully_qualify_type_references(
355        self,
356        all_defs: &AllDefs,
357    ) -> Result<CommonTypeDefs<InternalName>, TypeNotDefinedError> {
358        Ok(CommonTypeDefs {
359            defs: self
360                .defs
361                .into_iter()
362                .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
363                .collect::<Result<_, TypeNotDefinedError>>()?,
364        })
365    }
366}
367
368/// Holds a map from (fully qualified) [`EntityType`]s (names of entity types) to
369/// their corresponding [`EntityTypeFragment`]. The [`EntityType`] keys in
370/// the map are fully qualified, but inside the [`EntityTypeFragment`]s (values
371/// in the map), entity/common type references may or may not be fully qualified
372/// yet, depending on `N`; see notes on [`EntityTypeFragment`].
373///
374/// Inside the [`EntityTypeFragment`]s, entity type parents and attributes may
375/// reference undeclared entity/common types (that will be declared in a
376/// different schema fragment).
377///
378/// All [`EntityType`] keys in this map are declared in this schema fragment.
379#[derive(Debug, Clone)]
380pub struct EntityTypesDef<N> {
381    pub(super) defs: HashMap<EntityType, EntityTypeFragment<N>>,
382}
383
384impl<N> EntityTypesDef<N> {
385    /// Construct an empty [`EntityTypesDef`] defining no entity types.
386    pub fn new() -> Self {
387        Self {
388            defs: HashMap::new(),
389        }
390    }
391}
392
393impl EntityTypesDef<ConditionalName> {
394    /// Construct a [`EntityTypesDef<ConditionalName>`] by converting the
395    /// structures used by the schema format to those used internally by the
396    /// validator.
397    pub(crate) fn from_raw_entity_types(
398        schema_files_types: impl IntoIterator<Item = (UnreservedId, json_schema::EntityType<RawName>)>,
399        schema_namespace: Option<&InternalName>,
400    ) -> crate::err::Result<Self> {
401        let mut defs: HashMap<EntityType, _> = HashMap::new();
402        for (id, entity_type) in schema_files_types {
403            let ety = internal_name_to_entity_type(
404                RawName::new_from_unreserved(id).qualify_with(schema_namespace), // the declaration name is always (unconditionally) prefixed by the current/active namespace
405            )?;
406            match defs.entry(ety) {
407                Entry::Vacant(ventry) => {
408                    ventry.insert(EntityTypeFragment::from_raw_entity_type(
409                        entity_type,
410                        schema_namespace,
411                    ));
412                }
413                Entry::Occupied(entry) => {
414                    return Err(DuplicateEntityTypeError {
415                        ty: entry.key().clone(),
416                    }
417                    .into());
418                }
419            }
420        }
421        Ok(EntityTypesDef { defs })
422    }
423
424    /// Convert this [`EntityTypesDef<ConditionalName>`] into a
425    /// [`EntityTypesDef<InternalName>`] by fully-qualifying all typenames that
426    /// appear anywhere in any definitions.
427    ///
428    /// `all_defs` needs to contain the full set of all fully-qualified typenames
429    /// and actions that are defined in the schema (in all schema fragments).
430    pub fn fully_qualify_type_references(
431        self,
432        all_defs: &AllDefs,
433    ) -> Result<EntityTypesDef<InternalName>, TypeNotDefinedError> {
434        Ok(EntityTypesDef {
435            defs: self
436                .defs
437                .into_iter()
438                .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
439                .collect::<Result<_, TypeNotDefinedError>>()?,
440        })
441    }
442}
443
444/// Holds the attributes and parents information for an entity type definition.
445///
446/// In this representation, references to common types may not yet have been
447/// fully resolved/inlined, and `parents`, `attributes`, and `tags` may all
448/// reference undeclared entity/common types. Furthermore, entity/common type
449/// references in `parents`, `attributes`, and `tags` may or may not be fully
450/// qualified yet, depending on `N`.
451#[derive(Debug, Clone)]
452pub struct EntityTypeFragment<N> {
453    /// Description of the attribute types for this entity type.
454    ///
455    /// This may contain references to common types which have not yet been
456    /// resolved/inlined (e.g., because they are not defined in this schema
457    /// fragment).
458    /// In the extreme case, this may itself be just a common type pointing to a
459    /// `Record` type defined in another fragment.
460    pub(super) attributes: json_schema::AttributesOrContext<N>,
461    /// Direct parent entity types for this entity type.
462    /// These entity types may be declared in a different namespace or schema
463    /// fragment.
464    ///
465    /// We will check for undeclared parent types when combining fragments into
466    /// a [`crate::ValidatorSchema`].
467    pub(super) parents: HashSet<N>,
468    /// Tag type for this entity type. `None` means no tags are allowed on this
469    /// entity type.
470    ///
471    /// This may contain references to common types which have not yet been
472    /// resolved/inlined (e.g., because they are not defined in this schema
473    /// fragment).
474    pub(super) tags: Option<json_schema::Type<N>>,
475}
476
477impl EntityTypeFragment<ConditionalName> {
478    /// Construct a [`EntityTypeFragment<ConditionalName>`] by converting the
479    /// structures used by the schema format to those used internally by the
480    /// validator.
481    pub(crate) fn from_raw_entity_type(
482        schema_file_type: json_schema::EntityType<RawName>,
483        schema_namespace: Option<&InternalName>,
484    ) -> Self {
485        Self {
486            attributes: schema_file_type
487                .shape
488                .conditionally_qualify_type_references(schema_namespace),
489            parents: schema_file_type
490                .member_of_types
491                .into_iter()
492                .map(|raw_name| {
493                    // Only entity, not common, here for now; see #1064
494                    raw_name.conditionally_qualify_with(schema_namespace, ReferenceType::Entity)
495                })
496                .collect(),
497            tags: schema_file_type
498                .tags
499                .map(|tags| tags.conditionally_qualify_type_references(schema_namespace)),
500        }
501    }
502
503    /// Convert this [`EntityTypeFragment<ConditionalName>`] into a
504    /// [`EntityTypeFragment<InternalName>`] by fully-qualifying all typenames that
505    /// appear anywhere in any definitions.
506    ///
507    /// `all_defs` needs to contain the full set of all fully-qualified typenames
508    /// and actions that are defined in the schema (in all schema fragments).
509    pub fn fully_qualify_type_references(
510        self,
511        all_defs: &AllDefs,
512    ) -> Result<EntityTypeFragment<InternalName>, TypeNotDefinedError> {
513        // Fully qualify typenames appearing in `attributes`
514        let fully_qual_attributes = self.attributes.fully_qualify_type_references(all_defs);
515        // Fully qualify typenames appearing in `parents`
516        let parents: HashSet<InternalName> = self
517            .parents
518            .into_iter()
519            .map(|parent| parent.resolve(all_defs))
520            .collect::<Result<_, TypeNotDefinedError>>()?;
521        // Fully qualify typenames appearing in `tags`
522        let fully_qual_tags = self
523            .tags
524            .map(|tags| tags.fully_qualify_type_references(all_defs))
525            .transpose();
526        // Now is the time to check whether any parents are dangling, i.e.,
527        // refer to entity types that are not declared in any fragment (since we
528        // now have the set of typenames that are declared in all fragments).
529        let undeclared_parents: Option<NonEmpty<ConditionalName>> = NonEmpty::collect(
530            parents
531                .iter()
532                .filter(|ety| !all_defs.is_defined_as_entity(ety))
533                .map(|ety| ConditionalName::unconditional(ety.clone(), ReferenceType::Entity)),
534        );
535        match (fully_qual_attributes, fully_qual_tags, undeclared_parents) {
536            (Ok(attributes), Ok(tags), None) => Ok(EntityTypeFragment {
537                attributes,
538                parents,
539                tags,
540            }),
541            (Ok(_), Ok(_), Some(undeclared_parents)) => Err(TypeNotDefinedError {
542                undefined_types: undeclared_parents,
543            }),
544            (Err(e), Ok(_), None) | (Ok(_), Err(e), None) => Err(e),
545            (Err(e1), Err(e2), None) => Err(TypeNotDefinedError::join_nonempty(nonempty![e1, e2])),
546            (Err(e), Ok(_), Some(mut undeclared)) | (Ok(_), Err(e), Some(mut undeclared)) => {
547                undeclared.extend(e.undefined_types);
548                Err(TypeNotDefinedError {
549                    undefined_types: undeclared,
550                })
551            }
552            (Err(e1), Err(e2), Some(mut undeclared)) => {
553                undeclared.extend(e1.undefined_types);
554                undeclared.extend(e2.undefined_types);
555                Err(TypeNotDefinedError {
556                    undefined_types: undeclared,
557                })
558            }
559        }
560    }
561}
562
563/// Holds a map from (fully qualified) [`EntityUID`]s of action definitions
564/// to their corresponding [`ActionFragment`]. The action [`EntityUID`]s (keys
565/// in the map) are fully qualified, but inside the [`ActionFragment`]s (values
566/// in the map), entity/common type references (including references to other actions)
567/// may or may not be fully qualified yet, depending on `N` and `A`. See notes
568/// on [`ActionFragment`].
569///
570/// The [`ActionFragment`]s may also reference undeclared entity/common types
571/// and actions (that will be declared in a different schema fragment).
572///
573/// The current schema format specification does not include multiple action entity
574/// types. All action entities are required to use a single `Action` entity
575/// type. However, the action entity type may be namespaced, so an action entity
576/// may have a fully qualified entity type `My::Namespace::Action`.
577#[derive(Debug, Clone)]
578pub struct ActionsDef<N, A> {
579    pub(super) actions: HashMap<EntityUID, ActionFragment<N, A>>,
580}
581
582impl<N, A> ActionsDef<N, A> {
583    /// Construct an empty [`ActionsDef`] defining no entity types.
584    pub fn new() -> Self {
585        Self {
586            actions: HashMap::new(),
587        }
588    }
589}
590
591impl ActionsDef<ConditionalName, ConditionalName> {
592    /// Construct an [`ActionsDef<ConditionalName>`] by converting the structures used by the
593    /// schema format to those used internally by the validator.
594    pub(crate) fn from_raw_actions(
595        schema_file_actions: impl IntoIterator<Item = (SmolStr, json_schema::ActionType<RawName>)>,
596        schema_namespace: Option<&InternalName>,
597        extensions: &Extensions<'_>,
598    ) -> crate::err::Result<Self> {
599        let mut actions = HashMap::new();
600        for (action_id_str, action_type) in schema_file_actions {
601            let action_uid = json_schema::ActionEntityUID::default_type(action_id_str.clone())
602                .qualify_with(schema_namespace); // the declaration name is always (unconditionally) prefixed by the current/active namespace
603            match actions.entry(action_uid.try_into()?) {
604                Entry::Vacant(ventry) => {
605                    let frag = ActionFragment::from_raw_action(
606                        ventry.key(),
607                        action_type,
608                        schema_namespace,
609                        extensions,
610                    )?;
611                    ventry.insert(frag);
612                }
613                Entry::Occupied(_) => {
614                    return Err(DuplicateActionError(action_id_str).into());
615                }
616            }
617        }
618        Ok(Self { actions })
619    }
620
621    /// Convert this [`ActionsDef<ConditionalName>`] into a
622    /// [`ActionsDef<InternalName>`] by fully-qualifying all typenames that
623    /// appear anywhere in any definitions.
624    ///
625    /// `all_defs` needs to contain the full set of all fully-qualified typenames
626    /// and actions that are defined in the schema (in all schema fragments).
627    pub fn fully_qualify_type_references(
628        self,
629        all_defs: &AllDefs,
630    ) -> Result<ActionsDef<InternalName, EntityType>, SchemaError> {
631        Ok(ActionsDef {
632            actions: self
633                .actions
634                .into_iter()
635                .map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
636                .collect::<Result<_, SchemaError>>()?,
637        })
638    }
639}
640
641/// Holds the information about an action that comprises an action definition.
642///
643/// In this representation, references to common types may not yet have been
644/// fully resolved/inlined, and entity/common type references (including
645/// references to other actions) may not yet be fully qualified, depending on
646/// `N` and `A`. This [`ActionFragment`] may also reference undeclared entity/common
647/// types and actions (that will be declared in a different schema fragment).
648///
649/// `A` is used for typenames in `applies_to`, and `N` is used for all other
650/// type references.
651#[derive(Debug, Clone)]
652pub struct ActionFragment<N, A> {
653    /// The type of the context record for this action. This may contain
654    /// references to common types which have not yet been resolved/inlined
655    /// (e.g., because they are not defined in this schema fragment).
656    pub(super) context: json_schema::Type<N>,
657    /// The principals and resources that an action can be applied to.
658    pub(super) applies_to: ValidatorApplySpec<A>,
659    /// The direct parent action entities for this action.
660    /// These may be actions declared in a different namespace or schema
661    /// fragment, and thus not declared yet.
662    /// We will check for undeclared parents when combining fragments into a
663    /// [`crate::ValidatorSchema`].
664    pub(super) parents: HashSet<json_schema::ActionEntityUID<N>>,
665    /// The types for the attributes defined for this actions entity.
666    /// Here, common types have been fully resolved/inlined.
667    pub(super) attribute_types: Attributes,
668    /// The values for the attributes defined for this actions entity, stored
669    /// separately so that we can later extract these values to construct the
670    /// actual `Entity` objects defined by the schema.
671    pub(super) attributes: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
672}
673
674impl ActionFragment<ConditionalName, ConditionalName> {
675    pub(crate) fn from_raw_action(
676        action_uid: &EntityUID,
677        action_type: json_schema::ActionType<RawName>,
678        schema_namespace: Option<&InternalName>,
679        extensions: &Extensions<'_>,
680    ) -> crate::err::Result<Self> {
681        let (principal_types, resource_types, context) = action_type
682            .applies_to
683            .map(|applies_to| {
684                (
685                    applies_to.principal_types,
686                    applies_to.resource_types,
687                    applies_to.context,
688                )
689            })
690            .unwrap_or_default();
691        let (attribute_types, attributes) = Self::convert_attr_jsonval_map_to_attributes(
692            action_type.attributes.unwrap_or_default(),
693            action_uid,
694            extensions,
695        )?;
696        Ok(Self {
697            context: context
698                .into_inner()
699                .conditionally_qualify_type_references(schema_namespace),
700            applies_to: ValidatorApplySpec::<ConditionalName>::new(
701                principal_types
702                    .into_iter()
703                    .map(|pty| {
704                        pty.conditionally_qualify_with(schema_namespace, ReferenceType::Entity)
705                    })
706                    .collect(),
707                resource_types
708                    .into_iter()
709                    .map(|rty| {
710                        rty.conditionally_qualify_with(schema_namespace, ReferenceType::Entity)
711                    })
712                    .collect(),
713            ),
714            parents: action_type
715                .member_of
716                .unwrap_or_default()
717                .into_iter()
718                .map(|parent| parent.conditionally_qualify_type_references(schema_namespace))
719                .collect(),
720            attribute_types,
721            attributes,
722        })
723    }
724
725    /// Convert this [`ActionFragment<ConditionalName>`] into an
726    /// [`ActionFragment<InternalName>`] by fully-qualifying all typenames that
727    /// appear anywhere in any definitions.
728    ///
729    /// `all_defs` needs to contain the full set of all fully-qualified typenames
730    /// and actions that are defined in the schema (in all schema fragments).
731    pub fn fully_qualify_type_references(
732        self,
733        all_defs: &AllDefs,
734    ) -> Result<ActionFragment<InternalName, EntityType>, SchemaError> {
735        Ok(ActionFragment {
736            context: self.context.fully_qualify_type_references(all_defs)?,
737            applies_to: self.applies_to.fully_qualify_type_references(all_defs)?,
738            parents: self
739                .parents
740                .into_iter()
741                .map(|parent| {
742                    parent
743                        .fully_qualify_type_references(all_defs)
744                        .map_err(Into::into)
745                })
746                .collect::<Result<_, SchemaError>>()?,
747            attribute_types: self.attribute_types,
748            attributes: self.attributes,
749        })
750    }
751
752    fn convert_attr_jsonval_map_to_attributes(
753        m: HashMap<SmolStr, CedarValueJson>,
754        action_id: &EntityUID,
755        extensions: &Extensions<'_>,
756    ) -> crate::err::Result<(Attributes, BTreeMap<SmolStr, PartialValueSerializedAsExpr>)> {
757        let mut attr_types: HashMap<SmolStr, Type> = HashMap::with_capacity(m.len());
758        let mut attr_values: BTreeMap<SmolStr, PartialValueSerializedAsExpr> = BTreeMap::new();
759        let evaluator = RestrictedEvaluator::new(extensions);
760
761        for (k, v) in m {
762            let t = Self::jsonval_to_type_helper(&v, action_id);
763            match t {
764                Ok(ty) => attr_types.insert(k.clone(), ty),
765                Err(e) => return Err(e),
766            };
767
768            // As an artifact of the limited `CedarValueJson` variants accepted by
769            // `Self::jsonval_to_type_helper`, we know that this function will
770            // never error. Also note that this is only ever executed when
771            // action attributes are enabled, but they cannot be enabled when
772            // using Cedar through the public API. This is fortunate because
773            // handling an error here would mean adding a new error variant to
774            // `SchemaError` in the public API, but we didn't make that enum
775            // `non_exhaustive`, so any new variants are a breaking change.
776            // PANIC SAFETY: see above
777            #[allow(clippy::expect_used)]
778            let e = v.into_expr(|| JsonDeserializationErrorContext::EntityAttribute { uid: action_id.clone(), attr: k.clone() }).expect("`Self::jsonval_to_type_helper` will always return `Err` for a `CedarValueJson` that might make `into_expr` return `Err`");
779            let pv = evaluator
780                .partial_interpret(e.as_borrowed())
781                .map_err(|err| {
782                    ActionAttrEvalError(EntityAttrEvaluationError {
783                        uid: action_id.clone(),
784                        attr_or_tag: k.clone(),
785                        was_attr: true,
786                        err,
787                    })
788                })?;
789            attr_values.insert(k.clone(), pv.into());
790        }
791        Ok((
792            Attributes::with_required_attributes(attr_types),
793            attr_values,
794        ))
795    }
796
797    /// Helper to get types from `CedarValueJson`s. Currently doesn't support all
798    /// `CedarValueJson` types. Note: If this function is extended to cover move
799    /// `CedarValueJson`s, we must update `convert_attr_jsonval_map_to_attributes` to
800    /// handle errors that may occur when parsing these values. This will require
801    /// a breaking change in the `SchemaError` type in the public API.
802    fn jsonval_to_type_helper(
803        v: &CedarValueJson,
804        action_id: &EntityUID,
805    ) -> crate::err::Result<Type> {
806        match v {
807            CedarValueJson::Bool(_) => Ok(Type::primitive_boolean()),
808            CedarValueJson::Long(_) => Ok(Type::primitive_long()),
809            CedarValueJson::String(_) => Ok(Type::primitive_string()),
810            CedarValueJson::Record(r) => {
811                let mut required_attrs: HashMap<SmolStr, Type> = HashMap::with_capacity(r.len());
812                for (k, v_prime) in r {
813                    let t = Self::jsonval_to_type_helper(v_prime, action_id);
814                    match t {
815                        Ok(ty) => required_attrs.insert(k.clone(), ty),
816                        Err(e) => return Err(e),
817                    };
818                }
819                Ok(Type::record_with_required_attributes(
820                    required_attrs,
821                    OpenTag::ClosedAttributes,
822                ))
823            }
824            CedarValueJson::Set(v) => match v.first() {
825                //sets with elements of different types will be rejected elsewhere
826                None => Err(ActionAttributesContainEmptySetError {
827                    uid: action_id.clone(),
828                }
829                .into()),
830                Some(element) => {
831                    let element_type = Self::jsonval_to_type_helper(element, action_id);
832                    match element_type {
833                        Ok(t) => Ok(Type::Set {
834                            element_type: Some(Box::new(t)),
835                        }),
836                        Err(_) => element_type,
837                    }
838                }
839            },
840            CedarValueJson::EntityEscape { __entity: _ } => Err(UnsupportedActionAttributeError {
841                uid: action_id.clone(),
842                attr: "entity escape (`__entity`)".into(),
843            }
844            .into()),
845            CedarValueJson::ExprEscape { __expr: _ } => Err(UnsupportedActionAttributeError {
846                uid: action_id.clone(),
847                attr: "expression escape (`__expr`)".into(),
848            }
849            .into()),
850            CedarValueJson::ExtnEscape { __extn: _ } => Err(UnsupportedActionAttributeError {
851                uid: action_id.clone(),
852                attr: "extension function escape (`__extn`)".into(),
853            }
854            .into()),
855            CedarValueJson::Null => Err(UnsupportedActionAttributeError {
856                uid: action_id.clone(),
857                attr: "null".into(),
858            }
859            .into()),
860        }
861    }
862}
863
864type ResolveFunc<T> = dyn FnOnce(&HashMap<&InternalName, Type>) -> crate::err::Result<T>;
865/// Represent a type that might be defined in terms of some common-type
866/// definitions which are not necessarily available in the current namespace.
867pub(crate) enum WithUnresolvedCommonTypeRefs<T> {
868    WithUnresolved(Box<ResolveFunc<T>>),
869    WithoutUnresolved(T),
870}
871
872impl<T: 'static> WithUnresolvedCommonTypeRefs<T> {
873    pub fn new(
874        f: impl FnOnce(&HashMap<&InternalName, Type>) -> crate::err::Result<T> + 'static,
875    ) -> Self {
876        Self::WithUnresolved(Box::new(f))
877    }
878
879    pub fn map<U: 'static>(
880        self,
881        f: impl FnOnce(T) -> U + 'static,
882    ) -> WithUnresolvedCommonTypeRefs<U> {
883        match self {
884            Self::WithUnresolved(_) => WithUnresolvedCommonTypeRefs::new(|common_type_defs| {
885                self.resolve_common_type_refs(common_type_defs).map(f)
886            }),
887            Self::WithoutUnresolved(v) => WithUnresolvedCommonTypeRefs::WithoutUnresolved(f(v)),
888        }
889    }
890
891    /// Resolve references to common types by inlining their definitions from
892    /// the given `HashMap`.
893    ///
894    /// Be warned that `common_type_defs` should contain all definitions, from
895    /// all schema fragments.
896    /// If `self` references any type not in `common_type_defs`, this will
897    /// return a `TypeNotDefinedError`.
898    pub fn resolve_common_type_refs(
899        self,
900        common_type_defs: &HashMap<&InternalName, Type>,
901    ) -> crate::err::Result<T> {
902        match self {
903            WithUnresolvedCommonTypeRefs::WithUnresolved(f) => f(common_type_defs),
904            WithUnresolvedCommonTypeRefs::WithoutUnresolved(v) => Ok(v),
905        }
906    }
907}
908
909impl<T: 'static> From<T> for WithUnresolvedCommonTypeRefs<T> {
910    fn from(value: T) -> Self {
911        Self::WithoutUnresolved(value)
912    }
913}
914
915impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedCommonTypeRefs<T> {
916    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
917        match self {
918            WithUnresolvedCommonTypeRefs::WithUnresolved(_) => {
919                f.debug_tuple("WithUnresolved").finish()
920            }
921            WithUnresolvedCommonTypeRefs::WithoutUnresolved(v) => {
922                f.debug_tuple("WithoutUnresolved").field(v).finish()
923            }
924        }
925    }
926}
927
928impl TryInto<ValidatorNamespaceDef<ConditionalName, ConditionalName>>
929    for json_schema::NamespaceDefinition<RawName>
930{
931    type Error = SchemaError;
932
933    fn try_into(
934        self,
935    ) -> crate::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>> {
936        ValidatorNamespaceDef::from_namespace_definition(
937            None,
938            self,
939            ActionBehavior::default(),
940            Extensions::all_available(),
941        )
942    }
943}
944
945/// Convert a [`json_schema::Type`] (with fully-qualified names) into the
946/// [`Type`] type used by the validator.
947///
948/// Conversion can fail if an entity or record attribute name is invalid. It
949/// will also fail for some types that can be written in the schema, but are
950/// not yet implemented in the typechecking logic.
951pub(crate) fn try_jsonschema_type_into_validator_type(
952    schema_ty: json_schema::Type<InternalName>,
953    extensions: &Extensions<'_>,
954) -> crate::err::Result<WithUnresolvedCommonTypeRefs<Type>> {
955    match schema_ty {
956        json_schema::Type::Type {
957            ty: json_schema::TypeVariant::String,
958            ..
959        } => Ok(Type::primitive_string().into()),
960        json_schema::Type::Type {
961            ty: json_schema::TypeVariant::Long,
962            ..
963        } => Ok(Type::primitive_long().into()),
964        json_schema::Type::Type {
965            ty: json_schema::TypeVariant::Boolean,
966            ..
967        } => Ok(Type::primitive_boolean().into()),
968        json_schema::Type::Type {
969            ty: json_schema::TypeVariant::Set { element },
970            ..
971        } => Ok(try_jsonschema_type_into_validator_type(*element, extensions)?.map(Type::set)),
972        json_schema::Type::Type {
973            ty: json_schema::TypeVariant::Record(rty),
974            ..
975        } => try_record_type_into_validator_type(rty, extensions),
976        json_schema::Type::Type {
977            ty: json_schema::TypeVariant::Entity { name },
978            ..
979        } => Ok(Type::named_entity_reference(internal_name_to_entity_type(name)?).into()),
980        json_schema::Type::Type {
981            ty: json_schema::TypeVariant::Extension { name },
982            ..
983        } => {
984            let extension_type_name = Name::unqualified_name(name);
985            if extensions.ext_types().contains(&extension_type_name) {
986                Ok(Type::extension(extension_type_name).into())
987            } else {
988                let suggested_replacement = fuzzy_search(
989                    &extension_type_name.to_string(),
990                    &extensions
991                        .ext_types()
992                        .map(|n| n.to_string())
993                        .collect::<Vec<_>>(),
994                );
995                Err(SchemaError::UnknownExtensionType(
996                    UnknownExtensionTypeError {
997                        actual: extension_type_name,
998                        suggested_replacement,
999                    },
1000                ))
1001            }
1002        }
1003        json_schema::Type::CommonTypeRef { type_name, .. } => {
1004            Ok(WithUnresolvedCommonTypeRefs::new(move |common_type_defs| {
1005                common_type_defs
1006                    .get(&type_name)
1007                    .cloned()
1008                    // We should always have `Some` here, because if the common type
1009                    // wasn't defined, that error should have been caught earlier,
1010                    // when the `json_schema::Type<InternalName>` was created by
1011                    // resolving a `ConditionalName` into a fully-qualified
1012                    // `InternalName`.
1013                    // Nonetheless, instead of panicking if that internal
1014                    // invariant is violated, it's easy to return this dynamic
1015                    // error instead.
1016                    .ok_or_else(|| CommonTypeInvariantViolationError { name: type_name }.into())
1017            }))
1018        }
1019        json_schema::Type::Type {
1020            ty: json_schema::TypeVariant::EntityOrCommon { type_name },
1021            ..
1022        } => {
1023            Ok(WithUnresolvedCommonTypeRefs::new(move |common_type_defs| {
1024                // First check if it's a common type, because in the edge case where
1025                // the name is both a valid common type name and a valid entity type
1026                // name, we give preference to the common type (see RFC 24).
1027                match common_type_defs.get(&type_name) {
1028                    Some(def) => Ok(def.clone()),
1029                    None => {
1030                        // It wasn't a common type, so we assume it must be a valid
1031                        // entity type. Otherwise, we would have had an error earlier,
1032                        // when the `json_schema::Type<InternalName>` was created by
1033                        // resolving a `ConditionalName` into a fully-qualified
1034                        // `InternalName`.
1035                        Ok(Type::named_entity_reference(internal_name_to_entity_type(
1036                            type_name,
1037                        )?))
1038                    }
1039                }
1040            }))
1041        }
1042    }
1043}
1044
1045/// Convert a [`json_schema::RecordType`] (with fully qualified names) into the
1046/// [`Type`] type used by the validator.
1047pub(crate) fn try_record_type_into_validator_type(
1048    rty: json_schema::RecordType<InternalName>,
1049    extensions: &Extensions<'_>,
1050) -> crate::err::Result<WithUnresolvedCommonTypeRefs<Type>> {
1051    if cfg!(not(feature = "partial-validate")) && rty.additional_attributes {
1052        Err(UnsupportedFeatureError(UnsupportedFeature::OpenRecordsAndEntities).into())
1053    } else {
1054        Ok(
1055            parse_record_attributes(rty.attributes.into_iter(), extensions)?.map(move |attrs| {
1056                Type::record_with_attributes(
1057                    attrs,
1058                    if rty.additional_attributes {
1059                        OpenTag::OpenAttributes
1060                    } else {
1061                        OpenTag::ClosedAttributes
1062                    },
1063                )
1064            }),
1065        )
1066    }
1067}
1068
1069/// Given the attributes for an entity or record type in the schema file format
1070/// structures (but with fully-qualified names), convert the types of the
1071/// attributes into the [`Type`] data structure used by the validator, and
1072/// return the result as an [`Attributes`] structure.
1073fn parse_record_attributes(
1074    attrs: impl IntoIterator<Item = (SmolStr, json_schema::TypeOfAttribute<InternalName>)>,
1075    extensions: &Extensions<'_>,
1076) -> crate::err::Result<WithUnresolvedCommonTypeRefs<Attributes>> {
1077    let attrs_with_common_type_refs = attrs
1078        .into_iter()
1079        .map(|(attr, ty)| -> crate::err::Result<_> {
1080            Ok((
1081                attr,
1082                (
1083                    try_jsonschema_type_into_validator_type(ty.ty, extensions)?,
1084                    ty.required,
1085                ),
1086            ))
1087        })
1088        .collect::<crate::err::Result<Vec<_>>>()?;
1089    Ok(WithUnresolvedCommonTypeRefs::new(|common_type_defs| {
1090        attrs_with_common_type_refs
1091            .into_iter()
1092            .map(|(s, (attr_ty, is_req))| {
1093                attr_ty
1094                    .resolve_common_type_refs(common_type_defs)
1095                    .map(|ty| (s, AttributeType::new(ty, is_req)))
1096            })
1097            .collect::<crate::err::Result<Vec<_>>>()
1098            .map(Attributes::with_attributes)
1099    }))
1100}