rusqlite-model-derive 0.1.0

Derive implementation for `rusqlite_model::Model`
Documentation
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<usize> {
                conn.execute(
                    std::stringify!(
                        CREATE TABLE #table_name (
                            #(#field_names #sql_types),*
                        )
                    ),
                    rusqlite::NO_PARAMS,
                )
            }

            fn drop_table(conn: &rusqlite::Connection) -> rusqlite::Result<usize> {
            conn.execute(std::stringify!(
                    DROP TABLE IF EXISTS #table_name
                ),
                rusqlite::NO_PARAMS,
            )
            }

            fn insert(self, conn: &rusqlite::Connection) -> rusqlite::Result<usize> {
                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<Box<dyn rusqlite::ToSql>> {
                let ret: Vec<Box<dyn rusqlite::ToSql>> = 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<Self> {
                Ok(Self {
                    #(#field_names: row.get(row.column_index(std::stringify!(#field_names))?)?,)*
                })
            }
        }
    };

    gen.into()
}