use std::collections::{hash_map::Entry, BTreeMap, HashMap, HashSet};
use cedar_policy_core::{
ast::{
EntityAttrEvaluationError, EntityType, EntityUID, InternalName, Name,
PartialValueSerializedAsExpr, UnreservedId,
},
entities::{json::err::JsonDeserializationErrorContext, CedarValueJson},
evaluator::RestrictedEvaluator,
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, ValidatorApplySpec};
use crate::{
err::{schema_errors::*, SchemaError},
json_schema::{self, CommonTypeId},
types::{AttributeType, Attributes, OpenTag, Type},
ActionBehavior, ConditionalName, RawName, ReferenceType,
};
#[derive(Debug)]
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>,
}
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>,
action_behavior: ActionBehavior,
extensions: &Extensions<'_>,
) -> crate::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>> {
Self::check_action_behavior(&namespace_def, action_behavior)?;
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(), extensions)?;
let entity_types =
EntityTypesDef::from_raw_entity_types(namespace_def.entity_types, namespace.as_ref())?;
Ok(ValidatorNamespaceDef {
namespace,
common_types,
entity_types,
actions,
})
}
pub fn from_common_type_defs(
namespace: Option<InternalName>,
defs: HashMap<UnreservedId, json_schema::Type<ConditionalName>>,
) -> crate::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(),
})
}
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(),
}
}
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,
}),
(res1, res2, res3) => {
#[allow(clippy::expect_used)]
let errs = NonEmpty::collect(
res1.err()
.into_iter()
.map(SchemaError::from)
.chain(res2.err().map(SchemaError::from))
.chain(res3.err().map(SchemaError::from)),
)
.expect("there must be an error");
Err(SchemaError::join_nonempty(errs))
}
}
}
fn check_action_behavior<N>(
schema_nsdef: &json_schema::NamespaceDefinition<N>,
action_behavior: ActionBehavior,
) -> crate::err::Result<()> {
if schema_nsdef
.entity_types
.iter()
.any(|(name, _)| name.to_smolstr() == cedar_policy_core::ast::ACTION_ENTITY_TYPE)
{
return Err(ActionEntityTypeDeclaredError {}.into());
}
if action_behavior == ActionBehavior::ProhibitAttributes {
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)]
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: BTreeMap<CommonTypeId, json_schema::Type<RawName>>,
schema_namespace: Option<&InternalName>,
) -> crate::err::Result<Self> {
let mut defs = HashMap::with_capacity(schema_file_type_def.len());
for (id, schema_ty) in schema_file_type_def {
let name = RawName::new_from_unreserved(id.into()).qualify_with(schema_namespace); match defs.entry(name) {
Entry::Vacant(ventry) => {
ventry
.insert(schema_ty.conditionally_qualify_type_references(schema_namespace));
}
Entry::Occupied(oentry) => {
return Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError(
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::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).qualify_with(schema_namespace); match defs.entry(name) {
Entry::Vacant(ventry) => {
ventry.insert(schema_ty);
}
Entry::Occupied(oentry) => {
return Err(SchemaError::DuplicateCommonType(DuplicateCommonTypeError(
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).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)?)))
.collect::<Result<_, TypeNotDefinedError>>()?,
})
}
}
#[derive(Debug)]
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: BTreeMap<UnreservedId, json_schema::EntityType<RawName>>,
schema_namespace: Option<&InternalName>,
) -> crate::err::Result<Self> {
let mut defs: HashMap<EntityType, _> = HashMap::with_capacity(schema_files_types.len());
for (id, entity_type) in schema_files_types {
let ety = internal_name_to_entity_type(
RawName::new_from_unreserved(id).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(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)?)))
.collect::<Result<_, TypeNotDefinedError>>()?,
})
}
}
#[derive(Debug)]
pub struct EntityTypeFragment<N> {
pub(super) attributes: json_schema::AttributesOrContext<N>,
pub(super) parents: HashSet<N>,
pub(super) tags: Option<json_schema::Type<N>>,
}
impl EntityTypeFragment<ConditionalName> {
pub(crate) fn from_raw_entity_type(
schema_file_type: json_schema::EntityType<RawName>,
schema_namespace: Option<&InternalName>,
) -> Self {
Self {
attributes: schema_file_type
.shape
.conditionally_qualify_type_references(schema_namespace),
parents: schema_file_type
.member_of_types
.into_iter()
.map(|raw_name| {
raw_name.conditionally_qualify_with(schema_namespace, ReferenceType::Entity)
})
.collect(),
tags: schema_file_type
.tags
.map(|tags| tags.conditionally_qualify_type_references(schema_namespace)),
}
}
pub fn fully_qualify_type_references(
self,
all_defs: &AllDefs,
) -> Result<EntityTypeFragment<InternalName>, TypeNotDefinedError> {
let fully_qual_attributes = self.attributes.fully_qualify_type_references(all_defs);
let parents: HashSet<InternalName> = self
.parents
.into_iter()
.map(|parent| parent.resolve(all_defs))
.collect::<Result<_, TypeNotDefinedError>>()?;
let fully_qual_tags = self
.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 {
attributes,
parents,
tags,
}),
(Ok(_), Ok(_), Some(undeclared_parents)) => {
Err(TypeNotDefinedError(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.0);
Err(TypeNotDefinedError(undeclared))
}
(Err(e1), Err(e2), Some(mut undeclared)) => {
undeclared.extend(e1.0);
undeclared.extend(e2.0);
Err(TypeNotDefinedError(undeclared))
}
}
}
}
#[derive(Debug)]
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(),
}
}
}
impl ActionsDef<ConditionalName, ConditionalName> {
pub(crate) fn from_raw_actions(
schema_file_actions: BTreeMap<SmolStr, json_schema::ActionType<RawName>>,
schema_namespace: Option<&InternalName>,
extensions: &Extensions<'_>,
) -> crate::err::Result<Self> {
let mut actions = HashMap::with_capacity(schema_file_actions.len());
for (action_id_str, action_type) in schema_file_actions {
let action_uid = json_schema::ActionEntityUID::default_type(action_id_str.clone())
.qualify_with(schema_namespace); match actions.entry(action_uid.try_into()?) {
Entry::Vacant(ventry) => {
let frag = ActionFragment::from_raw_action(
ventry.key(),
action_type,
schema_namespace,
extensions,
)?;
ventry.insert(frag);
}
Entry::Occupied(_) => {
return Err(DuplicateActionError(action_id_str).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)| Ok((k, v.fully_qualify_type_references(all_defs)?)))
.collect::<Result<_, SchemaError>>()?,
})
}
}
#[derive(Debug)]
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) attribute_types: Attributes,
pub(super) attributes: BTreeMap<SmolStr, PartialValueSerializedAsExpr>,
}
impl ActionFragment<ConditionalName, ConditionalName> {
pub(crate) fn from_raw_action(
action_uid: &EntityUID,
action_type: json_schema::ActionType<RawName>,
schema_namespace: Option<&InternalName>,
extensions: &Extensions<'_>,
) -> crate::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();
let (attribute_types, attributes) = Self::convert_attr_jsonval_map_to_attributes(
action_type.attributes.unwrap_or_default(),
action_uid,
extensions,
)?;
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(),
attribute_types,
attributes,
})
}
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)
.map_err(Into::into)
})
.collect::<Result<_, SchemaError>>()?,
attribute_types: self.attribute_types,
attributes: self.attributes,
})
}
fn convert_attr_jsonval_map_to_attributes(
m: HashMap<SmolStr, CedarValueJson>,
action_id: &EntityUID,
extensions: &Extensions<'_>,
) -> crate::err::Result<(Attributes, BTreeMap<SmolStr, PartialValueSerializedAsExpr>)> {
let mut attr_types: HashMap<SmolStr, Type> = HashMap::with_capacity(m.len());
let mut attr_values: BTreeMap<SmolStr, PartialValueSerializedAsExpr> = BTreeMap::new();
let evaluator = RestrictedEvaluator::new(extensions);
for (k, v) in m {
let t = Self::jsonval_to_type_helper(&v, action_id);
match t {
Ok(ty) => attr_types.insert(k.clone(), ty),
Err(e) => return Err(e),
};
#[allow(clippy::expect_used)]
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`");
let pv = evaluator
.partial_interpret(e.as_borrowed())
.map_err(|err| {
ActionAttrEvalError(EntityAttrEvaluationError {
uid: action_id.clone(),
attr_or_tag: k.clone(),
was_attr: true,
err,
})
})?;
attr_values.insert(k.clone(), pv.into());
}
Ok((
Attributes::with_required_attributes(attr_types),
attr_values,
))
}
fn jsonval_to_type_helper(
v: &CedarValueJson,
action_id: &EntityUID,
) -> crate::err::Result<Type> {
match v {
CedarValueJson::Bool(_) => Ok(Type::primitive_boolean()),
CedarValueJson::Long(_) => Ok(Type::primitive_long()),
CedarValueJson::String(_) => Ok(Type::primitive_string()),
CedarValueJson::Record(r) => {
let mut required_attrs: HashMap<SmolStr, Type> = HashMap::with_capacity(r.len());
for (k, v_prime) in r {
let t = Self::jsonval_to_type_helper(v_prime, action_id);
match t {
Ok(ty) => required_attrs.insert(k.clone(), ty),
Err(e) => return Err(e),
};
}
Ok(Type::record_with_required_attributes(
required_attrs,
OpenTag::ClosedAttributes,
))
}
CedarValueJson::Set(v) => match v.first() {
None => Err(ActionAttributesContainEmptySetError(action_id.clone()).into()),
Some(element) => {
let element_type = Self::jsonval_to_type_helper(element, action_id);
match element_type {
Ok(t) => Ok(Type::Set {
element_type: Some(Box::new(t)),
}),
Err(_) => element_type,
}
}
},
CedarValueJson::EntityEscape { __entity: _ } => Err(UnsupportedActionAttributeError(
action_id.clone(),
"entity escape (`__entity`)".into(),
)
.into()),
CedarValueJson::ExprEscape { __expr: _ } => Err(UnsupportedActionAttributeError(
action_id.clone(),
"expression escape (`__expr`)".into(),
)
.into()),
CedarValueJson::ExtnEscape { __extn: _ } => Err(UnsupportedActionAttributeError(
action_id.clone(),
"extension function escape (`__extn`)".into(),
)
.into()),
CedarValueJson::Null => {
Err(UnsupportedActionAttributeError(action_id.clone(), "null".into()).into())
}
}
}
}
type ResolveFunc<T> = dyn FnOnce(&HashMap<&InternalName, Type>) -> crate::err::Result<T>;
pub(crate) enum WithUnresolvedCommonTypeRefs<T> {
WithUnresolved(Box<ResolveFunc<T>>),
WithoutUnresolved(T),
}
impl<T: 'static> WithUnresolvedCommonTypeRefs<T> {
pub fn new(
f: impl FnOnce(&HashMap<&InternalName, Type>) -> crate::err::Result<T> + 'static,
) -> Self {
Self::WithUnresolved(Box::new(f))
}
pub fn map<U: 'static>(
self,
f: impl FnOnce(T) -> U + 'static,
) -> WithUnresolvedCommonTypeRefs<U> {
match self {
Self::WithUnresolved(_) => WithUnresolvedCommonTypeRefs::new(|common_type_defs| {
self.resolve_common_type_refs(common_type_defs).map(f)
}),
Self::WithoutUnresolved(v) => WithUnresolvedCommonTypeRefs::WithoutUnresolved(f(v)),
}
}
pub fn resolve_common_type_refs(
self,
common_type_defs: &HashMap<&InternalName, Type>,
) -> crate::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)
}
}
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::err::Result<ValidatorNamespaceDef<ConditionalName, ConditionalName>> {
ValidatorNamespaceDef::from_namespace_definition(
None,
self,
ActionBehavior::default(),
Extensions::all_available(),
)
}
}
pub(crate) fn try_jsonschema_type_into_validator_type(
schema_ty: json_schema::Type<InternalName>,
extensions: &Extensions<'_>,
) -> crate::err::Result<WithUnresolvedCommonTypeRefs<Type>> {
match schema_ty {
json_schema::Type::Type(json_schema::TypeVariant::String) => {
Ok(Type::primitive_string().into())
}
json_schema::Type::Type(json_schema::TypeVariant::Long) => {
Ok(Type::primitive_long().into())
}
json_schema::Type::Type(json_schema::TypeVariant::Boolean) => {
Ok(Type::primitive_boolean().into())
}
json_schema::Type::Type(json_schema::TypeVariant::Set { element }) => {
Ok(try_jsonschema_type_into_validator_type(*element, extensions)?.map(Type::set))
}
json_schema::Type::Type(json_schema::TypeVariant::Record(rty)) => {
try_record_type_into_validator_type(rty, extensions)
}
json_schema::Type::Type(json_schema::TypeVariant::Entity { name }) => {
Ok(Type::named_entity_reference(internal_name_to_entity_type(name)?).into())
}
json_schema::Type::Type(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(CommonTypeInvariantViolationError { name: type_name }.into())
}))
}
json_schema::Type::Type(json_schema::TypeVariant::EntityOrCommon { type_name }) => {
Ok(WithUnresolvedCommonTypeRefs::new(move |common_type_defs| {
match common_type_defs.get(&type_name) {
Some(def) => Ok(def.clone()),
None => {
Ok(Type::named_entity_reference(internal_name_to_entity_type(
type_name,
)?))
}
}
}))
}
}
}
pub(crate) fn try_record_type_into_validator_type(
rty: json_schema::RecordType<InternalName>,
extensions: &Extensions<'_>,
) -> crate::err::Result<WithUnresolvedCommonTypeRefs<Type>> {
if cfg!(not(feature = "partial-validate")) && rty.additional_attributes {
Err(UnsupportedFeatureError(UnsupportedFeature::OpenRecordsAndEntities).into())
} else {
Ok(
parse_record_attributes(rty.attributes, extensions)?.map(move |attrs| {
Type::record_with_attributes(
attrs,
if rty.additional_attributes {
OpenTag::OpenAttributes
} else {
OpenTag::ClosedAttributes
},
)
}),
)
}
}
fn parse_record_attributes(
attrs: impl IntoIterator<Item = (SmolStr, json_schema::TypeOfAttribute<InternalName>)>,
extensions: &Extensions<'_>,
) -> crate::err::Result<WithUnresolvedCommonTypeRefs<Attributes>> {
let attrs_with_common_type_refs = attrs
.into_iter()
.map(|(attr, ty)| -> crate::err::Result<_> {
Ok((
attr,
(
try_jsonschema_type_into_validator_type(ty.ty, extensions)?,
ty.required,
),
))
})
.collect::<crate::err::Result<Vec<_>>>()?;
Ok(WithUnresolvedCommonTypeRefs::new(|common_type_defs| {
attrs_with_common_type_refs
.into_iter()
.map(|(s, (attr_ty, is_req))| {
attr_ty
.resolve_common_type_refs(common_type_defs)
.map(|ty| (s, AttributeType::new(ty, is_req)))
})
.collect::<crate::err::Result<Vec<_>>>()
.map(Attributes::with_attributes)
}))
}