use proc_macro::TokenStream; use quote::{format_ident, quote}; use syn::{parse_macro_input, DeriveInput}; use convert_case::{Case, Casing}; fn parse_fk(attrs: &[syn::Attribute]) -> bool { for attr in attrs { if attr.path.segments.len() == 1 && attr.path.segments.last().unwrap().ident == "microrm_foreign" { return true; } } false } fn derive_columns<'a, I: Iterator>( input: &DeriveInput, microrm_ref: &proc_macro2::TokenStream, fields: I, ) -> proc_macro2::TokenStream { let struct_name = &input.ident; let columns_name = format_ident!("_{}_columns", &input.ident.to_string().to_case(Case::Snake)); let mut index = 0usize; let mut column_types = Vec::new(); let mut column_impls = Vec::new(); let mut column_consts = Vec::new(); let mut column_array = Vec::new(); for name in fields { let original_case = name.ident.as_ref().unwrap().clone(); let snake_case = original_case.to_string(); if snake_case != snake_case.to_case(Case::Snake) { return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case")); } let converted_case = format_ident!("{}", original_case.to_string().to_case(Case::UpperCamel)); let ty = &name.ty; let mut fk_table_name = quote! { None }; let mut fk_column_name = quote! { None }; if parse_fk(&name.attrs) { fk_table_name = quote! { Some(<<#ty as #microrm_ref::entity::EntityID>::Entity as #microrm_ref::entity::Entity>::table_name()) }; fk_column_name = quote! { Some("id") }; } index += 1; column_types.push(quote! { pub struct #converted_case (); }); column_impls.push(quote! { impl #microrm_ref::entity::EntityColumn for #columns_name::#converted_case { type Entity = #struct_name; fn sql_type(&self) -> &'static str { <#ty as #microrm_ref::model::Modelable>::column_type() } fn index(&self) -> usize { #index } fn name(&self) -> &'static str { #snake_case } fn fk_table_name(&self) -> Option<&'static str> { #fk_table_name } fn fk_column_name(&self) -> Option<&'static str> { #fk_column_name } } }); column_consts.push(quote! { #[allow(non_upper_case_globals)] pub const #converted_case : #columns_name::#converted_case = #columns_name::#converted_case(); }); column_array.push(quote! { & #columns_name::#converted_case() }); } let columns_array_name = format_ident!( "{}_COLUMNS", struct_name.to_string().to_case(Case::ScreamingSnake) ); quote! { pub mod #columns_name { pub struct ID (); #(#column_types)* } impl #microrm_ref::entity::EntityColumn for #columns_name::ID { type Entity = #struct_name; fn sql_type(&self) -> &'static str { "integer" } fn index(&self) -> usize { 0 } fn name(&self) -> &'static str { "id" } fn fk_table_name(&self) -> Option<&'static str> { None } fn fk_column_name(&self) -> Option<&'static str> { None } } #(#column_impls)* impl #struct_name { pub const ID : #columns_name::ID = #columns_name::ID(); #(#column_consts)* } #microrm_ref::re_export::lazy_static::lazy_static!{ static ref #columns_array_name: [&'static dyn #microrm_ref::entity::EntityColumn;#index + 1] = { [ &#columns_name::ID(), #(#column_array),* ] }; } } } fn derive_id( input: &DeriveInput, microrm_ref: &proc_macro2::TokenStream, ) -> proc_macro2::TokenStream { let struct_name = &input.ident; let id_name = format_ident!("{}ID", &input.ident); quote! { #[derive(Debug,PartialEq,Clone,Copy,#microrm_ref::re_export::serde::Serialize,#microrm_ref::re_export::serde::Deserialize)] #[allow(unused)] pub struct #id_name (i64); impl #microrm_ref::entity::EntityID for #id_name { type Entity = #struct_name; fn from_raw_id(raw: i64) -> Self { Self(raw) } fn raw_id(&self) -> i64 { self.0 } } impl #microrm_ref::model::Modelable for #id_name { fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> { use #microrm_ref::re_export::sqlite::Bindable; self.0.bind(stmt, col) } fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self, usize)> where Self: Sized { stmt.read::(col_offset).map(|x| (#id_name(x), 1)) } fn column_type() -> &'static str where Self: Sized { "integer" } } } } pub(crate) fn derive(tokens: TokenStream) -> TokenStream { let input = parse_macro_input!(tokens as DeriveInput); let microrm_ref = crate::parse_microrm_ref(&input.attrs); let struct_name = &input.ident; let id_name = format_ident!("{}ID", &input.ident); let table_name = format!("{}", struct_name).to_case(Case::Snake); let st = match &input.data { syn::Data::Struct(st) => st, _ => panic!("Can only use derive(Entity) on structs!"), }; let fields = match &st.fields { syn::Fields::Named(fields) => fields, _ => panic!("Can only use derive(Entity) on non-unit structs with named fields!"), }; let mut variants = Vec::new(); let mut value_references = Vec::new(); let mut build_clauses = Vec::new(); // let mut foreign_keys = Vec::new(); // let mut foreign_key_impls = Vec::new(); let mut index: usize = 0; let column_output = derive_columns(&input, µrm_ref, fields.named.iter()); let id_output = derive_id(&input, µrm_ref); for name in fields.named.iter() { let original_case = name.ident.as_ref().unwrap().clone(); let snake_case = original_case.to_string().to_case(Case::Snake); if original_case != snake_case { return quote::quote_spanned!(original_case.span() => compile_error!("Names must be in snake_case")).into(); } let converted_case = format_ident!("{}", original_case.to_string().to_case(Case::UpperCamel)); variants.push(converted_case.clone()); let field_name = name.ident.as_ref().unwrap().clone(); value_references.push(quote! { &self. #field_name }); let ty = &name.ty; index += 1; build_clauses.push(quote! { #field_name: <#ty as #microrm_ref::model::Modelable>::build_from(stmt, #index)?.0 }); /* if parse_fk(&name.attrs) { let fk_struct_name = format_ident!("{}{}ForeignKey", struct_name, converted_case); let ty = &name.ty; foreign_keys.push(quote! { &#fk_struct_name { col: #columns_name::#converted_case } }); foreign_key_impls.push(quote!{ struct #fk_struct_name { col: #columns_name } impl #microrm_ref::entity::EntityForeignKey<#columns_name> for #fk_struct_name { /* fn local_column(&self) -> &#columns_name { &self.col } fn foreign_table_name(&self) -> &'static str { <<#ty as #microrm_ref::entity::EntityID>::Entity as #microrm_ref::entity::Entity>::table_name() } fn foreign_column_name(&self) -> &'static str { "id" } */ } }); } */ } let columns_array_name = format_ident!( "{}_COLUMNS", struct_name.to_string().to_case(Case::ScreamingSnake) ); let field_count = fields.named.iter().count(); let columns_name = format_ident!("_{}_columns", &input.ident.to_string().to_case(Case::Snake)); quote!{ // Related types for #struct_name #column_output #id_output // Implementations for #struct_name impl #microrm_ref::entity::Entity for #struct_name { type ID = #id_name; type IDColumn = #columns_name::ID; fn table_name() -> &'static str { #table_name } fn column_count() -> usize { // +1 for ID column #field_count + 1 } fn visit_values (Result<(), E>)>(&self, visit: &mut F) -> Result<(),E> { #(visit(#value_references)?);*; Ok(()) } fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement) -> #microrm_ref::re_export::sqlite::Result { Ok(Self { #(#build_clauses),* }) } fn columns() -> &'static [&'static dyn #microrm_ref::entity::EntityColumn] { #columns_array_name.as_ref() } fn id_column() -> Self::IDColumn { Self::ID } /*fn foreign_keys() -> &'static [&'static dyn #microrm_ref::entity::EntityForeignKey] { &[#(#foreign_keys),*] }*/ } // Foreign key struct implementations // #(#foreign_key_impls)* }.into() }