mouscache_derive 0.4.0

A utility crate implement trait to store object either in redis or in memory
Documentation
use syn;
use syn::{Ident, DeriveInput};
use quote::Tokens;
use proc_macro;
use proc_macro2::Span;

use attr::*;

pub fn impl_cacheable(input: proc_macro::TokenStream) -> Result<Tokens, String> {
    let input: DeriveInput = syn::parse(input).unwrap();

    let name: &Ident = &input.ident;

    let usages = expand_usages();

    let impl_block = expand_cacheable_impl_block(&input)?;

    let dummy_const = Ident::new(&format!("_IMPL_DESERIALIZE_FOR_{}", name), Span::call_site());

    Ok(quote! {
        #[allow(non_upper_case_globals, unused_attributes, unused_qualifications, unused_imports)]
        const #dummy_const: () = {
            #usages

            #impl_block
        };
    })
}

fn expand_usages() -> Tokens {
    quote! {
        use std::any::Any;
        use std::string::ToString;
        use std::collections::hash_map::HashMap;
        use ::mouscache::CacheError;
        use ::mouscache::Result;
    }
}

fn expand_cacheable_impl_block(input: &DeriveInput) -> Result<Tokens, String> {
    let ident: &Ident = &input.ident;

    let data_attrs = validate_data_attributes(&input.attrs)?;

    let base_func = expand_base_function(ident, data_attrs);

    let redis_func = expand_redis_function(input)?;

    Ok(quote! {
        impl ::mouscache::Cacheable for #ident {
            #base_func

            #redis_func
        }
    })
}

fn expand_base_function(ident: &Ident, data_attrs: DataAttribute) -> Tokens {
    let ident = if let Some(name) = data_attrs.rename {
        Ident::new(name.as_str(), ident.span())
    } else {
        ident.clone()
    };

    let expires_after_func = if let Some(ttl) = data_attrs.expires {
        quote! {
            fn expires_after(&self) -> Option<usize> {
                Option::from(#ttl)
            }
        }
    } else {
        quote! {
            fn expires_after(&self) -> Option<usize> {
                None
            }
        }
    };

    quote! {
        #[inline]
        fn model_name() -> &'static str where Self: Sized {
            stringify!(#ident)
        }

        fn as_any(&self) -> &Any {
            self
        }

        #expires_after_func
    }
}

fn expand_redis_function(input: &DeriveInput) -> Result<Tokens, String> {
    let struct_ident: &Ident = &input.ident;

    let fields = match input.data {
        syn::Data::Struct(ref data) => data.fields.clone(),
        syn::Data::Enum(_) => return Err(String::from("#[derive(Cacheable)] only apply to structs at this time")),
        syn::Data::Union(_) => return Err(String::from("#[derive(Cacheable)] only apply to structs at this time")),
    };

    let named_fields = match fields {
        syn::Fields::Named(ref f_named) => f_named.named.clone(),
        syn::Fields::Unnamed(_) => return Err(String::from("Unnamed fields are not implemented at this time -> #[derive(Cacheable)] only apply to structs with named fields")),
        syn::Fields::Unit => return Err(String::from("Unit fields are not implemented at this time -> #[derive(Cacheable)] only apply to structs with named fields")),
    };

    let mut field_tokens: Vec<Tokens> = Vec::new();

    for f in named_fields.iter() {
        if let Some(ref ident) = f.ident {
            field_tokens.push(quote!(String::from(stringify!(#ident)), self.#ident.to_string()))
        }
    }

    let hmap_ident = Ident::new("map", Span::call_site());

    let mut field_deser_tokens: Vec<Tokens> = Vec::new();

    for f in named_fields.iter() {
        let t = f.ty.clone();
        if let Some(ref ident) = f.ident {
            field_deser_tokens.push(quote! {
                let #ident = if let Some(obj) = #hmap_ident.get(&stringify!(#ident).to_string()) {
                    match obj.parse::<#t>() {
                        Ok(o) => o,
                        _ => return Err(CacheError::Other(format!("Unable to parse field {} from string into {}", stringify!(#ident), stringify!(#(f.ty))))),
                    }
                } else {
                   return Err(CacheError::Other(format!("Unable to parse field {}", stringify!(#ident))));
                };
            });
        }
    }

    let mut struct_ident_vec: Vec<Tokens> = Vec::new();

    for f in named_fields.iter() {
        if let Some(ref ident) = f.ident {
            struct_ident_vec.push(quote!(#ident,));
        }
    }

    let return_token = quote! {
        return Ok(#struct_ident {
            #(#struct_ident_vec)*
        });
    };

    Ok(quote! {
        fn to_redis_obj(&self) -> Vec<(String, String)> {
            let mut temp_vec = Vec::new();
            #(temp_vec.push((#field_tokens));)*
            temp_vec
        }

        fn from_redis_obj(#hmap_ident: HashMap<String, String>) -> Result<Self> where Self: Sized {
            if #hmap_ident.len() > 0 {
                #(#field_deser_tokens)*

                #return_token
            }
            return Err(CacheError::Other(String::new()));
        }
    })
}