use crate::hyperql::{CompilerError, CompilerResult};
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub struct TypeMapper;
impl TypeMapper {
pub fn new() -> Self {
Self
}
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> }),
_ => {
let ident = format_ident!("{}", hyperql_type);
Ok(quote! { #ident })
}
}
}
pub fn rust_to_manifold(&self, rust_type: &str) -> CompilerResult<TokenStream> {
Ok(quote! {
bincode::serialize(&value).map_err(|e| QueryError::SerializationError(e.to_string()))?
})
}
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()))?
})
}
pub fn table_to_schema_type(&self, table: &str) -> String {
self.to_pascal_case(table)
}
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()
}
pub fn parse_return_type(&self, return_type: &str) -> CompilerResult<TokenStream> {
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> });
}
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> });
}
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); }
#[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"
);
}
}