idkthings_core_macros 0.1.0

core macros for idkthings
Documentation
use proc_macro2::TokenStream;

mod create_graphql_impl;
use create_graphql_impl::create_graphql_impl;
use proc_macro2::Ident;
use proc_macro_crate::crate_name;
use std::iter::Map;
use syn::punctuated::Iter;
use syn::{Field, Fields, FieldsNamed, Item, ItemStruct, Type};

pub fn process_entity(item: Item) -> TokenStream {
    let struct_item = match item {
        Item::Struct(item) => item,
        _ => panic!("entity only accepts Structs"),
    };

    let name = struct_item.ident.clone();
    let fields = struct_item.fields.clone();

    let named_fields = match &fields {
        Fields::Named(named) => named,
        _ => panic!("Expected named fields"),
    };

    let graphql_impl = create_graphql_impl(&name, &fields);

    let fn_impls = create_field_name_getters(struct_item.ident.clone(), &named_fields);

    let struct_name = struct_item.ident.clone();
    let field_mapped_struct = map_struct_fields(&struct_item);

    let mongodb_result = crate_name("mongodb");
    let mongodb_name = match mongodb_result {
        Ok(_) => quote!(mongodb),
        Err(_) => quote!(idkthings_core::mongodb),
    };

    let output = quote!(
        #[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
        #field_mapped_struct

        #graphql_impl

        #(#fn_impls)*

        impl From<#struct_name> for bson::Bson {
            fn from(doc: #struct_name) -> Self {
                #mongodb_name::bson::to_bson(&doc).unwrap()
            }
        }
    );

    output
}

fn create_field_name_getters<'a>(
    struct_name: Ident,
    fields_named: &'a FieldsNamed,
) -> Map<Iter<Field>, impl FnMut(&'a Field) -> TokenStream + 'a> {
    fields_named.named.iter().map(move |field: &Field| {
        let field_name = field.ident.as_ref().unwrap();
        let fn_name = syn::Ident::new(
            &format!("get_field_name_{}", &field_name),
            field_name.span(),
        );
        let return_value = format!("{}", &field_name);
        quote!(
            impl #struct_name {
                pub fn #fn_name() -> String {
                    #return_value.to_string()
                }
            }
        )
    })
}

fn map_struct_fields(struct_item: &ItemStruct) -> TokenStream {
    let mapped_fields = struct_item.fields.iter().map(|field| match &field.ty {
        Type::Path(type_path) => {
            if type_path.path.segments.last().unwrap().ident.to_string() == "Uuid" {
                quote!(
                    #[serde(with = "bson::serde_helpers::uuid_1_as_binary")]
                    #field
                )
            } else {
                quote!(
                    #field
                )
            }
        }
        _ => quote!(
            #field
        ),
    });
    let struct_name = struct_item.ident.clone();
    let vis = struct_item.vis.clone();

    quote!(
        #vis struct #struct_name {
            #(#mapped_fields),*
        }
    )
}