audb-codegen 0.1.11

Code generation for AuDB compile-time database applications
//! Type Mapper
//!
//! Maps between HyperQL types, Rust types, and Manifold storage types.

use crate::hyperql::{CompilerError, CompilerResult};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};

/// Maps types between HyperQL, Rust, and Manifold
pub struct TypeMapper;

impl TypeMapper {
    /// Create a new type mapper
    pub fn new() -> Self {
        Self
    }

    /// Map HyperQL type string to Rust type
    pub fn hyperql_to_rust(&self, hyperql_type: &str) -> CompilerResult<TokenStream> {
        match hyperql_type {
            "Integer" => Ok(quote! { i64 }),
            "Float" => Ok(quote! { f64 }),
            "String" => Ok(quote! { String }),
            "Boolean" => Ok(quote! { bool }),
            "EntityId" => Ok(quote! { uuid::Uuid }),
            "Timestamp" => Ok(quote! { chrono::DateTime<chrono::Utc> }),
            _ => {
                // Assume it's a custom type (schema type)
                let ident = format_ident!("{}", hyperql_type);
                Ok(quote! { #ident })
            }
        }
    }

    /// Map Rust type to Manifold storage serialization
    pub fn rust_to_manifold(&self, rust_type: &str) -> CompilerResult<TokenStream> {
        // All types in Manifold are stored as Vec<u8> via bincode
        Ok(quote! {
            bincode::serialize(&value).map_err(|e| QueryError::SerializationError(e.to_string()))?
        })
    }

    /// Map Manifold storage to Rust type (deserialization)
    pub fn manifold_to_rust(&self, rust_type: &str) -> CompilerResult<TokenStream> {
        let type_ts = self.hyperql_to_rust(rust_type)?;
        Ok(quote! {
            bincode::deserialize::<#type_ts>(&bytes)
                .map_err(|e| QueryError::SerializationError(e.to_string()))?
        })
    }

    /// Get the schema type for a table name
    pub fn table_to_schema_type(&self, table: &str) -> String {
        // Convert table name to PascalCase schema type
        // e.g., "users" -> "User", "blog_posts" -> "BlogPost"
        self.to_pascal_case(table)
    }

    /// Convert snake_case to PascalCase
    fn to_pascal_case(&self, s: &str) -> String {
        s.split('_')
            .map(|word| {
                let mut chars = word.chars();
                match chars.next() {
                    None => String::new(),
                    Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
                }
            })
            .collect()
    }

    /// Convert return type string to TokenStream
    pub fn parse_return_type(&self, return_type: &str) -> CompilerResult<TokenStream> {
        // Handle Vec<T>
        if return_type.starts_with("Vec<") && return_type.ends_with('>') {
            let inner = &return_type[4..return_type.len() - 1];
            let inner_type = self.hyperql_to_rust(inner)?;
            return Ok(quote! { Vec<#inner_type> });
        }

        // Handle Option<T>
        if return_type.starts_with("Option<") && return_type.ends_with('>') {
            let inner = &return_type[7..return_type.len() - 1];
            let inner_type = self.hyperql_to_rust(inner)?;
            return Ok(quote! { Option<#inner_type> });
        }

        // Simple type
        self.hyperql_to_rust(return_type)
    }
}

impl Default for TypeMapper {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_type_mapper_creation() {
        let mapper = TypeMapper::new();
        assert!(true); // Mapper creates successfully
    }

    #[test]
    fn test_hyperql_to_rust_primitives() {
        let mapper = TypeMapper::new();

        let int_type = mapper.hyperql_to_rust("Integer").unwrap();
        assert_eq!(int_type.to_string(), "i64");

        let float_type = mapper.hyperql_to_rust("Float").unwrap();
        assert_eq!(float_type.to_string(), "f64");

        let string_type = mapper.hyperql_to_rust("String").unwrap();
        assert_eq!(string_type.to_string(), "String");

        let bool_type = mapper.hyperql_to_rust("Boolean").unwrap();
        assert_eq!(bool_type.to_string(), "bool");
    }

    #[test]
    fn test_table_to_schema_type() {
        let mapper = TypeMapper::new();

        assert_eq!(mapper.table_to_schema_type("users"), "Users");
        assert_eq!(mapper.table_to_schema_type("blog_posts"), "BlogPosts");
        assert_eq!(mapper.table_to_schema_type("user"), "User");
    }

    #[test]
    fn test_parse_return_type_vec() {
        let mapper = TypeMapper::new();

        let vec_type = mapper.parse_return_type("Vec<User>").unwrap();
        assert!(vec_type.to_string().contains("Vec"));
        assert!(vec_type.to_string().contains("User"));
    }

    #[test]
    fn test_parse_return_type_option() {
        let mapper = TypeMapper::new();

        let option_type = mapper.parse_return_type("Option<User>").unwrap();
        assert!(option_type.to_string().contains("Option"));
        assert!(option_type.to_string().contains("User"));
    }

    #[test]
    fn test_to_pascal_case() {
        let mapper = TypeMapper::new();

        assert_eq!(mapper.to_pascal_case("user"), "User");
        assert_eq!(mapper.to_pascal_case("blog_post"), "BlogPost");
        assert_eq!(
            mapper.to_pascal_case("user_profile_image"),
            "UserProfileImage"
        );
    }
}