use std::collections::{hash_map::Entry, HashMap, HashSet};
use crate::parser::Loc;
use crate::{
ast::{EntityType, EntityUID, InternalName, Name, UnreservedId},
extensions::Extensions,
fuzzy_match::fuzzy_search,
};
use itertools::Itertools;
use nonempty::{nonempty, NonEmpty};
use smol_str::{SmolStr, ToSmolStr};
use super::{internal_name_to_entity_type, AllDefs, LocatedType, ValidatorApplySpec};
use crate::validator::{
err::{schema_errors::*, SchemaError},
json_schema::{self, CommonTypeId, EntityTypeKind},
partition_nonempty::PartitionNonEmpty,
types::{AttributeType, Attributes, OpenTag, Type},
ConditionalName, RawName, ReferenceType,
};
#[derive(Debug, Clone)]
pub struct ValidatorNamespaceDef<N, A> {
namespace: Option<InternalName>,
pub(super) common_types: CommonTypeDefs<N>,
pub(super) entity_types: EntityTypesDef<N>,
pub(super) actions: ActionsDef<N, A>,
#[cfg(feature = "extended-schema")]
pub(super) loc: Option<Loc>,
}
impl<N, A> ValidatorNamespaceDef<N, A> {
pub fn all_declared_entity_type_names(&self) -> impl Iterator<Item = &InternalName> {
self.entity_types
.defs
.keys()
.map(|ety| ety.as_ref().as_ref())
}
pub fn all_declared_common_type_names(&self) -> impl Iterator<Item = &InternalName> {
self.common_types.defs.keys()
}
pub fn all_declared_action_names(&self) -> impl Iterator<Item = &EntityUID> {
self.actions.actions.keys()
}
pub fn namespace(&self) -> Option<&InternalName> {
self.namespace.as_ref()
}
}
impl ValidatorNamespaceDef<ConditionalName, ConditionalName> {
pub fn from_namespace_definition(
namespace: Option<InternalName>,
namespace_def: json_schema::NamespaceDefinition<RawName>,
) -> crate::validator::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>>
{
Self::check_action_restrictions(&namespace_def)?;
let common_types =
CommonTypeDefs::from_raw_common_types(namespace_def.common_types, namespace.as_ref())?;
let actions = ActionsDef::from_raw_actions(namespace_def.actions, namespace.as_ref())?;
let entity_types =
EntityTypesDef::from_raw_entity_types(namespace_def.entity_types, namespace.as_ref())?;
Ok(ValidatorNamespaceDef {
namespace,
common_types,
entity_types,
actions,
#[cfg(feature = "extended-schema")]
loc: namespace_def.loc,
})
}
pub fn from_common_type_defs(
namespace: Option<InternalName>,
defs: HashMap<UnreservedId, json_schema::Type<ConditionalName>>,
) -> crate::validator::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>>
{
let common_types = CommonTypeDefs::from_conditionalname_typedefs(defs, namespace.as_ref())?;
Ok(ValidatorNamespaceDef {
namespace,
common_types,
entity_types: EntityTypesDef::new(),
actions: ActionsDef::new(),
#[cfg(feature = "extended-schema")]
loc: None,
})
}
pub fn from_common_type_def(
namespace: Option<InternalName>,
def: (UnreservedId, json_schema::Type<ConditionalName>),
) -> ValidatorNamespaceDef<ConditionalName, ConditionalName> {
let common_types = CommonTypeDefs::from_conditionalname_typedef(def, namespace.as_ref());
ValidatorNamespaceDef {
namespace,
common_types,
entity_types: EntityTypesDef::new(),
actions: ActionsDef::new(),
#[cfg(feature = "extended-schema")]
loc: None,
}
}
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<ValidatorNamespaceDef<InternalName, EntityType>, SchemaError> {
match (
self.common_types.fully_qualify_type_references(all_defs),
self.entity_types.fully_qualify_type_references(all_defs),
self.actions.fully_qualify_type_references(all_defs),
) {
(Ok(common_types), Ok(entity_types), Ok(actions)) => Ok(ValidatorNamespaceDef {
namespace: self.namespace,
common_types,
entity_types,
actions,
#[cfg(feature = "extended-schema")]
loc: self.loc,
}),
(res1, res2, res3) => {
#[expect(
clippy::expect_used,
reason = "at least one of the results is `Err`, so the input to `NonEmpty::collect()` cannot be an empty iterator"
)]
let errs = NonEmpty::collect(
res1.err()
.into_iter()
.map(SchemaError::from)
.chain(res2.err().map(SchemaError::from))
.chain(res3.err()),
)
.expect("there must be an error");
Err(SchemaError::join_nonempty(errs))
}
}
}
fn check_action_restrictions<N>(
schema_nsdef: &json_schema::NamespaceDefinition<N>,
) -> crate::validator::err::Result<()> {
if schema_nsdef
.entity_types
.iter()
.any(|(name, _)| name.to_smolstr() == crate::ast::ACTION_ENTITY_TYPE)
{
return Err(ActionEntityTypeDeclaredError {}.into());
}
let mut actions_with_attributes: Vec<String> = Vec::new();
for (name, a) in &schema_nsdef.actions {
if a.attributes.is_some() {
actions_with_attributes.push(name.to_string());
}
}
if !actions_with_attributes.is_empty() {
actions_with_attributes.sort(); return Err(
UnsupportedFeatureError(UnsupportedFeature::ActionAttributes(
actions_with_attributes,
))
.into(),
);
}
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct CommonTypeDefs<N> {
pub(super) defs: HashMap<InternalName, json_schema::Type<N>>,
}
impl CommonTypeDefs<ConditionalName> {
pub(crate) fn from_raw_common_types(
schema_file_type_def: impl IntoIterator<Item = (CommonTypeId, json_schema::CommonType<RawName>)>,
schema_namespace: Option<&InternalName>,
) -> crate::validator::err::Result<Self> {
let mut defs = HashMap::new();
for (id, schema_ty) in schema_file_type_def {
let name = RawName::new_from_unreserved(id.into(), schema_ty.loc)
.qualify_with(schema_namespace); match defs.entry(name) {
Entry::Vacant(ventry) => {
ventry.insert(
schema_ty
.ty
.conditionally_qualify_type_references(schema_namespace),
);
}
Entry::Occupied(oentry) => {
return Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError {
ty: oentry.key().clone(),
}));
}
}
}
Ok(Self { defs })
}
pub(crate) fn from_conditionalname_typedefs(
input_type_defs: HashMap<UnreservedId, json_schema::Type<ConditionalName>>,
schema_namespace: Option<&InternalName>,
) -> crate::validator::err::Result<Self> {
let mut defs = HashMap::with_capacity(input_type_defs.len());
for (id, schema_ty) in input_type_defs {
let name = RawName::new_from_unreserved(id, None).qualify_with(schema_namespace); match defs.entry(name) {
Entry::Vacant(ventry) => {
ventry.insert(schema_ty);
}
Entry::Occupied(oentry) => {
return Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError {
ty: oentry.key().clone(),
}));
}
}
}
Ok(Self { defs })
}
pub(crate) fn from_conditionalname_typedef(
(id, schema_ty): (UnreservedId, json_schema::Type<ConditionalName>),
schema_namespace: Option<&InternalName>,
) -> Self {
Self {
defs: HashMap::from_iter([(
RawName::new_from_unreserved(id, None).qualify_with(schema_namespace),
schema_ty,
)]),
}
}
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<CommonTypeDefs<InternalName>, TypeNotDefinedError> {
Ok(CommonTypeDefs {
defs: self
.defs
.into_iter()
.map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
.partition_nonempty()?,
})
}
}
#[derive(Debug, Clone)]
pub struct EntityTypesDef<N> {
pub(super) defs: HashMap<EntityType, EntityTypeFragment<N>>,
}
impl<N> EntityTypesDef<N> {
pub fn new() -> Self {
Self {
defs: HashMap::new(),
}
}
}
impl EntityTypesDef<ConditionalName> {
pub(crate) fn from_raw_entity_types(
schema_files_types: impl IntoIterator<Item = (UnreservedId, json_schema::EntityType<RawName>)>,
schema_namespace: Option<&InternalName>,
) -> crate::validator::err::Result<Self> {
let mut defs: HashMap<EntityType, _> = HashMap::new();
for (id, entity_type) in schema_files_types {
let ety = internal_name_to_entity_type(
RawName::new_from_unreserved(id, entity_type.loc.clone())
.qualify_with(schema_namespace), )?;
match defs.entry(ety) {
Entry::Vacant(ventry) => {
ventry.insert(EntityTypeFragment::from_raw_entity_type(
entity_type,
schema_namespace,
));
}
Entry::Occupied(entry) => {
return Err(DuplicateEntityTypeError {
ty: entry.key().clone(),
}
.into());
}
}
}
Ok(EntityTypesDef { defs })
}
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<EntityTypesDef<InternalName>, TypeNotDefinedError> {
Ok(EntityTypesDef {
defs: self
.defs
.into_iter()
.map(|(k, v)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
.partition_nonempty()?,
})
}
}
#[derive(Debug, Clone)]
pub enum EntityTypeFragment<N> {
Standard {
attributes: json_schema::AttributesOrContext<N>,
parents: HashSet<N>,
tags: Option<json_schema::Type<N>>,
},
Enum(NonEmpty<SmolStr>),
}
impl<N> EntityTypeFragment<N> {
pub(crate) fn parents(&self) -> Box<dyn Iterator<Item = &N> + '_> {
match self {
Self::Standard { parents, .. } => Box::new(parents.iter()),
Self::Enum(_) => Box::new(std::iter::empty()),
}
}
}
impl EntityTypeFragment<ConditionalName> {
pub(crate) fn from_raw_entity_type(
schema_file_type: json_schema::EntityType<RawName>,
schema_namespace: Option<&InternalName>,
) -> Self {
match schema_file_type.kind {
EntityTypeKind::Enum { choices } => Self::Enum(choices),
EntityTypeKind::Standard(ty) => {
Self::Standard {
attributes: ty
.shape
.conditionally_qualify_type_references(schema_namespace),
parents: ty
.member_of_types
.into_iter()
.map(|raw_name| {
raw_name
.conditionally_qualify_with(schema_namespace, ReferenceType::Entity)
})
.collect(),
tags: ty
.tags
.map(|tags| tags.conditionally_qualify_type_references(schema_namespace)),
}
}
}
}
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<EntityTypeFragment<InternalName>, TypeNotDefinedError> {
match self {
Self::Enum(choices) => Ok(EntityTypeFragment::Enum(choices)),
Self::Standard {
attributes,
parents,
tags,
} => {
let fully_qual_attributes = attributes.fully_qualify_type_references(all_defs);
let parents: HashSet<InternalName> = parents
.into_iter()
.map(|parent| parent.resolve(all_defs))
.partition_nonempty()?;
let fully_qual_tags = tags
.map(|tags| tags.fully_qualify_type_references(all_defs))
.transpose();
let undeclared_parents: Option<NonEmpty<ConditionalName>> = NonEmpty::collect(
parents
.iter()
.filter(|ety| !all_defs.is_defined_as_entity(ety))
.map(|ety| {
ConditionalName::unconditional(ety.clone(), ReferenceType::Entity)
}),
);
match (fully_qual_attributes, fully_qual_tags, undeclared_parents) {
(Ok(attributes), Ok(tags), None) => Ok(EntityTypeFragment::Standard {
attributes,
parents,
tags,
}),
(Ok(_), Ok(_), Some(undeclared_parents)) => Err(TypeNotDefinedError {
undefined_types: undeclared_parents,
}),
(Err(e), Ok(_), None) | (Ok(_), Err(e), None) => Err(e),
(Err(e1), Err(e2), None) => {
Err(TypeNotDefinedError::join_nonempty(nonempty![e1, e2]))
}
(Err(e), Ok(_), Some(mut undeclared))
| (Ok(_), Err(e), Some(mut undeclared)) => {
undeclared.extend(e.undefined_types);
Err(TypeNotDefinedError {
undefined_types: undeclared,
})
}
(Err(e1), Err(e2), Some(mut undeclared)) => {
undeclared.extend(e1.undefined_types);
undeclared.extend(e2.undefined_types);
Err(TypeNotDefinedError {
undefined_types: undeclared,
})
}
}
}
}
}
}
#[derive(Debug, Clone)]
pub struct ActionsDef<N, A> {
pub(super) actions: HashMap<EntityUID, ActionFragment<N, A>>,
}
impl<N, A> ActionsDef<N, A> {
pub fn new() -> Self {
Self {
actions: HashMap::new(),
}
}
}
#[cfg_attr(
not(feature = "extended-schema"),
expect(unused_variables, reason = "used with some features")
)]
fn create_action_entity_uid_default_type(
action_name: &SmolStr,
action_type: &json_schema::ActionType<RawName>,
schema_namespace: Option<&InternalName>,
) -> json_schema::ActionEntityUID<InternalName> {
let action_id_str = action_name.clone();
#[cfg(feature = "extended-schema")]
let action_id_loc = action_type.defn_loc.clone();
#[cfg(feature = "extended-schema")]
return json_schema::ActionEntityUID::default_type_with_loc(action_id_str, action_id_loc)
.qualify_with(schema_namespace);
#[cfg(not(feature = "extended-schema"))]
json_schema::ActionEntityUID::default_type(action_id_str).qualify_with(schema_namespace)
}
impl ActionsDef<ConditionalName, ConditionalName> {
pub(crate) fn from_raw_actions(
schema_file_actions: impl IntoIterator<Item = (SmolStr, json_schema::ActionType<RawName>)>,
schema_namespace: Option<&InternalName>,
) -> crate::validator::err::Result<Self> {
let mut actions = HashMap::new();
for (action_name, action_type) in schema_file_actions {
let action_uid =
create_action_entity_uid_default_type(&action_name, &action_type, schema_namespace);
match actions.entry(action_uid.clone().try_into()?) {
Entry::Vacant(ventry) => {
let frag = ActionFragment::from_raw_action(action_type, schema_namespace)?;
ventry.insert(frag);
}
Entry::Occupied(_) => {
return Err(DuplicateActionError(action_name).into());
}
}
}
Ok(Self { actions })
}
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<ActionsDef<InternalName, EntityType>, SchemaError> {
Ok(ActionsDef {
actions: self
.actions
.into_iter()
.map(|(k, v)| v.fully_qualify_type_references(all_defs).map(|v| (k, v)))
.partition_nonempty()?,
})
}
}
#[derive(Debug, Clone)]
pub struct ActionFragment<N, A> {
pub(super) context: json_schema::Type<N>,
pub(super) applies_to: ValidatorApplySpec<A>,
pub(super) parents: HashSet<json_schema::ActionEntityUID<N>>,
pub(super) loc: Option<Loc>,
}
impl ActionFragment<ConditionalName, ConditionalName> {
pub(crate) fn from_raw_action(
action_type: json_schema::ActionType<RawName>,
schema_namespace: Option<&InternalName>,
) -> crate::validator::err::Result<Self> {
let (principal_types, resource_types, context) = action_type
.applies_to
.map(|applies_to| {
(
applies_to.principal_types,
applies_to.resource_types,
applies_to.context,
)
})
.unwrap_or_default();
Ok(Self {
context: context
.into_inner()
.conditionally_qualify_type_references(schema_namespace),
applies_to: ValidatorApplySpec::<ConditionalName>::new(
principal_types
.into_iter()
.map(|pty| {
pty.conditionally_qualify_with(schema_namespace, ReferenceType::Entity)
})
.collect(),
resource_types
.into_iter()
.map(|rty| {
rty.conditionally_qualify_with(schema_namespace, ReferenceType::Entity)
})
.collect(),
),
parents: action_type
.member_of
.unwrap_or_default()
.into_iter()
.map(|parent| parent.conditionally_qualify_type_references(schema_namespace))
.collect(),
loc: action_type.loc,
})
}
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<ActionFragment<InternalName, EntityType>, SchemaError> {
Ok(ActionFragment {
context: self.context.fully_qualify_type_references(all_defs)?,
applies_to: self.applies_to.fully_qualify_type_references(all_defs)?,
parents: self
.parents
.into_iter()
.map(|parent| parent.fully_qualify_type_references(all_defs))
.partition_nonempty()?,
loc: self.loc,
})
}
}
type ResolveFunc<T> =
dyn FnOnce(&HashMap<&InternalName, LocatedType>) -> crate::validator::err::Result<T>;
pub(crate) enum WithUnresolvedCommonTypeRefs<T> {
WithUnresolved(Box<ResolveFunc<T>>, Option<Loc>),
WithoutUnresolved(T, Option<Loc>),
}
impl<T: 'static> WithUnresolvedCommonTypeRefs<T> {
pub fn new(
f: impl FnOnce(&HashMap<&InternalName, LocatedType>) -> crate::validator::err::Result<T>
+ 'static,
loc: Option<Loc>,
) -> Self {
Self::WithUnresolved(Box::new(f), loc)
}
#[cfg(feature = "extended-schema")]
pub fn loc(&self) -> Option<&Loc> {
match self {
WithUnresolvedCommonTypeRefs::WithUnresolved(_, loc) => loc.as_ref(),
WithUnresolvedCommonTypeRefs::WithoutUnresolved(_, loc) => loc.as_ref(),
}
}
pub fn map<U: 'static>(
self,
f: impl FnOnce(T) -> U + 'static,
) -> WithUnresolvedCommonTypeRefs<U> {
match self {
Self::WithUnresolved(_, ref loc) => {
let loc = loc.clone();
WithUnresolvedCommonTypeRefs::new(
|common_type_defs| self.resolve_common_type_refs(common_type_defs).map(f),
loc,
)
}
Self::WithoutUnresolved(v, loc) => {
WithUnresolvedCommonTypeRefs::WithoutUnresolved(f(v), loc)
}
}
}
pub fn resolve_common_type_refs(
self,
common_type_defs: &HashMap<&InternalName, LocatedType>,
) -> crate::validator::err::Result<T> {
match self {
WithUnresolvedCommonTypeRefs::WithUnresolved(f, _) => f(common_type_defs),
WithUnresolvedCommonTypeRefs::WithoutUnresolved(v, _) => Ok(v),
}
}
}
impl<T: 'static> From<T> for WithUnresolvedCommonTypeRefs<T> {
fn from(value: T) -> Self {
Self::WithoutUnresolved(value, None)
}
}
impl From<Type> for WithUnresolvedCommonTypeRefs<LocatedType> {
fn from(value: Type) -> Self {
Self::WithoutUnresolved(LocatedType::new(value), None)
}
}
impl<T: std::fmt::Debug> std::fmt::Debug for WithUnresolvedCommonTypeRefs<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
WithUnresolvedCommonTypeRefs::WithUnresolved(_, _) => {
f.debug_tuple("WithUnresolved").finish()
}
WithUnresolvedCommonTypeRefs::WithoutUnresolved(v, _) => {
f.debug_tuple("WithoutUnresolved").field(v).finish()
}
}
}
}
impl TryInto<ValidatorNamespaceDef<ConditionalName, ConditionalName>>
for json_schema::NamespaceDefinition<RawName>
{
type Error = SchemaError;
fn try_into(
self,
) -> crate::validator::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>>
{
ValidatorNamespaceDef::from_namespace_definition(None, self)
}
}
pub(crate) fn try_jsonschema_type_into_validator_type(
schema_ty: json_schema::Type<InternalName>,
extensions: &Extensions<'_>,
loc: Option<Loc>,
) -> crate::validator::err::Result<WithUnresolvedCommonTypeRefs<LocatedType>> {
match schema_ty {
json_schema::Type::Type {
ty: json_schema::TypeVariant::String,
..
} => Ok(WithUnresolvedCommonTypeRefs::WithoutUnresolved(
LocatedType::new_with_loc(Type::primitive_string(), &loc),
loc,
)),
json_schema::Type::Type {
ty: json_schema::TypeVariant::Long,
..
} => Ok(WithUnresolvedCommonTypeRefs::WithoutUnresolved(
LocatedType::new_with_loc(Type::primitive_long(), &loc),
loc,
)),
json_schema::Type::Type {
ty: json_schema::TypeVariant::Boolean,
..
} => Ok(WithUnresolvedCommonTypeRefs::WithoutUnresolved(
LocatedType::new_with_loc(Type::primitive_boolean(), &loc),
loc,
)),
json_schema::Type::Type {
ty: json_schema::TypeVariant::Set { element },
..
} => Ok(
try_jsonschema_type_into_validator_type(*element, extensions, loc)?.map(|vt| {
let (vt_ty, vt_loc) = vt.into_type_and_loc();
LocatedType::new_with_loc(Type::set(vt_ty.into()), &vt_loc)
}),
),
json_schema::Type::Type {
ty: json_schema::TypeVariant::Record(rty),
..
} => try_record_type_into_validator_type(rty, extensions, loc),
json_schema::Type::Type {
ty: json_schema::TypeVariant::Entity { name },
..
} => Ok(WithUnresolvedCommonTypeRefs::WithoutUnresolved(
LocatedType::new_with_loc(
Type::named_entity_reference(internal_name_to_entity_type(name)?),
&loc,
),
loc,
)),
json_schema::Type::Type {
ty: json_schema::TypeVariant::Extension { name },
..
} => {
let extension_type_name = Name::unqualified_name(name);
if extensions.ext_types().contains(&extension_type_name) {
Ok(Type::extension(extension_type_name).into())
} else {
let suggested_replacement = fuzzy_search(
&extension_type_name.to_string(),
&extensions
.ext_types()
.map(|n| n.to_string())
.collect::<Vec<_>>(),
);
Err(SchemaError::UnknownExtensionType(
UnknownExtensionTypeError {
actual: extension_type_name,
suggested_replacement,
},
))
}
}
json_schema::Type::CommonTypeRef { type_name, .. } => {
Ok(WithUnresolvedCommonTypeRefs::new(
move |common_type_defs| {
common_type_defs
.get(&type_name)
.cloned()
.ok_or_else(|| CommonTypeInvariantViolationError { name: type_name }.into())
},
loc,
))
}
json_schema::Type::Type {
ty: json_schema::TypeVariant::EntityOrCommon { type_name },
..
} => {
let loc_clone = loc.clone();
Ok(WithUnresolvedCommonTypeRefs::new(
move |common_type_defs| {
match common_type_defs.get(&type_name) {
Some(def) => Ok(def.clone()),
None => {
Ok(LocatedType::new_with_loc(
Type::named_entity_reference(internal_name_to_entity_type(
type_name,
)?),
&loc,
))
}
}
},
loc_clone,
))
}
}
}
pub(crate) fn try_record_type_into_validator_type(
rty: json_schema::RecordType<InternalName>,
extensions: &Extensions<'_>,
loc: Option<Loc>,
) -> crate::validator::err::Result<WithUnresolvedCommonTypeRefs<LocatedType>> {
if cfg!(not(feature = "partial-validate")) && rty.additional_attributes {
Err(UnsupportedFeatureError(UnsupportedFeature::OpenRecordsAndEntities).into())
} else {
#[cfg(feature = "extended-schema")]
let attr_loc = loc.clone();
#[cfg(not(feature = "extended-schema"))]
let attr_loc = None;
Ok(
parse_record_attributes(rty.attributes.into_iter(), extensions, attr_loc)?.map(
move |attrs| {
LocatedType::new_with_loc(
Type::record_with_attributes(
attrs,
if rty.additional_attributes {
OpenTag::OpenAttributes
} else {
OpenTag::ClosedAttributes
},
),
&loc,
)
},
),
)
}
}
fn parse_record_attributes(
attrs: impl IntoIterator<Item = (SmolStr, json_schema::TypeOfAttribute<InternalName>)>,
extensions: &Extensions<'_>,
loc: Option<Loc>,
) -> crate::validator::err::Result<WithUnresolvedCommonTypeRefs<Attributes>> {
let attrs_with_common_type_refs = attrs
.into_iter()
.map(|(attr, ty)| -> crate::validator::err::Result<_> {
#[cfg(feature = "extended-schema")]
let loc = ty.loc;
#[cfg(not(feature = "extended-schema"))]
let loc = None;
Ok((
attr,
(
try_jsonschema_type_into_validator_type(ty.ty.clone(), extensions, loc)?,
ty.required,
),
))
})
.collect::<crate::validator::err::Result<Vec<_>>>()?;
Ok(WithUnresolvedCommonTypeRefs::new(
|common_type_defs| {
attrs_with_common_type_refs
.into_iter()
.map(|(s, (attr_ty, is_req))| {
#[cfg(feature = "extended-schema")]
let loc = attr_ty.loc().cloned();
attr_ty
.resolve_common_type_refs(common_type_defs)
.map(|ty| {
#[cfg(feature = "extended-schema")]
let ty_pair =
(s, AttributeType::new_with_loc(ty.ty.into(), is_req, loc));
#[cfg(not(feature = "extended-schema"))]
let ty_pair = (s, AttributeType::new(ty.ty.into(), is_req));
ty_pair
})
})
.collect::<crate::validator::err::Result<Vec<_>>>()
.map(Attributes::with_attributes)
},
loc,
))
}