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::{BTreeMap, HashMap, HashSet};
21
22use cedar_policy_core::{
23    ast::{
24        Eid, EntityAttrEvaluationError, EntityType, EntityUID, Id, Name,
25        PartialValueSerializedAsExpr,
26    },
27    entities::{CedarValueJson, JsonDeserializationErrorContext},
28    evaluator::RestrictedEvaluator,
29    extensions::Extensions,
30    FromNormalizedStr,
31};
32use smol_str::{SmolStr, ToSmolStr};
33
34use super::ValidatorApplySpec;
35use crate::types::OpenTag;
36use crate::{
37    err::*,
38    schema_file_format,
39    types::{AttributeType, Attributes, Type},
40    ActionBehavior, ActionEntityUID, ActionType, NamespaceDefinition, SchemaType,
41    SchemaTypeVariant, TypeOfAttribute, SCHEMA_TYPE_VARIANT_TAGS,
42};
43
44/// The current schema format specification does not include multiple action entity
45/// types. All action entities are required to use a single `Action` entity
46/// type. However, the action entity type may be namespaced, so an action entity
47/// may have a fully qualified entity type `My::Namespace::Action`.
48/// This string must be parsable by as an entity type name.
49pub(crate) static ACTION_ENTITY_TYPE: &str = "Action";
50
51#[test]
52fn action_entity_type_parses() {
53    Id::from_normalized_str(ACTION_ENTITY_TYPE).unwrap();
54}
55
56/// Return true when an entity type is an action entity type. This compares the
57/// base name for the type, so this will return true for any entity type named
58/// `Action` regardless of namespaces.
59pub(crate) fn is_action_entity_type(ty: &Name) -> bool {
60    ty.basename().as_ref() == ACTION_ENTITY_TYPE
61}
62
63/// A single namespace definition from the schema json processed into a form
64/// which is closer to that used by the validator. The processing includes
65/// detection of some errors, for example, parse errors in entity type names or
66/// entity type which are declared multiple times. This does not detect
67/// references to undeclared entity types because any entity type may be
68/// declared in a different fragment that will only be known about when building
69/// the complete `ValidatorSchema`.
70#[derive(Debug)]
71pub struct ValidatorNamespaceDef {
72    /// The namespace declared for the schema fragment. We track a namespace for
73    /// fragments because they have at most one namespace that is applied
74    /// everywhere. It would be less useful to track all namespaces for a
75    /// complete schema.
76    namespace: Option<Name>,
77    /// Preprocessed common type definitions which can be used to define entity
78    /// type attributes and action contexts.
79    pub(super) type_defs: TypeDefs,
80    /// The preprocessed entity type declarations from the schema fragment json.
81    pub(super) entity_types: EntityTypesDef,
82    /// The preprocessed action declarations from the schema fragment json.
83    pub(super) actions: ActionsDef,
84}
85
86/// Holds a map from `Name`s of common type definitions to their corresponding
87/// `SchemaType`. Note that the schema type should have all common type
88/// references fully qualified.
89#[derive(Debug)]
90pub struct TypeDefs {
91    pub(super) type_defs: HashMap<Name, SchemaType>,
92}
93
94/// Entity type declarations held in a `ValidatorNamespaceDef`. Entity type
95/// parents and attributes may reference undeclared entity types.
96#[derive(Debug)]
97pub struct EntityTypesDef {
98    pub(super) entity_types: HashMap<Name, EntityTypeFragment>,
99}
100
101/// Defines an EntityType where we have not resolved typedefs occurring in the
102/// attributes or verified that the parent entity types and entity types
103/// occurring in attributes are defined.
104#[derive(Debug)]
105pub struct EntityTypeFragment {
106    /// The attributes record type for this entity type.  The type is wrapped in
107    /// a `WithUnresolvedTypeDefs` because it may contain typedefs which are not
108    /// defined in this schema fragment. All entity type `Name` keys in this map
109    /// are declared in this schema fragment.
110    pub(super) attributes: WithUnresolvedTypeDefs<Type>,
111    /// The direct parent entity types for this entity type come from the
112    /// `memberOfTypes` list. These types might be declared in a different
113    /// namespace, so we will check if they are declared in any fragment when
114    /// constructing a `ValidatorSchema`.
115    pub(super) parents: HashSet<Name>,
116}
117
118/// Action declarations held in a `ValidatorNamespaceDef`. Entity types
119/// referenced here do not need to be declared in the schema.
120#[derive(Debug)]
121pub struct ActionsDef {
122    pub(super) actions: HashMap<EntityUID, ActionFragment>,
123}
124
125#[derive(Debug)]
126pub struct ActionFragment {
127    /// The type of the context record for this actions. The types is wrapped in
128    /// a `WithUnresolvedTypeDefs` because it may refer to common types which
129    /// are not defined in this fragment.
130    pub(super) context: WithUnresolvedTypeDefs<Type>,
131    /// The principals and resources that an action can be applied to.
132    pub(super) applies_to: ValidatorApplySpec,
133    /// The direct parent action entities for this action.
134    pub(super) parents: HashSet<EntityUID>,
135    /// The types for the attributes defined for this actions entity.
136    pub(super) attribute_types: Attributes,
137    /// The values for the attributes defined for this actions entity, stored
138    /// separately so that we can later extract use these values to construct
139    /// the actual `Entity` objects defined by the schema.
140    pub(super) attributes: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
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(
197            None,
198            self,
199            ActionBehavior::default(),
200            Extensions::all_available(),
201        )
202    }
203}
204
205impl ValidatorNamespaceDef {
206    // We need to treat this as if it had `pub(crate)` visibility to avoid sharing
207    // the file format. However, our fuzzing library currently needs it to be public.
208    /// Construct a new `ValidatorSchema` from the underlying `SchemaFragment`.
209    pub fn from_namespace_definition(
210        namespace: Option<Name>,
211        namespace_def: NamespaceDefinition,
212        action_behavior: ActionBehavior,
213        extensions: Extensions<'_>,
214    ) -> Result<ValidatorNamespaceDef> {
215        // Check that each entity types and action is only declared once.
216        let mut e_types_ids: HashSet<Id> = HashSet::new();
217        for name in namespace_def.entity_types.keys() {
218            if !e_types_ids.insert(name.clone()) {
219                // insert returns false for duplicates
220                return Err(SchemaError::DuplicateEntityType(name.to_string()));
221            }
222        }
223        let mut a_name_eids: HashSet<SmolStr> = HashSet::new();
224        for name in namespace_def.actions.keys() {
225            if !a_name_eids.insert(name.clone()) {
226                // insert returns false for duplicates
227                return Err(SchemaError::DuplicateAction(name.to_string()));
228            }
229        }
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 = Self::build_type_defs(namespace_def.common_types, namespace.as_ref())?;
238        let actions =
239            Self::build_action_ids(namespace_def.actions, namespace.as_ref(), extensions)?;
240        let entity_types =
241            Self::build_entity_types(namespace_def.entity_types, namespace.as_ref())?;
242
243        Ok(ValidatorNamespaceDef {
244            namespace,
245            type_defs,
246            entity_types,
247            actions,
248        })
249    }
250
251    fn is_builtin_type_name(name: &SmolStr) -> bool {
252        SCHEMA_TYPE_VARIANT_TAGS
253            .iter()
254            .any(|type_name| name == type_name)
255    }
256
257    fn build_type_defs(
258        schema_file_type_def: HashMap<Id, SchemaType>,
259        schema_namespace: Option<&Name>,
260    ) -> Result<TypeDefs> {
261        let type_defs = schema_file_type_def
262            .into_iter()
263            .map(|(name, schema_ty)| -> Result<_> {
264                let name_str = name.clone().into_smolstr();
265                if Self::is_builtin_type_name(&name_str) {
266                    return Err(SchemaError::DuplicateCommonType(name_str.to_string()));
267                }
268                let name =
269                    Name::from(name).prefix_namespace_if_unqualified(schema_namespace.cloned());
270                Ok((
271                    name,
272                    schema_ty
273                        .prefix_common_type_references_with_namespace(schema_namespace.cloned()),
274                ))
275            })
276            .collect::<Result<HashMap<_, _>>>()?;
277        Ok(TypeDefs { type_defs })
278    }
279
280    // Transform the schema data structures for entity types into the structures
281    // used internally by the validator. This is mostly accomplished by directly
282    // copying data between fields.
283    fn build_entity_types(
284        schema_files_types: HashMap<Id, schema_file_format::EntityType>,
285        schema_namespace: Option<&Name>,
286    ) -> Result<EntityTypesDef> {
287        Ok(EntityTypesDef {
288            entity_types: schema_files_types
289                .into_iter()
290                .map(|(id, entity_type)| -> Result<_> {
291                    let name =
292                        Name::from(id).prefix_namespace_if_unqualified(schema_namespace.cloned());
293
294                    let parents = entity_type
295                        .member_of_types
296                        .into_iter()
297                        .map(|ty| ty.prefix_namespace_if_unqualified(schema_namespace.cloned()))
298                        .collect();
299
300                    let attributes = Self::try_schema_type_into_validator_type(
301                        schema_namespace,
302                        entity_type.shape.into_inner(),
303                    )?;
304
305                    Ok((
306                        name,
307                        EntityTypeFragment {
308                            attributes,
309                            parents,
310                        },
311                    ))
312                })
313                .collect::<Result<HashMap<_, _>>>()?,
314        })
315    }
316
317    // Helper to get types from `CedarValueJson`s. Currently doesn't support all
318    // `CedarValueJson` types. Note: If this function is extended to cover move
319    // `CedarValueJson`s, we must update `convert_attr_jsonval_map_to_attributes` to
320    // handle errors that may occur when parsing these values. This will require
321    // a breaking change in the `SchemaError` type in the public API.
322    fn jsonval_to_type_helper(v: &CedarValueJson, action_id: &EntityUID) -> Result<Type> {
323        match v {
324            CedarValueJson::Bool(_) => Ok(Type::primitive_boolean()),
325            CedarValueJson::Long(_) => Ok(Type::primitive_long()),
326            CedarValueJson::String(_) => Ok(Type::primitive_string()),
327            CedarValueJson::Record(r) => {
328                let mut required_attrs: HashMap<SmolStr, Type> = HashMap::new();
329                for (k, v_prime) in r {
330                    let t = Self::jsonval_to_type_helper(v_prime, action_id);
331                    match t {
332                        Ok(ty) => required_attrs.insert(k.clone(), ty),
333                        Err(e) => return Err(e),
334                    };
335                }
336                Ok(Type::record_with_required_attributes(
337                    required_attrs,
338                    OpenTag::ClosedAttributes,
339                ))
340            }
341            CedarValueJson::Set(v) => match v.first() {
342                //sets with elements of different types will be rejected elsewhere
343                None => Err(SchemaError::ActionAttributesContainEmptySet(
344                    action_id.clone(),
345                )),
346                Some(element) => {
347                    let element_type = Self::jsonval_to_type_helper(element, action_id);
348                    match element_type {
349                        Ok(t) => Ok(Type::Set {
350                            element_type: Some(Box::new(t)),
351                        }),
352                        Err(_) => element_type,
353                    }
354                }
355            },
356            CedarValueJson::EntityEscape { __entity: _ } => {
357                Err(SchemaError::UnsupportedActionAttribute(
358                    action_id.clone(),
359                    "entity escape (`__entity`)".to_owned(),
360                ))
361            }
362            CedarValueJson::ExprEscape { __expr: _ } => {
363                Err(SchemaError::UnsupportedActionAttribute(
364                    action_id.clone(),
365                    "expression escape (`__expr`)".to_owned(),
366                ))
367            }
368            CedarValueJson::ExtnEscape { __extn: _ } => {
369                Err(SchemaError::UnsupportedActionAttribute(
370                    action_id.clone(),
371                    "extension function escape (`__extn`)".to_owned(),
372                ))
373            }
374            CedarValueJson::Null => Err(SchemaError::UnsupportedActionAttribute(
375                action_id.clone(),
376                "null".to_owned(),
377            )),
378        }
379    }
380
381    //Convert jsonval map to attributes
382    fn convert_attr_jsonval_map_to_attributes(
383        m: HashMap<SmolStr, CedarValueJson>,
384        action_id: &EntityUID,
385        extensions: Extensions<'_>,
386    ) -> Result<(Attributes, BTreeMap<SmolStr, PartialValueSerializedAsExpr>)> {
387        let mut attr_types: HashMap<SmolStr, Type> = HashMap::with_capacity(m.len());
388        let mut attr_values: BTreeMap<SmolStr, PartialValueSerializedAsExpr> = BTreeMap::new();
389        let evaluator = RestrictedEvaluator::new(&extensions);
390
391        for (k, v) in m {
392            let t = Self::jsonval_to_type_helper(&v, action_id);
393            match t {
394                Ok(ty) => attr_types.insert(k.clone(), ty),
395                Err(e) => return Err(e),
396            };
397
398            // As an artifact of the limited `CedarValueJson` variants accepted by
399            // `Self::jsonval_to_type_helper`, we know that this function will
400            // never error. Also note that this is only ever executed when
401            // action attributes are enabled, but they cannot be enabled when
402            // using Cedar through the public API. This is fortunate because
403            // handling an error here would mean adding a new error variant to
404            // `SchemaError` in the public API, but we didn't make that enum
405            // `non_exhaustive`, so any new variants are a breaking change.
406            // PANIC SAFETY: see above
407            #[allow(clippy::expect_used)]
408            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`");
409            let pv = evaluator
410                .partial_interpret(e.as_borrowed())
411                .map_err(|err| {
412                    SchemaError::ActionAttrEval(EntityAttrEvaluationError {
413                        uid: action_id.clone(),
414                        attr: k.clone(),
415                        err,
416                    })
417                })?;
418            attr_values.insert(k.clone(), pv.into());
419        }
420        Ok((
421            Attributes::with_required_attributes(attr_types),
422            attr_values,
423        ))
424    }
425
426    // Transform the schema data structures for actions into the structures used
427    // internally by the validator. This is mostly accomplished by directly
428    // copying data between fields.
429    fn build_action_ids(
430        schema_file_actions: HashMap<SmolStr, ActionType>,
431        schema_namespace: Option<&Name>,
432        extensions: Extensions<'_>,
433    ) -> Result<ActionsDef> {
434        Ok(ActionsDef {
435            actions: schema_file_actions
436                .into_iter()
437                .map(|(action_id_str, action_type)| -> Result<_> {
438                    let action_id = Self::parse_action_id_with_namespace(
439                        &ActionEntityUID::default_type(action_id_str),
440                        schema_namespace,
441                    );
442
443                    let (principal_types, resource_types, context) = action_type
444                        .applies_to
445                        .map(|applies_to| {
446                            (
447                                applies_to.principal_types,
448                                applies_to.resource_types,
449                                applies_to.context,
450                            )
451                        })
452                        .unwrap_or_default();
453
454                    // Convert the entries in the `appliesTo` lists into sets of
455                    // `EntityTypes`. If one of the lists is `None` (absent from the
456                    // schema), then the specification is undefined.
457                    let applies_to = ValidatorApplySpec::new(
458                        Self::parse_apply_spec_type_list(principal_types, schema_namespace),
459                        Self::parse_apply_spec_type_list(resource_types, schema_namespace),
460                    );
461
462                    let context = Self::try_schema_type_into_validator_type(
463                        schema_namespace,
464                        context.into_inner(),
465                    )?;
466
467                    let parents = action_type
468                        .member_of
469                        .unwrap_or_default()
470                        .iter()
471                        .map(|parent| {
472                            Self::parse_action_id_with_namespace(parent, schema_namespace)
473                        })
474                        .collect::<HashSet<_>>();
475
476                    let (attribute_types, attributes) =
477                        Self::convert_attr_jsonval_map_to_attributes(
478                            action_type.attributes.unwrap_or_default(),
479                            &action_id,
480                            extensions,
481                        )?;
482
483                    Ok((
484                        action_id,
485                        ActionFragment {
486                            context,
487                            applies_to,
488                            parents,
489                            attribute_types,
490                            attributes,
491                        },
492                    ))
493                })
494                .collect::<Result<HashMap<_, _>>>()?,
495        })
496    }
497
498    // Check that `schema_file` uses actions in a way consistent with the
499    // specified `action_behavior`. When the behavior specifies that actions
500    // should not be used in groups and should not have attributes, then this
501    // function will return `Err` if it sees any action groups or attributes
502    // declared in the schema.
503    fn check_action_behavior(
504        schema_file: &NamespaceDefinition,
505        action_behavior: ActionBehavior,
506    ) -> Result<()> {
507        if schema_file
508            .entity_types
509            .iter()
510            // The `name` in an entity type declaration cannot be qualified
511            // with a namespace (it always implicitly takes the schema
512            // namespace), so we do this comparison directly.
513            .any(|(name, _)| name.to_smolstr() == ACTION_ENTITY_TYPE)
514        {
515            return Err(SchemaError::ActionEntityTypeDeclared);
516        }
517        if action_behavior == ActionBehavior::ProhibitAttributes {
518            let mut actions_with_attributes: Vec<String> = Vec::new();
519            for (name, a) in &schema_file.actions {
520                if a.attributes.is_some() {
521                    actions_with_attributes.push(name.to_string());
522                }
523            }
524            if !actions_with_attributes.is_empty() {
525                return Err(SchemaError::UnsupportedFeature(
526                    UnsupportedFeature::ActionAttributes(actions_with_attributes),
527                ));
528            }
529        }
530
531        Ok(())
532    }
533
534    /// Given the attributes for an entity type or action context as written in
535    /// a schema file, convert the types of the attributes into the `Type` data
536    /// structure used by the typechecker, and return the result as a map from
537    /// attribute name to type.
538    fn parse_record_attributes(
539        schema_namespace: Option<&Name>,
540        attrs: impl IntoIterator<Item = (SmolStr, TypeOfAttribute)>,
541    ) -> Result<WithUnresolvedTypeDefs<Attributes>> {
542        let attrs_with_type_defs = attrs
543            .into_iter()
544            .map(|(attr, ty)| -> Result<_> {
545                Ok((
546                    attr,
547                    (
548                        Self::try_schema_type_into_validator_type(schema_namespace, ty.ty)?,
549                        ty.required,
550                    ),
551                ))
552            })
553            .collect::<Result<Vec<_>>>()?;
554        Ok(WithUnresolvedTypeDefs::new(|typ_defs| {
555            attrs_with_type_defs
556                .into_iter()
557                .map(|(s, (attr_ty, is_req))| {
558                    attr_ty
559                        .resolve_type_defs(typ_defs)
560                        .map(|ty| (s, AttributeType::new(ty, is_req)))
561                })
562                .collect::<Result<Vec<_>>>()
563                .map(Attributes::with_attributes)
564        }))
565    }
566
567    /// Take an optional list of entity type name strings from an action apply
568    /// spec and parse it into a set of `Name`s for those entity types. If any
569    /// of the entity type names cannot be parsed, then the `Err` case is
570    /// returned, and it will indicate which name did not parse.
571    fn parse_apply_spec_type_list(
572        types: Option<Vec<Name>>,
573        namespace: Option<&Name>,
574    ) -> HashSet<EntityType> {
575        types
576            .map(|types| {
577                types
578                    .iter()
579                    // Parse each type name string into a `Name`, generating an
580                    // `EntityTypeParseError` when the string is not a valid
581                    // name.
582                    .map(|ty| {
583                        EntityType::Specified(
584                            ty.prefix_namespace_if_unqualified(namespace.cloned()),
585                        )
586                    })
587                    // Fail if any of the types failed.
588                    .collect::<HashSet<_>>()
589            })
590            .unwrap_or_else(|| HashSet::from([EntityType::Unspecified]))
591    }
592
593    /// Take an action identifier as a string and use it to construct an
594    /// EntityUID for that action. The entity type of the action will always
595    /// have the base type `Action`. The type will be qualified with any
596    /// namespace provided in the `namespace` argument or with the namespace
597    /// inside the ActionEntityUID if one is present.
598    fn parse_action_id_with_namespace(
599        action_id: &ActionEntityUID,
600        namespace: Option<&Name>,
601    ) -> EntityUID {
602        let namespaced_action_type = if let Some(action_ty) = &action_id.ty {
603            action_ty.prefix_namespace_if_unqualified(namespace.cloned())
604        } else {
605            // PANIC SAFETY: The constant ACTION_ENTITY_TYPE is valid entity type.
606            #[allow(clippy::expect_used)]
607            let id = Id::from_normalized_str(ACTION_ENTITY_TYPE).expect(
608                "Expected that the constant ACTION_ENTITY_TYPE would be a valid entity type.",
609            );
610            match namespace {
611                Some(namespace) => Name::type_in_namespace(id, namespace.clone(), None),
612                None => Name::unqualified_name(id),
613            }
614        };
615        EntityUID::from_components(namespaced_action_type, Eid::new(action_id.id.clone()), None)
616    }
617
618    /// Implemented to convert a type as written in the schema json format into the
619    /// `Type` type used by the validator. Conversion can fail if an entity or
620    /// record attribute name is invalid. It will also fail for some types that can
621    /// be written in the schema, but are not yet implemented in the typechecking
622    /// logic.
623    pub(crate) fn try_schema_type_into_validator_type(
624        default_namespace: Option<&Name>,
625        schema_ty: SchemaType,
626    ) -> Result<WithUnresolvedTypeDefs<Type>> {
627        match schema_ty {
628            SchemaType::Type(SchemaTypeVariant::String) => Ok(Type::primitive_string().into()),
629            SchemaType::Type(SchemaTypeVariant::Long) => Ok(Type::primitive_long().into()),
630            SchemaType::Type(SchemaTypeVariant::Boolean) => Ok(Type::primitive_boolean().into()),
631            SchemaType::Type(SchemaTypeVariant::Set { element }) => Ok(
632                Self::try_schema_type_into_validator_type(default_namespace, *element)?
633                    .map(Type::set),
634            ),
635            SchemaType::Type(SchemaTypeVariant::Record {
636                attributes,
637                additional_attributes,
638            }) => {
639                if cfg!(not(feature = "partial-validate")) && additional_attributes {
640                    Err(SchemaError::UnsupportedFeature(
641                        UnsupportedFeature::OpenRecordsAndEntities,
642                    ))
643                } else {
644                    Ok(
645                        Self::parse_record_attributes(default_namespace, attributes)?.map(
646                            move |attrs| {
647                                Type::record_with_attributes(
648                                    attrs,
649                                    if additional_attributes {
650                                        OpenTag::OpenAttributes
651                                    } else {
652                                        OpenTag::ClosedAttributes
653                                    },
654                                )
655                            },
656                        ),
657                    )
658                }
659            }
660            SchemaType::Type(SchemaTypeVariant::Entity { name }) => {
661                Ok(Type::named_entity_reference(
662                    name.prefix_namespace_if_unqualified(default_namespace.cloned()),
663                )
664                .into())
665            }
666            SchemaType::Type(SchemaTypeVariant::Extension { name }) => {
667                let extension_type_name = Name::unqualified_name(name);
668                Ok(Type::extension(extension_type_name).into())
669            }
670            SchemaType::TypeDef { type_name } => {
671                let defined_type_name =
672                    type_name.prefix_namespace_if_unqualified(default_namespace.cloned());
673                Ok(WithUnresolvedTypeDefs::new(move |typ_defs| {
674                    typ_defs.get(&defined_type_name).cloned().ok_or(
675                        SchemaError::UndeclaredCommonTypes(HashSet::from([
676                            defined_type_name.to_string()
677                        ])),
678                    )
679                }))
680            }
681        }
682    }
683
684    /// Access the `Name` for the namespace of this definition.
685    pub fn namespace(&self) -> &Option<Name> {
686        &self.namespace
687    }
688}