use super::{
expect_ident,
expect_list,
expect_name_value,
get_elastic_meta_items,
get_ident_from_lit,
get_tokens_from_lit,
};
use quote::Tokens;
use serde_derive_internals::{
self,
attr as serde_attr,
};
use syn;
struct ElasticDocumentMapping {
ident: syn::Ident,
definition: Tokens,
impl_block: Tokens,
}
pub fn expand_derive(
crate_root: Tokens,
input: &syn::MacroInput,
) -> Result<Vec<Tokens>, DeriveElasticTypeError> {
let fields = match input.body {
syn::Body::Struct(ref data) => match *data {
syn::VariantData::Struct(ref fields) => Some(fields),
_ => None,
},
_ => None,
};
let fields = fields.ok_or(DeriveElasticTypeError::InvalidInput)?;
let fields: Vec<(syn::Ident, &syn::Field)> = fields
.iter()
.map(|f| get_ser_field(f))
.filter(|f| f.is_some())
.map(|f| f.unwrap())
.collect();
let mapping = get_mapping(&crate_root, input);
let doc_ty_impl_block = get_doc_ty_impl_block(&crate_root, input, &fields, &mapping.ident);
let props_impl_block = get_props_impl_block(&crate_root, &input.ident, &fields);
let dummy_wrapper = syn::Ident::new(format!("_IMPL_EASTIC_TYPE_FOR_{}", input.ident));
let mapping_definition = &mapping.definition;
let mapping_impl_block = &mapping.impl_block;
Ok(vec![quote!(
#[allow(missing_docs)]
#mapping_definition
#[allow(non_upper_case_globals, dead_code, unused_variables)]
const #dummy_wrapper: () = {
#mapping_impl_block
#doc_ty_impl_block
#props_impl_block
};
)])
}
fn get_mapping(crate_root: &Tokens, input: &syn::MacroInput) -> ElasticDocumentMapping {
fn define_mapping(vis: &syn::Visibility, name: &syn::Ident) -> Tokens {
quote!(
#[derive(Default, Clone, Copy, Debug)]
#vis struct #name;
)
}
fn get_default_mapping(item: &syn::MacroInput) -> syn::Ident {
syn::Ident::from(format!("{}Mapping", item.ident))
}
fn get_mapping_from_attr(item: &syn::MacroInput) -> Option<syn::Ident> {
let val = get_elastic_meta_items(&item.attrs);
let val = val
.iter()
.filter_map(|meta| expect_name_value("mapping", &meta))
.next();
val.and_then(|v| get_ident_from_lit(v).ok())
}
fn impl_document_mapping(
crate_root: &Tokens,
mapping: &syn::Ident,
properties: &syn::Ident,
) -> Tokens {
quote!(
impl #crate_root::derive::ObjectMapping for #mapping {
type Properties = #properties;
}
)
}
if let Some(ident) = get_mapping_from_attr(input) {
ElasticDocumentMapping {
ident,
definition: Tokens::new(),
impl_block: Tokens::new(),
}
} else {
let ident = get_default_mapping(input);
let definition = define_mapping(&input.vis, &ident);
let impl_block = impl_document_mapping(&crate_root, &ident, &input.ident);
ElasticDocumentMapping {
ident,
definition,
impl_block,
}
}
}
fn get_doc_ty_impl_block(
crate_root: &Tokens,
item: &syn::MacroInput,
fields: &[(syn::Ident, &syn::Field)],
mapping: &syn::Ident,
) -> Tokens {
struct MetadataBlock {
instance_methods: Tokens,
static_impls: Tokens,
}
fn get_doc_ty_methods(
crate_root: &Tokens,
item: &syn::MacroInput,
fields: &[(syn::Ident, &syn::Field)],
) -> MetadataBlock {
struct ElasticMetadataMethods {
index: Tokens,
index_is_static: bool,
ty: Tokens,
ty_is_static: bool,
id: Tokens,
}
fn get_doc_type_methods(
crate_root: &Tokens,
item: &syn::MacroInput,
fields: &[(syn::Ident, &syn::Field)],
) -> ElasticMetadataMethods {
fn get_elastic_type_name(item: &syn::MacroInput) -> syn::Lit {
syn::Lit::Str(
format!("{}", item.ident).to_lowercase(),
syn::StrStyle::Cooked,
)
}
let (index, index_is_static) = {
match get_method_from_struct(item, "index") {
Some(MethodFromStruct::Literal(name)) => (name, true),
Some(MethodFromStruct::Expr(expr)) => (expr, false),
_ => {
let name = get_elastic_type_name(item);
(quote!(#name), true)
}
}
};
let (ty, ty_is_static) = {
match get_method_from_struct(item, "ty") {
Some(MethodFromStruct::Literal(name)) => (name, true),
Some(MethodFromStruct::Expr(expr)) => (expr, false),
_ => (quote!(#crate_root::derive::DEFAULT_DOC_TYPE), true),
}
};
let id = get_method_from_struct(item, "id")
.map(|id_expr| match id_expr {
MethodFromStruct::Expr(expr) => expr,
_ => panic!("id attributes on a struct definition must be of the form #[id(expr = \"expression\")]"),
})
.or_else(|| {
get_method_from_fields(fields, "id").map(|field| match field {
MethodFromField::Field(field) => quote!(&self . #field),
MethodFromField::Expr(field, expr) => quote!({
let #field = &self . #field;
#expr
}),
_ => panic!("id attributes on a field must be of the form #[id] or #[id(expr = \"expression\")]"),
})
})
.map(|id_expr| quote!(Some((#id_expr).into())))
.unwrap_or_else(|| quote!(None));
ElasticMetadataMethods {
index,
index_is_static,
ty,
ty_is_static,
id,
}
}
let ElasticMetadataMethods {
ref index,
index_is_static,
ref ty,
ty_is_static,
ref id,
} = get_doc_type_methods(crate_root, item, fields);
let doc_ty = &item.ident;
let (partial_static_index, static_index_block) = if index_is_static {
let method = quote!(
fn partial_static_index() -> ::std::option::Option<&'static str> {
Some(#index)
}
);
let block = quote!(
impl #crate_root::derive::StaticIndex for #doc_ty { }
);
(Some(method), Some(block))
} else {
let method = quote!(
fn partial_static_index() -> ::std::option::Option<&'static str> {
None
}
);
(Some(method), None)
};
let (partial_static_ty, static_ty_block) = if ty_is_static {
let method = quote!(
fn partial_static_ty() -> ::std::option::Option<&'static str> {
Some(#ty)
}
);
let block = quote!(
impl #crate_root::derive::StaticType for #doc_ty { }
);
(Some(method), Some(block))
} else {
let method = quote!(
fn partial_static_ty() -> ::std::option::Option<&'static str> {
None
}
);
(Some(method), None)
};
let instance_methods = quote!(
fn index(&self) -> ::std::borrow::Cow<str> {
(#index).into()
}
fn ty(&self) -> ::std::borrow::Cow<str> {
(#ty).into()
}
fn partial_id(&self) -> ::std::option::Option<::std::borrow::Cow<str>> {
(#id).into()
}
#partial_static_index
#partial_static_ty
);
MetadataBlock {
instance_methods,
static_impls: quote!(
#static_index_block
#static_ty_block
),
}
}
let doc_ty = &item.ident;
let MetadataBlock {
instance_methods,
static_impls,
} = get_doc_ty_methods(crate_root, item, fields);
quote!(
impl #crate_root::derive::ObjectFieldType for #doc_ty {
type Mapping = #mapping;
}
impl #crate_root::derive::DocumentType for #doc_ty {
#instance_methods
}
#static_impls
)
}
fn get_props_impl_block(
crate_root: &Tokens,
props_ty: &syn::Ident,
fields: &[(syn::Ident, &syn::Field)],
) -> Tokens {
fn get_field_ser_stmts(
crate_root: &Tokens,
fields: &[(syn::Ident, &syn::Field)],
) -> Vec<Tokens> {
let fields: Vec<Tokens> = fields
.iter()
.cloned()
.map(|(name, field)| {
let lit = syn::Lit::Str(name.as_ref().to_string(), syn::StrStyle::Cooked);
let ty = &field.ty;
quote!(try!(#crate_root::derive::field_ser::<#ty, _, _, _>(state, #lit));)
})
.collect();
fields
}
let stmts = get_field_ser_stmts(crate_root, fields);
let stmts_len = stmts.len();
quote!(
impl #crate_root::derive::PropertiesMapping for #props_ty {
fn props_len() -> usize { #stmts_len }
fn serialize_props<S>(state: &mut S) -> ::std::result::Result<(), S::Error>
where S: #crate_root::derive::SerializeStruct {
#(#stmts)*
Ok(())
}
}
)
}
fn get_ser_field(field: &syn::Field) -> Option<(syn::Ident, &syn::Field)> {
let ctxt = serde_derive_internals::Ctxt::new();
let serde_field = serde_attr::Field::from_ast(&ctxt, 0, field);
match ctxt.check() {
Err(_) => return None,
_ => (),
};
if serde_field.skip_serializing() {
return None;
}
Some((
syn::Ident::from(serde_field.name().serialize_name().as_ref()),
field,
))
}
quick_error! {
#[derive(Debug)]
pub enum DeriveElasticTypeError {
InvalidInput {
display("deriving a document type is only valid for structs")
}
}
}
enum MethodFromStruct {
Literal(Tokens),
Expr(Tokens),
}
enum MethodFromField {
Field(Tokens),
Literal(Tokens, Tokens),
Expr(Tokens, Tokens),
}
fn get_method_from_struct(item: &syn::MacroInput, method: &str) -> Option<MethodFromStruct> {
let val = get_elastic_meta_items(&item.attrs);
if let Some(lit) = val
.iter()
.filter_map(|meta| expect_name_value(method, meta))
.next()
{
return Some(MethodFromStruct::Literal(quote!(#lit)));
}
if let Some(expr) = val
.iter()
.filter_map(|meta| expect_list(method, meta))
.flat_map(|attrs| attrs)
.filter_map(|meta| expect_name_value("expr", meta))
.next()
.and_then(|expr| get_tokens_from_lit(expr).ok())
{
return Some(MethodFromStruct::Expr(quote!(#expr)));
}
None
}
fn get_method_from_fields(
fields: &[(syn::Ident, &syn::Field)],
method: &str,
) -> Option<MethodFromField> {
for &(_, ref field) in fields {
let val = get_elastic_meta_items(&field.attrs);
let field = &field.ident;
if val.iter().any(|meta| expect_ident(method, meta)) {
return Some(MethodFromField::Field(quote!(#field)));
}
if let Some(lit) = val
.iter()
.filter_map(|meta| expect_name_value(method, meta))
.next()
{
return Some(MethodFromField::Literal(quote!(#field), quote!(#lit)));
}
if let Some(expr) = val
.iter()
.filter_map(|meta| expect_list(method, meta))
.flat_map(|attrs| attrs)
.filter_map(|meta| expect_name_value("expr", meta))
.next()
.and_then(|expr| get_tokens_from_lit(expr).ok())
{
return Some(MethodFromField::Expr(quote!(#field), quote!(#expr)));
}
}
None
}