mod codegen;
mod enums;
mod error;
mod rename;
mod structs;
use darling::{FromDeriveInput, FromField, FromMeta, FromVariant, ast::Data};
use proc_macro::TokenStream;
use syn::{DeriveInput, Generics, Ident, parse_macro_input};
use codegen::gen_mapping_impl;
use enums::{classify_enum, gen_enum_impl};
use error::GeneratorError;
use structs::gen_struct;
use crate::rename::{RenameCasing, apply_rename_casing};
const TYPE_FIELD: &str = "type";
const PROPERTIES_FIELD: &str = "properties";
const FIELDS_FIELD: &str = "fields";
const ANALYZER_FIELD: &str = "analyzer";
const INDEX_FIELD: &str = "index";
const IGNORE_ABOVE_FIELD: &str = "ignore_above";
const OBJECT_TYPE: &str = "object";
const KEYWORD_TYPE: &str = "keyword";
#[proc_macro_derive(Document, attributes(document))]
pub fn derive_response(input: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as DeriveInput);
match generate(args) {
Ok(stream) => stream,
Err(err) => err.write_errors().into(),
}
}
fn generate(input: DeriveInput) -> Result<TokenStream, GeneratorError> {
let args = DocumentArgs::from_derive_input(&input)?;
let ident = &args.ident;
let generics = &args.generics;
let output = match &args.data {
Data::Enum(variants) => {
let representation = classify_enum(&args, variants)?;
gen_enum_impl(ident, generics, representation)?
}
Data::Struct(fields) => {
let (properties, iterable) = gen_struct(fields.fields.as_slice(), args.rename_all)?;
gen_mapping_impl(ident, generics, "object", properties, iterable)
}
};
Ok(output.into())
}
#[derive(Debug, FromDeriveInput)]
#[darling(attributes(document, serde), allow_unknown_fields)]
struct DocumentArgs {
pub(crate) ident: Ident,
pub(crate) generics: Generics,
pub(crate) data: Data<DocumentVariant, DocumentField>,
pub(crate) rename_all: Option<RenameCasing>,
pub(crate) tag: Option<String>,
pub(crate) content: Option<String>,
}
#[derive(Debug, FromVariant)]
#[darling(attributes(document, serde), allow_unknown_fields)]
struct DocumentVariant {
pub(crate) ident: Ident,
pub(crate) fields: darling::ast::Fields<DocumentField>,
pub(crate) rename: Option<String>,
}
impl DocumentVariant {
pub(crate) fn resolve_name(&self, rename_all: Option<RenameCasing>) -> String {
self.rename.clone().unwrap_or_else(|| {
let ident_str = self.ident.to_string();
apply_rename_casing(&ident_str, rename_all)
})
}
}
#[derive(Debug, FromField)]
#[darling(attributes(document, serde), allow_unknown_fields)]
struct DocumentField {
pub(crate) ident: Option<Ident>,
pub(crate) ty: syn::Type,
pub(crate) analyzer: Option<String>,
pub(crate) keyword: Option<DocumentFieldKeyword>,
#[darling(default)]
pub(crate) flatten: bool,
pub(crate) rename: Option<String>,
}
#[derive(Debug, FromMeta)]
struct DocumentFieldKeyword {
pub(crate) index: Option<bool>,
pub(crate) ignore_above: Option<u32>,
}
struct MappingProperty {
pub(crate) name: String,
pub(crate) mapping: proc_macro2::TokenStream,
}
impl DocumentField {
pub(crate) fn resolve_name(&self, rename_all: Option<RenameCasing>) -> String {
self.rename.clone().unwrap_or_else(|| {
let ident_str = self
.ident
.as_ref()
.map(|i| i.to_string())
.unwrap_or_default();
apply_rename_casing(&ident_str, rename_all)
})
}
pub(crate) fn gen_mapping(&self, rename_all: Option<RenameCasing>) -> MappingProperty {
let ty = &self.ty;
let name = self.resolve_name(rename_all);
let analyzer = self.analyzer.as_ref().map(|analyzer| {
quote::quote! {
m.insert(#ANALYZER_FIELD.into(), #analyzer.into());
}
});
let keyword = self.keyword.as_ref().map(|keyword| {
let ignore_above = keyword.ignore_above.map(|ignore_above| {
quote::quote! {
field_m.insert(#IGNORE_ABOVE_FIELD.into(), #ignore_above.into());
}
});
let index = keyword.index.map(|index| {
quote::quote! {
field_m.insert(#INDEX_FIELD.into(), #index.into());
}
});
quote::quote! {
m.insert(#FIELDS_FIELD.into(), {
let mut km = elastic_mapping::serde_json::Map::default();
km.insert(#KEYWORD_TYPE.into(), {
let mut field_m = elastic_mapping::serde_json::Map::default();
field_m.insert(#TYPE_FIELD.into(), #KEYWORD_TYPE.into());
#ignore_above
#index
field_m.into()
});
km.into()
});
}
});
let mapping = quote::quote! {
{
let mut m = <#ty as elastic_mapping::MappingType>::mapping();
#analyzer
#keyword
m.into()
}
};
MappingProperty { name, mapping }
}
}