123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274 |
- 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<Item = &'a syn::Field>>(
- 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<Entity = #struct_name>;#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::<i64>(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<E, F: FnMut(&dyn #microrm_ref::model::Modelable) -> (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<Self> {
- Ok(Self {
- #(#build_clauses),*
- })
- }
- fn columns() -> &'static [&'static dyn #microrm_ref::entity::EntityColumn<Entity = Self>] {
- #columns_array_name.as_ref()
- }
- fn id_column() -> Self::IDColumn {
- Self::ID
- }
- /*fn foreign_keys() -> &'static [&'static dyn #microrm_ref::entity::EntityForeignKey<Self::Column>] {
- &[#(#foreign_keys),*]
- }*/
- }
- // Foreign key struct implementations
- // #(#foreign_key_impls)*
- }.into()
- }
|