use proc_macro::TokenStream; use quote::quote; use syn::parse_macro_input; use syn::DeriveInput; #[proc_macro_derive(Model, attributes(sql_type))] pub fn derive_model(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as DeriveInput); let name = &ast.ident; let fields = match &ast.data { syn::Data::Struct(data) => match &data.fields { syn::Fields::Named(fields) => fields.named.iter(), _ => panic!("expected named fields"), }, _ => panic!("expected struct"), } .collect(); let modelimpl = impl_model(&name, &fields); let tryfromrow = impl_tryfrom(&fields); let mut ts = TokenStream::new(); ts.extend(modelimpl); ts.extend(tryfromrow); ts } fn impl_model(name: &syn::Ident, fields: &Vec<&syn::Field>) -> TokenStream { let param_holders = vec![quote!(?); fields.len()]; let mut table_name = format!("{}s", name); table_name.make_ascii_lowercase(); let field_names: Vec<_> = fields .iter() .map(|f| f.ident.as_ref().expect("expected named field")) .collect(); let sql_types = fields.iter().map(|f| { for attr in &f.attrs { if let Some(attrident) = attr.path.get_ident() { if attrident.to_string() == "sql_type" { return attr.parse_args().expect("failed to read sql_type"); } } } match &f.ty { syn::Type::Path(tp) => match tp.path.get_ident() { Some(ident) => match ident.to_string().as_ref() { "bool" => quote!(BOOL NOT NULL), "String" => quote!(TEXT NOT NULL), _ => panic!("Don't know how to map to SQL type {}", ident.to_string()), }, None => panic!("Unsupported type path"), }, _ => panic!("Don't know how to map to SQL type {:?}", f.ty), } }); let gen = quote! { impl Model<'_> for #name { fn create_table(conn: &rusqlite::Connection) -> rusqlite::Result { conn.execute( std::stringify!( CREATE TABLE #table_name ( #(#field_names #sql_types),* ) ), rusqlite::NO_PARAMS, ) } fn drop_table(conn: &rusqlite::Connection) -> rusqlite::Result { conn.execute(std::stringify!( DROP TABLE IF EXISTS #table_name ), rusqlite::NO_PARAMS, ) } fn insert(self, conn: &rusqlite::Connection) -> rusqlite::Result { let mut stmt = conn .prepare(std::stringify!( INSERT INTO #table_name (#(#field_names),*) VALUES (#(#param_holders),*) )) .unwrap(); println!("{:?}", stmt); stmt.execute(self.into_params()) } fn into_params(self) -> std::vec::IntoIter> { let ret: Vec> = vec![ #(Box::new(self.#field_names),)* ]; ret.into_iter() } } }; gen.into() } fn impl_tryfrom(fields: &Vec<&syn::Field>) -> TokenStream { let field_names = fields .iter() .map(|f| f.ident.as_ref().expect("expected named field")); let gen = quote! { impl<'a> std::convert::TryFrom<&'a rusqlite::Row<'a>> for Transaction { type Error = rusqlite::Error; fn try_from(row: &rusqlite::Row<'_>) -> rusqlite::Result { Ok(Self { #(#field_names: row.get(row.column_index(std::stringify!(#field_names))?)?,)* }) } } }; gen.into() }