use anyhow::anyhow;
use derive_more::{From, TryInto};
use std::{
any::type_name,
cell::Cell,
collections::HashMap,
convert::{TryFrom, TryInto},
fmt,
fmt::{Debug, Display},
hash::Hash,
};
use super::{
identifiers::QualifiedName, identifiers::TargetPath, ComplexType, ComplexishType,
EntityContainer, EntitySet, EntityType, EnumType, NavigationProperty, PrimitiveTypeAlias,
Property, Singleton,
};
pub trait EdmItem {
fn item_type_name() -> &'static str {
type_name::<Self>()
}
}
pub trait NamedEdmItem {
type Name: Display + Debug + Eq + Hash;
fn name(&self) -> Self::Name;
}
pub struct EdmLookup<'a> {
schema_lookup: HashMap<QualifiedName<'a>, SchemaLookupEntry<'a>>,
namespace_alias_lookup: HashMap<&'a str, &'a str>,
target_path_lookup: HashMap<TargetPath<'a>, TargetPathLookupEntry<'a>>,
reverse_lookup: HashMap<QualifiedName<'a>, Vec<TargetPath<'a>>>,
}
impl<'ar> EdmLookup<'ar> {
pub fn new() -> Self {
EdmLookup {
schema_lookup: HashMap::new(),
namespace_alias_lookup: HashMap::new(),
target_path_lookup: HashMap::new(),
reverse_lookup: HashMap::new(),
}
}
pub fn set_named_item<Key, Val>(&mut self, val: &'ar Val) -> Result<(), anyhow::Error>
where
Key: EdmLookupKey<'ar>,
Val: NamedEdmItem<Name = Key> + EdmItem + 'ar,
&'ar Val: Into<Key::LookupEntry>,
{
let key: Key = val.name();
let entry = val.into();
key.set_lookup_entry(entry, self)
}
fn get_item<Key, Val>(&self, key: &Key) -> Result<&'ar Val, anyhow::Error>
where
Key: EdmLookupKey<'ar>,
Val: EdmItem + 'ar,
&'ar Val: TryFrom<Key::LookupEntry>,
{
let &item_entry = self.get_entry(key)?;
item_entry.try_into().map_err(|_| {
anyhow!(
"Definition '{}' is unexpected type: expected {}, but is {}",
key,
Val::item_type_name(),
item_entry.item_type_name()
)
})
}
pub fn get_entry<Key: EdmLookupKey<'ar>>(
&self,
key: &Key,
) -> Result<&Key::LookupEntry, anyhow::Error> {
match key.get_lookup_entry(self) {
Some(entry) => Ok(entry),
None => Err(anyhow!("Entity model definition {} not present", key)),
}
}
pub fn set_alias(&mut self, alias: &'ar str, namespace: &'ar str) -> Result<(), anyhow::Error> {
if self.namespace_alias_lookup.contains_key(alias) {
Err(anyhow!(
"Multiple namespaces defined with alias '{}'",
alias
))
} else {
self.namespace_alias_lookup.insert(alias, namespace);
Ok(())
}
}
pub fn register_targeted_type(
&mut self,
targeting_member: TargetPath<'ar>,
targeted_type_name: QualifiedName<'ar>,
) {
self.reverse_lookup
.entry(targeted_type_name)
.or_insert(Vec::new())
.push(targeting_member);
}
pub fn get_target_path_references(
&self,
type_name: QualifiedName<'ar>,
) -> Vec<&TargetPathLookupEntry<'ar>> {
match self.reverse_lookup.get(&type_name) {
Some(nav_prop_paths) => nav_prop_paths
.iter()
.map(|nav_prop_path| self.get_entry(nav_prop_path).unwrap())
.collect(),
None => Vec::new(),
}
}
}
pub trait EdmLookupEntry: Copy + Debug {
fn item_type_name(&self) -> &'static str;
}
#[derive(Debug, TryInto, From, Clone, Copy)]
pub enum SchemaLookupEntry<'a> {
PrimitiveAlias(&'a PrimitiveTypeAlias<'a>),
#[try_into(ignore)]
Abstract, Entity(&'a EntityType<'a>),
Complex(&'a ComplexType<'a>),
Enumeration(&'a EnumType<'a>),
EntityContainer(&'a EntityContainer<'a>),
}
impl<'a> EdmLookupEntry for SchemaLookupEntry<'a> {
fn item_type_name(&self) -> &'static str {
match self {
SchemaLookupEntry::PrimitiveAlias(_) => PrimitiveTypeAlias::item_type_name(),
SchemaLookupEntry::Abstract => "Abstract", SchemaLookupEntry::Entity(_) => EntityType::item_type_name(),
SchemaLookupEntry::Complex(_) => ComplexType::item_type_name(),
SchemaLookupEntry::Enumeration(_) => EnumType::item_type_name(),
SchemaLookupEntry::EntityContainer(_) => EntityContainer::item_type_name(),
}
}
}
#[derive(Debug, TryInto, From, Clone, Copy)]
pub enum TargetPathLookupEntry<'a> {
EntitySet(&'a EntitySet<'a>),
Singleton(&'a Singleton<'a>),
Property(&'a Property<'a>),
NavProp(&'a NavigationProperty<'a>),
}
impl<'a> EdmLookupEntry for TargetPathLookupEntry<'a> {
fn item_type_name(&self) -> &'static str {
match self {
TargetPathLookupEntry::EntitySet(_) => EntitySet::item_type_name(),
TargetPathLookupEntry::Singleton(_) => Singleton::item_type_name(),
TargetPathLookupEntry::Property(_) => Property::item_type_name(),
TargetPathLookupEntry::NavProp(_) => NavigationProperty::item_type_name(),
}
}
}
pub trait EdmLookupKey<'ar>: Sized + Eq + Hash + Debug + Display + 'ar {
type LookupEntry: EdmLookupEntry + 'ar;
fn get_lookup_entry<'lk>(&self, lookup: &'lk EdmLookup<'ar>) -> Option<&'lk Self::LookupEntry>;
fn set_lookup_entry<'lk>(
self,
entry: Self::LookupEntry,
lookup: &'lk mut EdmLookup<'ar>,
) -> Result<(), anyhow::Error>;
}
impl<'ar> EdmLookupKey<'ar> for QualifiedName<'ar> {
type LookupEntry = SchemaLookupEntry<'ar>;
fn get_lookup_entry<'lk>(&self, lookup: &'lk EdmLookup<'ar>) -> Option<&'lk Self::LookupEntry> {
let normalised_name = self.with_namespace(&lookup.namespace_alias_lookup);
lookup.schema_lookup.get(&normalised_name)
}
fn set_lookup_entry<'lk>(
self,
entry: Self::LookupEntry,
lookup: &'lk mut EdmLookup<'ar>,
) -> Result<(), anyhow::Error> {
let normalised_name = self.with_namespace(&lookup.namespace_alias_lookup);
if lookup.schema_lookup.contains_key(&normalised_name) {
Err(anyhow!("Multiple items defined with path {}", self))
} else {
lookup.schema_lookup.insert(normalised_name, entry);
Ok(())
}
}
}
impl<'ar> EdmLookupKey<'ar> for TargetPath<'ar> {
type LookupEntry = TargetPathLookupEntry<'ar>;
fn get_lookup_entry<'lk>(&self, lookup: &'lk EdmLookup<'ar>) -> Option<&'lk Self::LookupEntry> {
let normalised_path = self.with_namespace(&lookup.namespace_alias_lookup);
if let Some(entry) = lookup.target_path_lookup.get(&normalised_path) {
return Some(entry);
}
fn search_base_type<'ar, 'lk, T: EdmItem + NamedEdmItem<Name = QualifiedName<'ar>>>(
target_path: TargetPath<'ar>,
lookup: &'lk EdmLookup<'ar>,
complex_type_fields: &'ar ComplexishType<'ar, T>,
) -> Option<&'lk TargetPathLookupEntry<'ar>> {
if let Some(base_type) = &complex_type_fields.base_type {
let base_type_qualified_name = base_type.get().name();
let base_type_target_path = base_type_qualified_name.with_path(target_path.path);
base_type_target_path.get_lookup_entry(lookup)
} else {
None
}
}
match lookup
.schema_lookup
.get(&normalised_path.parent_qualified_name())
{
Some(SchemaLookupEntry::Entity(EntityType {
complex_type_fields,
..
})) => search_base_type(normalised_path, lookup, complex_type_fields),
Some(SchemaLookupEntry::Complex(ComplexType(complex_type_fields))) => {
search_base_type(normalised_path, lookup, complex_type_fields)
}
_ => None,
}
}
fn set_lookup_entry<'lk>(
self,
entry: Self::LookupEntry,
lookup: &'lk mut EdmLookup<'ar>,
) -> Result<(), anyhow::Error> {
let normalised_path = self.with_namespace(&lookup.namespace_alias_lookup);
if lookup.target_path_lookup.contains_key(&normalised_path) {
Err(anyhow!("Multiple items defined with path {}", self))
} else {
lookup.target_path_lookup.insert(normalised_path, entry);
Ok(())
}
}
}
pub struct EdmRef<'a, Val, Key> {
key: Option<Key>,
val: Cell<Option<&'a Val>>,
}
impl<'a, Val: fmt::Debug, Key: fmt::Debug> fmt::Debug for EdmRef<'a, Val, Key> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let EdmRef { key, val } = self;
match (key, val.get()) {
(Some(key), None) => f.write_fmt(format_args!("Unset({:?})", key)),
(Some(key), Some(_val)) => f.write_fmt(format_args!("Set({:?})", key)),
(None, Some(val)) => f.write_fmt(format_args!("SetNoRef({:?})", val)),
(None, None) => panic!("invalid EdmRef state"),
}
}
}
impl<'a, Val, Key> EdmRef<'a, Val, Key> {
pub fn new(lookup_ref: Key) -> Self {
EdmRef {
key: Some(lookup_ref),
val: Cell::new(None),
}
}
pub fn new_set(val: &'a Val) -> Self {
EdmRef {
key: None,
val: Cell::new(Some(val)),
}
}
pub fn set(&self, val: &'a Val) {
let EdmRef { key, val: prev_val } = self;
match (key, prev_val.get()) {
(Some(_key), None) => {
self.val.set(Some(val));
}
(_, Some(_)) => panic!("tried to set typeref which already has value"),
(None, None) => panic!("invalid EdmRef state"),
}
}
pub fn get(&self) -> &'a Val {
let EdmRef { key, val } = self;
match (key, val.get()) {
(_, Some(val)) => val,
(Some(_), None) => panic!("accessed typeref which hasn't been set"),
(None, None) => panic!("invalid EdmRef state"),
}
}
pub fn get_key(&self) -> &Key {
match &self.key {
Some(key) => key,
None => panic!("tried to acces key of keyless typeref"),
}
}
pub fn is_set(&self) -> bool {
self.val.get().is_some()
}
}
impl<'ar, 'lk, Key, Val> EdmRef<'ar, Val, Key>
where
Key: EdmLookupKey<'ar>,
Val: EdmItem,
&'ar Val: TryFrom<Key::LookupEntry>,
{
pub fn set_from_lookup(&self, lookup: &'lk EdmLookup<'ar>) -> Result<(), anyhow::Error> {
let key = self.get_key();
let item = lookup.get_item(key)?;
self.set(item);
Ok(())
}
}