odata_client_codegen 0.1.0

Strongly-typed OData client code generation
Documentation
//! Functionality & types related to resolving references between type definitions within an entity
//! model.

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,
};

/// Types which represent a single entity model schema item.
pub trait EdmItem {
    /// The name of this type of entity model item as used in the EDM/EDMX spec. Override this
    /// implementation if the Rust type name does not match the item name used in schema
    /// documents.
    fn item_type_name() -> &'static str {
        type_name::<Self>()
    }
}

/// Entity model schema items which are addressable by a canonical name.
pub trait NamedEdmItem {
    type Name: Display + Debug + Eq + Hash;

    fn name(&self) -> Self::Name;
}

/// Collection of all defined entity model items in a document.
pub struct EdmLookup<'a> {
    /// Lookup of all item types which are direct children of a Schema
    schema_lookup: HashMap<QualifiedName<'a>, SchemaLookupEntry<'a>>,

    /// Lookup of namespace aliases to the full namespace name
    namespace_alias_lookup: HashMap<&'a str, &'a str>,

    /// Lookup of all item types which are descendants of schema children
    target_path_lookup: HashMap<TargetPath<'a>, TargetPathLookupEntry<'a>>,

    /// Reverse lookup of entity model types to properties/entity sets/singletons which target the
    /// given type.
    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(())
        }
    }

    /// Register a type in the entity model as being used by a property/entity set/singleton.
    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 {
    /// The schema-based name for the type of item currently referenced by this entry.
    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, // TODO
    Entity(&'a EntityType<'a>),
    Complex(&'a ComplexType<'a>),
    Enumeration(&'a EnumType<'a>),
    EntityContainer(&'a EntityContainer<'a>),
}

impl<'a> EdmLookupEntry for SchemaLookupEntry<'a> {
    /// The name of this type of entity model item as used in the EDM/EDMX spec.
    fn item_type_name(&self) -> &'static str {
        match self {
            SchemaLookupEntry::PrimitiveAlias(_) => PrimitiveTypeAlias::item_type_name(),
            SchemaLookupEntry::Abstract => "Abstract", // TODO
            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> {
    /// The name of this type of entity model item as used in the EDM/EDMX spec.
    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);
        }

        // Cannot find item at this literal target path

        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(())
        }
    }
}

/// Contains the lookup value for a type definition during entity model construction. Ordinarily
/// contains a reference to the type definition once construction is complete.
///
/// If the value referenced by `key` could not be resolved, an error will usually be thrown during
/// construction - unless explicitly configured not to, as a workaround to process inconsistent
/// entity models.
pub struct EdmRef<'a, Val, Key> {
    /// Will be `None` if `val` is populated on construction; no lookup ref necessary/available
    key: Option<Key>,
    /// Should be `Some(_)` once reference resolution is completed
    val: Cell<Option<&'a Val>>,
}

impl<'a, Val: fmt::Debug, Key: fmt::Debug> fmt::Debug for EdmRef<'a, Val, Key> {
    /// The lookup key is printed rather than the value contents in most instances, to avoid
    /// reference cycles.
    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)),
            // Having no initial key should imply that the backing data originated from this
            // `EdmRef` - so we should print its full debug here, as it won't appear elsewhere.
            (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()
    }
}

// 'ar: Arena lifetime, 'lk: lookup lifetime
impl<'ar, 'lk, Key, Val> EdmRef<'ar, Val, Key>
where
    Key: EdmLookupKey<'ar>,
    Val: EdmItem,
    &'ar Val: TryFrom<Key::LookupEntry>,
{
    /// Set the value of an `EdmRef` from the lookup of all items in the document.
    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(())
    }
}