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