odata_client_codegen 0.1.0

Strongly-typed OData client code generation
Documentation
/// Entity model definition structure, informed by:
/// - <http://docs.oasis-open.org/odata/odata-csdl-json/v4.01/odata-csdl-json-v4.01.html>
/// - <http://docs.oasis-open.org/odata/odata-csdl-xml/v4.01/odata-csdl-xml-v4.01.html>
// TODO: Update references in comments to use odata-csdl-xml-v4.01 rather than odata-csdl-json-v4.01
pub mod construct;
pub mod definition_lookup;
pub mod identifiers;
mod primitive_type_defs;

#[cfg(test)]
mod test;

use std::fmt::Display;

use primitive_type_defs::{PrimitiveProperty, PrimitiveType};

use self::{
    definition_lookup::{EdmItem, EdmRef, NamedEdmItem},
    identifiers::{QualifiedName, TargetPath},
};

#[derive(Debug)]
pub struct EntityModel<'arena> {
    pub service_url: String,
    pub schemas: Vec<Schema<'arena>>,
}

#[derive(Debug)]
pub struct Schema<'a> {
    pub namespace: &'a str,
    pub alias: Option<&'a str>,
    pub complex_types: Vec<&'a ComplexType<'a>>,
    pub entity_types: Vec<&'a EntityType<'a>>,
    pub primitive_aliases: Vec<&'a PrimitiveTypeAlias<'a>>,
    pub enum_types: Vec<&'a EnumType<'a>>,
    pub entity_container: &'a EntityContainer<'a>,
}

impl<'a> EdmItem for Schema<'a> {}

impl<'a> NamedEdmItem for Schema<'a> {
    type Name = &'a str;

    fn name(&self) -> Self::Name {
        self.namespace
    }
}

/// Fields shared by both `EntityType` and `ComplexType`
#[derive(Debug)]
pub struct ComplexishType<'a, BaseType: EdmItem + NamedEdmItem<Name = QualifiedName<'a>>> {
    pub name: QualifiedName<'a>,
    pub base_type: Option<EdmRef<'a, BaseType, QualifiedName<'a>>>,
    pub is_open_type: bool,
    pub is_abstract: bool,
    pub properties: Vec<&'a Property<'a>>,
    pub nav_properties: Vec<&'a NavigationProperty<'a>>,
}

#[derive(Debug)]
pub struct ComplexType<'a>(pub ComplexishType<'a, ComplexType<'a>>);

impl<'a> EdmItem for ComplexType<'a> {}

impl<'a> NamedEdmItem for ComplexType<'a> {
    type Name = QualifiedName<'a>;

    fn name(&self) -> Self::Name {
        self.0.name
    }
}

/// During initial construction, the key properties specified directly within an `EntityType`
/// element are set as the `Key` value for this `EdmRef`. This will be checked for validity during
/// the resolution stage, and the `Val` value for this `EdmRef` will either be set to this key, or
/// some base type key.
type KeyPropertiesRef<'a> =
    EdmRef<'a, Option<Vec<KeyProperty<'a>>>, &'a Option<Vec<KeyProperty<'a>>>>;

impl<'a> EdmItem for Vec<KeyProperty<'a>> {
    fn item_type_name() -> &'static str {
        "Key"
    }
}

#[derive(Debug)]
pub struct EntityType<'a> {
    pub complex_type_fields: ComplexishType<'a, EntityType<'a>>,
    pub has_stream: bool,
    pub key_properties: KeyPropertiesRef<'a>,
}

impl<'a> EdmItem for EntityType<'a> {}

impl<'a> NamedEdmItem for EntityType<'a> {
    type Name = QualifiedName<'a>;

    fn name(&self) -> Self::Name {
        self.complex_type_fields.name
    }
}

#[derive(Debug)]
pub struct KeyProperty<'a> {
    pub property: EdmRef<'a, Property<'a>, TargetPath<'a>>,
    pub alias: Option<String>,
}

/// A Property's `type` value may resolve to a primitive alias. In this case, it is valid for the
/// property to specify a default value. So we store the default value with the lookup path in the
/// `EdmRef` to be set during definition resolution.
#[derive(Debug)]
pub struct PropertyVariantLookupKey<'a> {
    pub qualified_name: QualifiedName<'a>,
    pub default_value_str: Option<String>,
}

impl<'a> Display for PropertyVariantLookupKey<'a> {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match &self.default_value_str {
            None => f.write_fmt(format_args!("{}", self.qualified_name)),
            Some(default_value_str) => f.write_fmt(format_args!(
                "{} ({})",
                self.qualified_name, default_value_str
            )),
        }
    }
}

#[derive(Debug)]
pub struct Property<'a> {
    pub name: TargetPath<'a>,
    pub r#type: EdmRef<'a, PropertyVariant<'a>, PropertyVariantLookupKey<'a>>,
    pub nullable: bool,
    pub is_collection: bool,
}

impl<'a> EdmItem for Property<'a> {}

impl<'a> NamedEdmItem for Property<'a> {
    type Name = TargetPath<'a>;

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

#[derive(Debug)]
pub enum PropertyVariant<'a> {
    Primitive(PrimitiveProperty),
    Abstract, // TODO

