use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput};
pub fn derive(tokens: TokenStream) -> TokenStream {
let input = parse_macro_input!(tokens as DeriveInput);
if let syn::Data::Enum(e) = &input.data {
return derive_modelable_enum(&input, e);
}
if let syn::Data::Struct(s) = &input.data {
if s.fields.len() == 1 {
return derive_transparent_struct(&input, s);
}
}
derive_modelable_general(&input)
}
fn derive_transparent_struct(input: &syn::DeriveInput, ds: &syn::DataStruct) -> TokenStream {
let microrm_ref = crate::parse_microrm_ref(&input.attrs);
let struct_name = &input.ident;
let field = ds.fields.iter().next().unwrap();
let (bind_to, build_from) = if let Some(i) = &field.ident {
(
quote! { self.#i.bind_to(stmt, col) },
quote! { Self { #i: field.0 } },
)
} else {
(
quote! { self.0.bind_to(stmt, col) },
quote! { Self(field.0) },
)
};
let field_ty = &field.ty;
quote! {
impl #microrm_ref::model::Modelable for #struct_name {
fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
#bind_to
}
fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
let field = #field_ty::build_from(stmt, col_offset)?;
Ok((#build_from, field.1))
}
fn column_type() -> &'static str where Self: Sized {
<#field_ty as #microrm_ref::model::Modelable>::column_type()
}
}
}.into()
}
fn derive_modelable_enum(input: &syn::DeriveInput, de: &syn::DataEnum) -> TokenStream {
for variant in &de.variants {
if !variant.fields.is_empty() {
return derive_modelable_general(input);
}
}
let microrm_ref = crate::parse_microrm_ref(&input.attrs);
let enum_name = &input.ident;
let mut variant_names = Vec::new();
let mut variant_name_strs = Vec::new();
for variant in &de.variants {
variant_names.push(variant.ident.clone());
variant_name_strs.push(variant.ident.to_string());
}
quote! {
impl #microrm_ref::model::Modelable for #enum_name {
fn bind_to(&self, stmt: &mut #microrm_ref::re_export::sqlite::Statement, col: usize) -> #microrm_ref::re_export::sqlite::Result<()> {
match self {
#(
Self::#variant_names => #variant_name_strs.bind_to(stmt, col)
),*
}
}
fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
let str_form = String::build_from(stmt, col_offset)?.0;
#(
if str_form == #variant_name_strs { return Ok((Self::#variant_names, 1)) }
)*
return Err(#microrm_ref::re_export::sqlite::Error { code: None, message: None })
}
fn column_type() -> &'static str where Self: Sized {
"text"
}
}
}.into()
}
fn derive_modelable_general(input: &syn::DeriveInput) -> TokenStream {
let microrm_ref = crate::parse_microrm_ref(&input.attrs);
let ident = &input.ident;
quote!{
impl #microrm_ref::model::Modelable for #ident {
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;
use #microrm_ref::model::Modelable;
serde_json::to_string(self).expect("can be serialized").bind_to(stmt, col)
}
fn build_from(stmt: &#microrm_ref::re_export::sqlite::Statement, col_offset: usize) -> #microrm_ref::re_export::sqlite::Result<(Self,usize)> {
use #microrm_ref::re_export::sqlite;
use #microrm_ref::model::Modelable;
let str_data = stmt.read::<String>(col_offset).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
let data = serde_json::from_str(str_data.as_str()).map_err(|e| sqlite::Error { code: None, message: Some(e.to_string()) })?;
Ok((data,1))
}
fn column_type() -> &'static str where Self: Sized {
"blob"
}
}
}.into()
}