use std::convert::TryFrom;
use anyhow::{anyhow, Context as AnyhowContext};
use bumpalo::Bump;
use crate::entity_data_model_parse::{edm, edmx};
use super::{
definition_lookup::{
EdmItem, EdmLookup, EdmRef, NamedEdmItem, SchemaLookupEntry, TargetPathLookupEntry,
},
identifiers::{QualifiedName, TargetPath},
primitive_type_defs::{PrimitiveProperty, PrimitiveType},
ComplexType, ComplexishType, EntityContainer, EntityModel, EntitySet, EntityType, EnumType,
EnumTypeMember, EnumUnderlyingType, KeyPropertiesRef, KeyProperty, NavigationProperty,
NavigationPropertyBinding, NavigationPropertyModifier, NavigationPropertyVariant,
PrimitiveTypeAlias, Property, PropertyVariant, PropertyVariantLookupKey, ReferentialConstraint,
Schema, Singleton,
};
fn schema_from_parsed<'ar, 'ctx>(
schema_parsed: edm::Schema,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<Schema<'ar>, anyhow::Error> {
let namespace = ctx.arena.alloc(schema_parsed.namespace.0.0).as_str();
let alias = schema_parsed.alias.map(|a| ctx.arena.alloc(a.0.0).as_str());
if let Some(alias) = alias {
ctx.lookup.set_alias(alias, namespace)?;
}
let mut complex_types: Vec<&ComplexType> = Vec::new();
let mut entity_types: Vec<&EntityType> = Vec::new();
let mut primitive_aliases: Vec<&PrimitiveTypeAlias> = Vec::new();
let mut enum_types: Vec<&EnumType> = Vec::new();
for t_complex_type in schema_parsed.complex_types {
let ctx_msg = format!(
"Constructing complex type \"{}\"",
&t_complex_type
.derivable_type_attributes
.type_attributes
.name
.0
.0
);
let complex_type: &ComplexType = ctx
.arena
.alloc(complex_type_from_parsed(t_complex_type, namespace, ctx).context(ctx_msg)?);
ctx.lookup.set_named_item(complex_type)?;
complex_types.push(complex_type);
}
for t_entity_type in schema_parsed.entity_types {
let ctx_msg = format!(
"Constructing entity type \"{}\"",
&t_entity_type
.derivable_type_attributes
.type_attributes
.name
.0
.0
);
let entity_type = ctx
.arena
.alloc(entity_type_from_parsed(t_entity_type, namespace, ctx).context(ctx_msg)?);
ctx.lookup.set_named_item(entity_type)?;
entity_types.push(entity_type);
}
for t_type_definition in schema_parsed.type_definitions {
let ctx_msg = format!(
"Constructing type definition \"{}\"",
&t_type_definition.name.0.0
);
let primitive_type_alias = ctx.arena.alloc(
primitive_type_alias_from_parsed(t_type_definition, namespace, ctx).context(ctx_msg)?,
);
ctx.lookup.set_named_item(primitive_type_alias)?;
primitive_aliases.push(primitive_type_alias);
}
for t_enum_type in schema_parsed.enum_types {
let ctx_msg = format!(
"Constructing enum type \"{}\"",
&t_enum_type.type_attributes.name.0.0
);
let enum_type = ctx
.arena
.alloc(enum_type_from_parsed(t_enum_type, namespace, ctx).context(ctx_msg)?);
ctx.lookup.set_named_item(enum_type)?;
enum_types.push(enum_type);
}
if schema_parsed.entity_containers.len() != 1 {
return Err(anyhow!(
"Expected exactly one entity container in schema; found {}",
schema_parsed.entity_containers.len()
));
}
let mut entity_containers = schema_parsed.entity_containers;
let t_entity_container = entity_containers.pop().unwrap();
let entity_container = ctx.arena.alloc(
entity_container_from_parsed(t_entity_container, namespace, ctx)
.context("Constructing entity container")?,
);
ctx.lookup.set_named_item(entity_container)?;
Ok(Schema {
namespace,
alias,
complex_types,
entity_types,
primitive_aliases,
enum_types,
entity_container,
})
}
fn complex_type_from_parsed<'ar, 'ctx>(
t_complex_type: edm::TComplexType,
qualifier: &'ar str,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<ComplexType<'ar>, anyhow::Error> {
let edm::TComplexType {
derivable_type_attributes,
open_type,
properties,
navigation_properties,
..
} = t_complex_type;
let is_open_type = open_type.0;
let inner = complexish_type_from_parsed(
derivable_type_attributes,
is_open_type,
properties,
navigation_properties,
qualifier,
ctx,
)?;
Ok(ComplexType(inner))
}
fn entity_type_from_parsed<'ar, 'ctx>(
t_entity_type: edm::TEntityType,
qualifier: &'ar str,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<EntityType<'ar>, anyhow::Error> {
let edm::TEntityType {
key,
properties,
navigation_properties,
derivable_type_attributes,
open_type,
has_stream,
..
} = t_entity_type;
let is_open_type = open_type.0;
let has_stream = has_stream.0;
let complex_type_fields = complexish_type_from_parsed(
derivable_type_attributes,
is_open_type,
properties,
navigation_properties,
qualifier,
ctx,
)?;
let key_properties: KeyPropertiesRef = match key {
Some(key) => {
if key.property_refs.is_empty() {
return Err(anyhow!(
"EntityType key element should specify at least one property"
));
}
let mut key_properties = Vec::new();
for edm::TPropertyRef {
name: key_prop_name,
alias,
} in key.property_refs
{
let key_prop_name = ctx.arena.alloc(key_prop_name.0.0).as_str();
let alias = alias.map(|a| a.0.0);
let key_prop_is_nested = key_prop_name.contains('/');
if key_prop_is_nested && alias.is_none() {
return Err(anyhow!(
"Nested key property '{}' for EntityType '{}' should have an alias",
key_prop_name,
complex_type_fields.name
));
}
if !key_prop_is_nested && alias.is_some() {
return Err(anyhow!(
"Primitive key property '{}' for EntityType '{}' should not have an alias",
key_prop_name,
complex_type_fields.name
));
}
let target_path = complex_type_fields.name.with_path(key_prop_name);
key_properties.push(KeyProperty {
property: EdmRef::new(target_path),
alias,
});
}
EdmRef::new(ctx.arena.alloc(Some(key_properties)))
}
None => EdmRef::new(ctx.arena.alloc(None)),
};
Ok(EntityType {
complex_type_fields,
has_stream,
key_properties,
})
}
fn complexish_type_from_parsed<'ar, 'ctx, T: EdmItem + NamedEdmItem<Name = QualifiedName<'ar>>>(
dta: edm::TDerivableTypeAttributes,
is_open_type: bool,
t_properties: Vec<edm::TProperty>,
t_nav_properties: Vec<edm::TNavigationProperty>,
qualifier: &'ar str,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<ComplexishType<'ar, T>, anyhow::Error> {
let name = QualifiedName {
qualifier,
uq_name: ctx.arena.alloc(dta.type_attributes.name.0.0).as_str(),
};
let mut properties = Vec::new();
let mut nav_properties = Vec::new();
for t_property in t_properties {
let ctx_msg = format!(
"Constructing property \"{}\"",
&t_property.common_property_attributes.name.0.0
);
let property = property_from_parsed(t_property, name, ctx).context(ctx_msg)?;
ctx.lookup.set_named_item(property)?;
properties.push(property);
}
for t_nav_property in t_nav_properties {
let ctx_msg = format!(
"Constructing navigation property \"{}\"",
&t_nav_property.name.0.0
);
let nav_property = nav_property_from_parsed(t_nav_property, name, ctx).context(ctx_msg)?;
ctx.lookup.set_named_item(nav_property)?;
nav_properties.push(nav_property);
}
let base_type = match dta.base_type {
Some(t_qualified_name) => {
let qualified_name = QualifiedName::from_parsed(ctx.arena.alloc(t_qualified_name))?;
Some(EdmRef::new(qualified_name))
}
None => None,
};
Ok(ComplexishType {
name,
base_type,
is_open_type,
is_abstract: dta.r#abstract.0,
properties,
nav_properties,
})
}
fn property_from_parsed<'ar, 'ctx>(
t_prop: edm::TProperty,
parent_qualified_name: QualifiedName<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<&'ar Property<'ar>, anyhow::Error> {
let edm::TCommonPropertyAttributes {
name,
r#type: type_name,
nullable,
default_value,
facet_attributes,
} = t_prop.common_property_attributes;
let prop_path = parent_qualified_name.with_path(ctx.arena.alloc(name.0.0).as_str());
let nullable = nullable.0;
let default_value_str = default_value.map(|d| d.0);
let (prop_variant, is_collection) = match type_name {
edm::TTypeName::Primitive(t_primitive_type) => {
let (prim_prop, is_collection) = PrimitiveProperty::from_property_attributes(
t_primitive_type,
facet_attributes,
default_value_str,
)?;
(
EdmRef::new_set(ctx.arena.alloc(PropertyVariant::Primitive(prim_prop)) as &_),
is_collection,
)
}
edm::TTypeName::Abstract(_) => (
EdmRef::new_set(ctx.arena.alloc(PropertyVariant::Abstract) as &_),
false,
),
edm::TTypeName::CollectionQualified(collection_qualified) => {
let t_qualified_name = ctx.arena.alloc(collection_qualified.0);
let qualified_name = QualifiedName::from_parsed(t_qualified_name)?;
ctx.lookup.register_targeted_type(prop_path, qualified_name);
(
EdmRef::new(PropertyVariantLookupKey {
qualified_name,
default_value_str,
}),
true,
)
}
edm::TTypeName::Qualified(t_qualified_name) => {
let t_qualified_name = ctx.arena.alloc(t_qualified_name);
let qualified_name = QualifiedName::from_parsed(t_qualified_name)?;
ctx.lookup.register_targeted_type(prop_path, qualified_name);
(
EdmRef::new(PropertyVariantLookupKey {
qualified_name,
default_value_str,
}),
false,
)
}
};
let property = ctx.arena.alloc(Property {
name: prop_path,
r#type: prop_variant,
nullable,
is_collection,
});
Ok(property)
}
fn nav_property_from_parsed<'ar, 'ctx>(
prop: edm::TNavigationProperty,
entity_name: QualifiedName<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<&'ar NavigationProperty<'ar>, anyhow::Error> {
let edm::TNavigationProperty {
referential_constraints,
mut on_deletes,
name,
r#type: type_name,
nullable,
partner,
contains_target,
..
} = prop;
let nav_prop_path = entity_name.with_path(ctx.arena.alloc(name.0.0).as_str());
let nullable = nullable.map(|n| n.0);
let contains_target = contains_target.0;
let on_delete_action = match on_deletes.len() {
1 => Some(on_deletes.pop().unwrap().action),
0 => None,
len => {
return Err(anyhow!(
"Expected 0 or 1 OnDelete elements for NavigationProperty, found {}",
len,
));
}
};
let (prop_variant, type_modifier) = match type_name {
edm::TTypeName::Primitive(_) => {
return Err(anyhow!(
"Navigation property type cannot be a literal primitive",
));
}
edm::TTypeName::Abstract(_) => {
let modifier = if nullable == Some(true) {
NavigationPropertyModifier::Nullable
} else {
NavigationPropertyModifier::NonNull
};
(NavigationPropertyVariant::Abstract, modifier)
}
edm::TTypeName::CollectionQualified(collection_qualified) => {
if nullable.is_some() {
return Err(anyhow!(
"Nullable attribute should not be set for `NavigationProperty`s with a collection type",
));
}
let t_qualified_name = ctx.arena.alloc(collection_qualified.0);
let qualified_name = QualifiedName::from_parsed(t_qualified_name)?;
ctx.lookup
.register_targeted_type(nav_prop_path, qualified_name);
(
NavigationPropertyVariant::Entity(EdmRef::new(qualified_name)),
NavigationPropertyModifier::Collection,
)
}
edm::TTypeName::Qualified(t_qualified_name) => {
let t_qualified_name = ctx.arena.alloc(t_qualified_name);
let qualified_name = QualifiedName::from_parsed(t_qualified_name)?;
let modifier = match nullable {
None | Some(true) => NavigationPropertyModifier::Nullable,
Some(false) => NavigationPropertyModifier::NonNull,
};
ctx.lookup
.register_targeted_type(nav_prop_path, qualified_name);
(
NavigationPropertyVariant::Entity(EdmRef::new(qualified_name)),
modifier,
)
}
};
let referential_constraints: Vec<ReferentialConstraint> = referential_constraints
.into_iter()
.map(|r| {
let prop_target_path = {
let prop_name = ctx.arena.alloc(r.property.0.0);
entity_name.with_path(prop_name)
};
let refd_prop_target_path = {
let prop_name = ctx.arena.alloc(r.referenced_property.0.0);
entity_name.with_path(prop_name)
};
ReferentialConstraint {
property: EdmRef::new(prop_target_path),
referenced_property: EdmRef::new(refd_prop_target_path),
}
})
.collect();
let partner = partner.map(|p| {
let partner_name = ctx.arena.alloc(p.0.0);
let target_path = entity_name.with_path(partner_name);
EdmRef::new(target_path)
});
let nav_property = ctx.arena.alloc(NavigationProperty {
name: nav_prop_path,
r#type: prop_variant,
type_modifier,
referential_constraints,
on_delete_action,
partner,
contains_target,
});
Ok(nav_property)
}
fn primitive_type_alias_from_parsed<'ar, 'ctx>(
t_type_definition: edm::TTypeDefinition,
namespace: &'ar str,
ctx: &'ctx ConstructContext<'ar>,
) -> Result<PrimitiveTypeAlias<'ar>, anyhow::Error> {
let edm::TTypeDefinition {
name,
underlying_type,
facet_attributes,
..
} = t_type_definition;
let name = QualifiedName {
qualifier: namespace,
uq_name: ctx.arena.alloc(name.0.0).as_str(),
};
let (primitive_type, is_collection) =
PrimitiveType::from_property_attributes(underlying_type, facet_attributes)?;
Ok(PrimitiveTypeAlias {
name,
r#type: primitive_type,
is_collection,
})
}
fn enum_type_from_parsed<'ar, 'ctx>(
t_enum_type: edm::TEnumType,
namespace: &'ar str,
ctx: &'ctx ConstructContext<'ar>,
) -> Result<EnumType<'ar>, anyhow::Error> {
let edm::TEnumType {
type_attributes,
members,
is_flags,
underlying_type,
..
} = t_enum_type;
let name = QualifiedName {
qualifier: namespace,
uq_name: ctx.arena.alloc(type_attributes.name.0.0).as_str(),
};
let is_flags = is_flags.0;
let underlying_type = match underlying_type {
None => EnumUnderlyingType::Int32,
Some(edm::TTypeName::Primitive(edm::TPrimitiveType::EdmByte)) => EnumUnderlyingType::Byte,
Some(edm::TTypeName::Primitive(edm::TPrimitiveType::EdmSByte)) => EnumUnderlyingType::SByte,
Some(edm::TTypeName::Primitive(edm::TPrimitiveType::EdmInt16)) => EnumUnderlyingType::Int16,
Some(edm::TTypeName::Primitive(edm::TPrimitiveType::EdmInt32)) => EnumUnderlyingType::Int32,
Some(edm::TTypeName::Primitive(edm::TPrimitiveType::EdmInt64)) => EnumUnderlyingType::Int64,
Some(t_type_name) => return Err(anyhow!("Invalid underlying enum type {:?}", t_type_name)),
};
let all_members_have_values = members.iter().all(|m| m.value.is_some());
let no_members_have_values = members.iter().all(|m| m.value.is_none());
if is_flags && !all_members_have_values {
return Err(anyhow!(
"Members of flags enum '{}' should all specify values",
name
));
}
if !all_members_have_values && !no_members_have_values {
return Err(anyhow!(
"Either all members, or no members, should have values for enum '{}'",
name
));
}
let members = members
.into_iter()
.enumerate()
.map(|(i, m)| EnumTypeMember {
name: m.name.0.0,
value: if all_members_have_values {
m.value.unwrap().0
} else {
i as i64
},
})
.collect();
Ok(EnumType {
name,
members,
is_flags,
underlying_type,
})
}
fn entity_container_from_parsed<'ar, 'ctx>(
t_entity_container: edm::TEntityContainer,
qualifier: &'ar str,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<EntityContainer<'ar>, anyhow::Error> {
let edm::TEntityContainer {
entity_sets: t_entity_sets,
singletons: t_singletons,
name,
extends: t_extends,
..
} = t_entity_container;
let container_qualified_name = QualifiedName {
qualifier,
uq_name: ctx.arena.alloc(name.0.0).as_str(),
};
let mut entity_sets = Vec::new();
let mut singletons = Vec::new();
for t_entity_set in t_entity_sets {
let entity_set = ctx.arena.alloc(entity_set_from_parsed(
t_entity_set,
container_qualified_name,
ctx,
)?);
ctx.lookup.set_named_item(entity_set)?;
entity_sets.push(entity_set as &_);
}
for t_singleton in t_singletons {
let singleton = ctx.arena.alloc(singleton_from_parsed(
t_singleton,
container_qualified_name,
ctx,
)?);
ctx.lookup.set_named_item(singleton)?;
singletons.push(singleton as &_);
}
let extends = match t_extends {
Some(t_qualified_name) => {
let qualified_name = QualifiedName::from_parsed(ctx.arena.alloc(t_qualified_name))?;
Some(EdmRef::new(qualified_name))
}
None => None,
};
Ok(EntityContainer {
name: container_qualified_name,
extends,
entity_sets,
singletons,
})
}
fn entity_set_from_parsed<'ar, 'ctx>(
t_entity_set: edm::TEntitySet,
container_qualified_name: QualifiedName<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<EntitySet<'ar>, anyhow::Error> {
let edm::TEntitySet {
navigation_property_bindings,
entity_set_attributes:
edm::TEntitySetAttributes {
name,
entity_type,
include_in_service_document,
},
..
} = t_entity_set;
let name = container_qualified_name.with_path(ctx.arena.alloc(name.0.0).as_str());
let t_entity_type_qualified_name = ctx.arena.alloc(entity_type);
let entity_type_qualified_name = QualifiedName::from_parsed(t_entity_type_qualified_name)?;
let entity_type = EdmRef::new(entity_type_qualified_name);
let include_in_service_document = include_in_service_document.0;
let navigation_property_bindings = navigation_property_bindings
.into_iter()
.map(|n| {
navigation_propery_binding_from_parsed(
n,
container_qualified_name,
entity_type_qualified_name,
ctx,
)
})
.collect::<Result<Vec<_>, _>>()?;
ctx.lookup
.register_targeted_type(name, entity_type_qualified_name);
Ok(EntitySet {
name,
entity_type,
include_in_service_document,
navigation_property_bindings,
})
}
fn singleton_from_parsed<'ar, 'ctx>(
t_singleton: edm::TSingleton,
container_qualified_name: QualifiedName<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<Singleton<'ar>, anyhow::Error> {
let edm::TSingleton {
name,
r#type,
nullable,
navigation_property_bindings: t_navigation_property_bindings,
..
} = t_singleton;
let name = container_qualified_name.with_path(ctx.arena.alloc(name.0.0).as_str());
let type_qualified_name = QualifiedName::from_parsed(ctx.arena.alloc(r#type))?;
let entity_type = EdmRef::new(type_qualified_name);
let nullable = nullable.0;
let navigation_property_bindings = t_navigation_property_bindings
.into_iter()
.map(|n| {
navigation_propery_binding_from_parsed(
n,
container_qualified_name,
type_qualified_name,
ctx,
)
})
.collect::<Result<Vec<_>, _>>()?;
ctx.lookup.register_targeted_type(name, type_qualified_name);
Ok(Singleton {
name,
entity_type,
nullable,
navigation_property_bindings,
})
}
fn navigation_propery_binding_from_parsed<'ar, 'ctx>(
t_navigation_property_binding: edm::TNavigationPropertyBinding,
container_qualified_name: QualifiedName<'ar>,
entity_type_qualified_name: QualifiedName<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<NavigationPropertyBinding<'ar>, anyhow::Error> {
let t_navigation_property_binding = ctx.arena.alloc(t_navigation_property_binding);
let path = {
let target_path = TargetPath::from_parsed(
&t_navigation_property_binding.path,
entity_type_qualified_name,
)?;
EdmRef::new(target_path)
};
let target = {
let target_path = TargetPath::from_parsed(
&t_navigation_property_binding.target,
container_qualified_name,
)?;
EdmRef::new(target_path)
};
Ok(NavigationPropertyBinding { path, target })
}
fn schema_resolve_refs<'ar, 'ctx>(
schema: &'ctx Schema<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<(), anyhow::Error> {
for complex_type in &schema.complex_types {
complexish_type_resolve_refs(&complex_type.0, ctx).with_context(|| {
format!(
"Resolving references in complex type definition '{}'",
complex_type.0.name
)
})?;
}
for entity_type in &schema.entity_types {
complexish_type_resolve_refs(&entity_type.complex_type_fields, ctx).with_context(|| {
format!(
"Resolving references in entity type definition '{}'",
entity_type.complex_type_fields.name
)
})?;
if let Some(key_properties) = entity_type.key_properties.get_key() {
for key_prop in key_properties {
key_prop
.property
.set_from_lookup(&ctx.lookup)
.with_context(|| {
format!(
"Resolving key property '{}' on entity type '{}'",
key_prop.property.get_key(),
entity_type.complex_type_fields.name
)
})?;
}
}
}
if let Some(extends) = &schema.entity_container.extends {
extends
.set_from_lookup(&ctx.lookup)
.with_context(|| format!("Resolving base schema '{}'", extends.get_key()))?;
}
for entity_set in &schema.entity_container.entity_sets {
entity_set
.entity_type
.set_from_lookup(&ctx.lookup)
.with_context(|| {
format!(
"Resolving entity type '{}' for entity set '{}'",
entity_set.entity_type.get_key(),
entity_set.name
)
})?;
for nav_prop_binding in &entity_set.navigation_property_bindings {
let resolve_result = navigation_property_binding_resolve_refs(nav_prop_binding, ctx)
.with_context(|| {
format!(
"Resolving references for navigation property binding in entity set '{}'",
entity_set.name
)
});
match (ctx.config.skip_invalid_nav_prop_bindings, resolve_result) {
(false, Err(e)) => {
return Err(e);
}
_ => {}
}
}
}
for singleton in &schema.entity_container.singletons {
singleton
.entity_type
.set_from_lookup(&ctx.lookup)
.with_context(|| {
format!(
"Resolving entity type '{}', for singleton '{}'",
singleton.entity_type.get_key(),
singleton.name
)
})?;
for nav_prop_binding in &singleton.navigation_property_bindings {
let resolve_result = navigation_property_binding_resolve_refs(nav_prop_binding, ctx)
.with_context(|| {
format!(
"Resolving references for navigation property binding in singleton '{}'",
singleton.name
)
});
match (ctx.config.skip_invalid_nav_prop_bindings, resolve_result) {
(false, Err(e)) => {
return Err(e);
}
_ => {}
}
}
}
Ok(())
}
fn complexish_type_resolve_refs<'ar, 'ctx, T: EdmItem + NamedEdmItem<Name = QualifiedName<'ar>>>(
complexish_type: &'ar ComplexishType<'ar, T>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<(), anyhow::Error>
where
T: EdmItem,
&'ar T: TryFrom<SchemaLookupEntry<'ar>>,
{
if let Some(base_type) = &complexish_type.base_type {
base_type.set_from_lookup(&ctx.lookup).with_context(|| {
format!(
"Resolving base type '{}' of type '{}'",
base_type.get_key(),
complexish_type.name,
)
})?;
}
for property in &complexish_type.properties {
if !property.r#type.is_set() {
let property_type = ctx.arena.alloc(
resolve_property_type(property.r#type.get_key(), ctx).with_context(|| {
format!(
"Resolving type '{}' for property '{}'",
property.r#type.get_key(),
property.name
)
})?,
);
property.r#type.set(property_type);
}
}
for nav_prop in &complexish_type.nav_properties {
match &nav_prop.r#type {
NavigationPropertyVariant::Abstract => {} NavigationPropertyVariant::Entity(entity_ref) => {
if !entity_ref.is_set() {
entity_ref.set_from_lookup(&ctx.lookup).with_context(|| {
format!(
"Resolving type '{}' for navigation property '{}'",
entity_ref.get_key(),
nav_prop.name
)
})?;
}
}
}
}
Ok(())
}
fn resolve_property_type<'ar, 'ctx>(
key: &'ar PropertyVariantLookupKey<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<PropertyVariant<'ar>, anyhow::Error> {
let PropertyVariantLookupKey {
qualified_name,
default_value_str,
} = key;
let type_item = ctx.lookup.get_entry(qualified_name)?;
if default_value_str.is_some() {
match type_item {
SchemaLookupEntry::Enumeration(_) => {}
_ => {
return Err(anyhow!(
"Default value attribute is not valid for property type '{}'",
qualified_name
));
}
}
}
match type_item {
SchemaLookupEntry::PrimitiveAlias(p) => Ok(PropertyVariant::PrimitiveAlias(p)),
SchemaLookupEntry::Abstract => Ok(PropertyVariant::Abstract), SchemaLookupEntry::Entity(_) => Err(anyhow!("Property refers to an entity type")),
SchemaLookupEntry::Complex(c) => Ok(PropertyVariant::Complex(c)),
SchemaLookupEntry::Enumeration(e) => {
let default_value = match default_value_str {
Some(default_value_str) => Some(default_value_str.parse().with_context(|| {
format!("Default value '{}' is not a valid int", default_value_str)
})?),
None => None,
};
Ok(PropertyVariant::Enumeration {
enum_type: e,
default_value,
})
}
SchemaLookupEntry::EntityContainer(_) => Err(anyhow!(
"Entity container '{}' is not a valid property type",
qualified_name
)),
}
}
fn navigation_property_binding_resolve_refs<'ar, 'ctx>(
nav_prop_binding: &'ar NavigationPropertyBinding<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<(), anyhow::Error> {
nav_prop_binding
.path
.set_from_lookup(&ctx.lookup)
.with_context(|| {
format!(
"Resolving navigation property binding path '{}'",
nav_prop_binding.path.get_key(),
)
})?;
nav_prop_binding
.target
.set_from_lookup(&ctx.lookup)
.with_context(|| {
format!(
"Resolving navigation property binding target '{}'",
nav_prop_binding.target.get_key(),
)
})?;
Ok(())
}
fn schema_resolve_transitive_refs<'ar, 'ctx>(
schema: &'ctx Schema<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
) -> Result<(), anyhow::Error> {
for entity_type in &schema.entity_types {
resolve_base_key_props(entity_type, ctx, Vec::new()).with_context(|| {
format!(
"Resolving key properties from base types for entity type '{}'",
entity_type.complex_type_fields.name
)
})?;
}
Ok(())
}
fn resolve_base_key_props<'ar, 'ctx>(
entity_type: &'ar EntityType<'ar>,
ctx: &'ctx mut ConstructContext<'ar>,
mut traversed_subtypes: Vec<&'ar QualifiedName<'ar>>,
) -> Result<&'ar Option<Vec<KeyProperty<'ar>>>, anyhow::Error> {
if entity_type.key_properties.is_set() {
return Ok(entity_type.key_properties.get());
}
let direct_key_props = entity_type.key_properties.get_key();
let key_properties = if let Some(base_type_ref) =
entity_type.complex_type_fields.base_type.as_ref()
{
let base_type_ref_key = base_type_ref.get_key();
let base_type = base_type_ref.get();
if traversed_subtypes.contains(&base_type_ref_key) {
return Err(anyhow!(
"Detected base type reference cycle from '{}' onwards",
traversed_subtypes[0]
));
}
traversed_subtypes.push(base_type_ref_key);
let base_key_props = resolve_base_key_props(base_type, ctx, traversed_subtypes)?;
match (base_key_props, direct_key_props) {
(Some(base_key_props_), Some(direct_key_props_)) => {
let keys_identical =
base_key_props_
.iter()
.zip(direct_key_props_.iter())
.all(|(b, d)| {
(b.property.get().name, &b.alias) == (d.property.get().name, &d.alias)
});
if !keys_identical {
return Err(anyhow!(
"Entity type specifies key properties, but a base type also specifies key properties"
));
} else {
direct_key_props
}
}
(Some(_), None) => base_key_props,
(None, _) => direct_key_props,
}
} else {
direct_key_props
};
if key_properties.is_none() {
let used_in_collection = ctx
.lookup
.get_target_path_references(entity_type.name())
.into_iter()
.any(|referencing_item| match referencing_item {
TargetPathLookupEntry::EntitySet(_) => true,
TargetPathLookupEntry::NavProp(nav_prop) => {
nav_prop.type_modifier == NavigationPropertyModifier::Collection
}
_ => false,
});
if !entity_type.complex_type_fields.is_abstract && used_in_collection {
return Err(anyhow!(
"No key properties found for non-abstract type which is used in a collection"
));
}
}
entity_type.key_properties.set(key_properties);
Ok(key_properties)
}
#[derive(Debug, Clone)]
pub struct ConstructConfig {
pub skip_invalid_nav_prop_bindings: bool,
}
impl Default for ConstructConfig {
fn default() -> Self {
ConstructConfig {
skip_invalid_nav_prop_bindings: false,
}
}
}
struct ConstructContext<'ar> {
lookup: EdmLookup<'ar>,
arena: &'ar Bump,
config: ConstructConfig,
}
pub fn construct_entity_model<'ar>(
t_edmx: edmx::TEdmx,
service_url: String,
arena: &'ar Bump,
config: ConstructConfig,
) -> Result<EntityModel<'ar>, anyhow::Error> {
let lookup = EdmLookup::new();
let mut schemas = Vec::new();
let mut ctx = ConstructContext {
lookup,
arena,
config,
};
for schema_parsed in t_edmx.data_services.schemas {
let ctx_msg = format!("Constructing schema \"{}\"", schema_parsed.namespace.0.0);
let schema = schema_from_parsed(schema_parsed, &mut ctx).context(ctx_msg)?;
schemas.push(schema);
}
for schema in schemas.iter() {
schema_resolve_refs(schema, &mut ctx).with_context(|| {
format!(
"Resolving references within schema \"{}\"",
schema.namespace
)
})?;
}
for schema in schemas.iter() {
schema_resolve_transitive_refs(schema, &mut ctx).with_context(|| {
format!(
"Resolving transitive references within schema \"{}\"",
schema.namespace
)
})?;
}
Ok(EntityModel {
service_url,
schemas,
})
}