    // Nominal types
    // TODO: can primitive alias properties have default values? Unclear in odata-csdl-xml-v4.01
    // sec 7.2.7
    // TODO: primitive alias properties can specify additional facet modifiers:
    // odata-csdl-xml-v4.01 sec 11.1. Ideally these should be recorded in the entity model and
    // used to augment the primitive type in codegen, if applicable.
    PrimitiveAlias(&'a PrimitiveTypeAlias<'a>),
    Complex(&'a ComplexType<'a>),
    Enumeration {
        enum_type: &'a EnumType<'a>,
        default_value: Option<i64>,
    },
}

/// This is a union of all nominal & built-in types
impl<'a> EdmItem for PropertyVariant<'a> {
    fn item_type_name() -> &'static str {
        "[Nominal/Primitive/Abstract type]"
    }
}

/// Converted from `TTypeDefinition`
#[derive(Debug)]
pub struct PrimitiveTypeAlias<'a> {
    pub name: QualifiedName<'a>,
    pub r#type: PrimitiveType,
    pub is_collection: bool,
}

impl<'a> EdmItem for PrimitiveTypeAlias<'a> {
    fn item_type_name() -> &'static str {
        "TypeDefinition"
    }
}

impl<'a> NamedEdmItem for PrimitiveTypeAlias<'a> {
    type Name = QualifiedName<'a>;

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

#[derive(Debug)]
pub struct NavigationProperty<'a> {
    pub name: TargetPath<'a>,
    pub r#type: NavigationPropertyVariant<'a>,
    pub type_modifier: NavigationPropertyModifier,
    pub referential_constraints: Vec<ReferentialConstraint<'a>>,
    pub on_delete_action: Option<OnDeleteAction>,
    /// Partner is navigation property belonging to a different type definition
    pub partner: Option<EdmRef<'a, NavigationProperty<'a>, TargetPath<'a>>>,
    pub contains_target: bool,
}

#[derive(Debug, Clone, PartialEq)]
pub enum NavigationPropertyModifier {
    NonNull,
    Collection,
    Nullable,
}

impl<'a> EdmItem for NavigationProperty<'a> {}

impl<'a> NamedEdmItem for NavigationProperty<'a> {
    type Name = TargetPath<'a>;

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

#[derive(Debug)]
pub enum NavigationPropertyVariant<'a> {
    Abstract, // TODO (see odata-csdl-json-v4.01 sec 8.1: abstract can only be `Edm.EntityType`)
    Entity(EdmRef<'a, EntityType<'a>, QualifiedName<'a>>),
}

/// Properties are members of the same type definition as the parent navigation property
#[derive(Debug)]
pub struct ReferentialConstraint<'a> {
    pub property: EdmRef<'a, Property<'a>, TargetPath<'a>>,
    pub referenced_property: EdmRef<'a, Property<'a>, TargetPath<'a>>,
}

impl<'a> EdmItem for ReferentialConstraint<'a> {}

pub type OnDeleteAction = crate::entity_data_model_parse::edm::TOnDeleteAction;

#[derive(Debug, PartialEq)]
pub struct EnumType<'a> {
    pub name: QualifiedName<'a>,
    pub members: Vec<EnumTypeMember>,
    pub is_flags: bool,
    pub underlying_type: EnumUnderlyingType,
}

impl<'a> EdmItem for EnumType<'a> {}

impl<'a> NamedEdmItem for EnumType<'a> {
    type Name = QualifiedName<'a>;

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

#[derive(Debug, PartialEq)]
pub enum EnumUnderlyingType {
    Byte,
    SByte,
    Int16,
    Int32,
    Int64,
}

#[derive(Debug, PartialEq)]
pub struct EnumTypeMember {
    pub name: String,
    pub value: i64,
}

#[derive(Debug)]
pub struct EntityContainer<'a> {
    pub name: QualifiedName<'a>,
    pub extends: Option<EdmRef<'a, EntityContainer<'a>, QualifiedName<'a>>>,
    pub entity_sets: Vec<&'a EntitySet<'a>>,
    pub singletons: Vec<&'a Singleton<'a>>,
    /* TODO:
     * - action imports
     * - function imports
     * - extends */
}

impl<'a> EdmItem for EntityContainer<'a> {}

impl<'a> NamedEdmItem for EntityContainer<'a> {
    type Name = QualifiedName<'a>;

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

#[derive(Debug)]
pub struct EntitySet<'a> {
    pub name: TargetPath<'a>,
    pub entity_type: EdmRef<'a, EntityType<'a>, QualifiedName<'a>>,
    pub include_in_service_document: bool,
    pub navigation_property_bindings: Vec<NavigationPropertyBinding<'a>>,
}

impl<'a> EdmItem for EntitySet<'a> {}

impl<'a> NamedEdmItem for EntitySet<'a> {
    type Name = TargetPath<'a>;

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

#[derive(Debug)]
pub struct NavigationPropertyBinding<'a> {
    /// If local/unqualified, `path` is a property member of the parent set's `entity_type`
    pub path: EdmRef<'a, NavigationProperty<'a>, TargetPath<'a>>,
    pub target: EdmRef<'a, EntitySet<'a>, TargetPath<'a>>,
}

impl<'a> EdmItem for NavigationPropertyBinding<'a> {}

#[derive(Debug)]
pub struct Singleton<'a> {
    pub name: TargetPath<'a>,
    pub entity_type: EdmRef<'a, EntityType<'a>, QualifiedName<'a>>,
    pub nullable: bool,
    pub navigation_property_bindings: Vec<NavigationPropertyBinding<'a>>,
}

impl<'a> EdmItem for Singleton<'a> {}

impl<'a> NamedEdmItem for Singleton<'a> {
    type Name = TargetPath<'a>;

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