elastic-mapping-macro 0.1.0

Procedural macros for elastic-mapping - generate Elasticsearch mappings from Rust types
Documentation
use proc_macro2::TokenStream;
use syn::{Generics, Ident};

use crate::{MappingProperty, OBJECT_TYPE, PROPERTIES_FIELD, TYPE_FIELD};

/// Properties that can be iterated (from flattened or newtype fields)
pub(crate) struct IterableProperties {
    pub(crate) source: TokenStream,
}

/// Generates the field_type function implementation
pub(crate) fn gen_field_type_fn(ty: &str) -> TokenStream {
    quote::quote! {
        fn field_type() -> &'static str {
            #ty
        }
    }
}

/// Generates code to insert properties into a map
pub(crate) fn gen_properties_insertions(properties: Vec<MappingProperty>) -> Vec<TokenStream> {
    properties
        .into_iter()
        .map(|prop| {
            let name = prop.name;
            let mapping = prop.mapping;
            quote::quote! {
                acc.insert(#name.into(), #mapping);
            }
        })
        .collect()
}

/// Generates code to iterate over iterable properties
pub(crate) fn gen_iterable_insertions(iterable: Vec<IterableProperties>) -> Vec<TokenStream> {
    iterable
        .into_iter()
        .map(|it| {
            let source = it.source;
            quote::quote! {
                for (key, field) in #source {
                    acc.insert(key, field);
                }
            }
        })
        .collect()
}

/// Generates the full MappingType + MappingObject implementation
pub(crate) fn gen_mapping_impl(
    ident: &Ident,
    generics: &Generics,
    field_type: &str,
    properties: Vec<MappingProperty>,
    iterable: Vec<IterableProperties>,
) -> TokenStream {
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    let field_type_fn = gen_field_type_fn(field_type);
    let property_insertions = gen_properties_insertions(properties);
    let iterable_insertions = gen_iterable_insertions(iterable);

    quote::quote! {
        impl #impl_generics elastic_mapping::MappingType for #ident #ty_generics #where_clause {
            #field_type_fn

            fn fields() -> elastic_mapping::serde_json::Map<String, elastic_mapping::serde_json::Value> {
                let mut acc = elastic_mapping::serde_json::Map::new();
                #(#property_insertions)*
                #(#iterable_insertions)*
                acc
            }
        }

        impl #impl_generics elastic_mapping::MappingObject for #ident #ty_generics #where_clause { }
    }
}

/// Generates a simple MappingType implementation (for unit enums)
pub(crate) fn gen_simple_mapping_impl(
    ident: &Ident,
    generics: &Generics,
    field_type: &str,
) -> TokenStream {
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
    let field_type_fn = gen_field_type_fn(field_type);

    quote::quote! {
        impl #impl_generics elastic_mapping::MappingType for #ident #ty_generics #where_clause {
            #field_type_fn
        }
    }
}

/// Generates an object mapping with properties
pub(crate) fn gen_object_mapping(properties: Vec<TokenStream>) -> TokenStream {
    quote::quote! {{
        let mut m = elastic_mapping::serde_json::Map::default();
        m.insert(#TYPE_FIELD.into(), #OBJECT_TYPE.into());
        m.insert(#PROPERTIES_FIELD.into(), {
            let mut prop = elastic_mapping::serde_json::Map::default();
            #(#properties;)*
            prop.into()
        });
        m.into()
    }}
}

/// Generates a string mapping (for tag/discriminator fields)
pub(crate) fn gen_string_mapping() -> TokenStream {
    quote::quote! {
        <::std::string::String as elastic_mapping::MappingType>::mapping().into()
    }
}