castle_macro 0.20.2

Castle Macro, macros to make your life easier
Documentation

mod get_from_conversion;
use std::collections::HashSet;

use crate::types::get_from_conversion::get_from_conversion;
use quote::quote_spanned;
use syn::{
    spanned::Spanned, FnArg, ImplItem, PatType, ReturnType, Item
};
use crate::{Unzip2, directives::AppliedDirective};

pub fn derive_type(item_impl: Item) -> proc_macro2::TokenStream {
    let mut types_used = HashSet::new();
    let (self_name, field_definitions, items): (syn::Type, Vec<syn::Expr>, Vec<syn::Item>) = match item_impl {
        Item::Impl(item_impl) => get_item_impl_conversion(item_impl, &mut types_used),
        Item::Struct(item_struct) => get_from_conversion(item_struct, &mut types_used),
        _ => panic!("Only impls and structs are supported"),
    };

    let initializations = types_used.iter()
        .map(|ty| quote_spanned!(ty.span() => <#ty as ::castle_api::types::SchemaItem>::initialize_item(schema)))
        .collect::<Vec<_>>();

    quote_spanned!{ self_name.span() =>
        impl ::castle_api::types::HasKind for #self_name{
            fn kind() -> ::castle_api::types::Kind {
                ::castle_api::types::Kind {
                    ident: stringify!(#self_name).into(),
                    generics: vec![]
                }
            }
        }

        impl ::castle_api::types::SchemaItem for #self_name {
            fn initialize_item(schema: &mut ::castle_api::types::SchemaDefinition) {
                if !schema.kind_is_registered(&stringify!(#self_name)) {
                    let type_def = ::castle_api::types::TypeDefinition {
                        ident: stringify!(#self_name).into(),
                        fields: [
                            #(
                                #field_definitions,
                            )*
                        ].into(),
                        directives: [].into(),
                    };
                    schema.register_type(type_def);
                    #(
                        #initializations;
                    )*
                }
            }
        }

        #(
            #items
        )*
    }.into()

}

fn get_item_impl_conversion(mut item_impl: syn::ItemImpl, types_used: &mut HashSet<syn::Type>) -> (syn::Type, Vec<syn::Expr>, Vec<syn::Item>) {
    let self_name = item_impl.self_ty.clone();
    let (
        matched_fns,
        field_definitions,
    ) = item_impl.items.iter_mut().map(|impl_item| match impl_item {
        ImplItem::Method(method) => {
            let fn_name = &method.sig.ident;
            let fn_return_type = match &method.sig.output {
                ReturnType::Type(_, ty) => *ty.clone(),
                ReturnType::Default => syn::parse_quote_spanned! { fn_name.span() => () }
            };
            let await_suffix = match method.sig.asyncness {
                Some(_) => quote_spanned! { fn_name.span() => .await },
                None => quote_spanned!(fn_name.span() => ),
            };

            let directives: String = method.attrs.drain_filter(|attr| attr.path.is_ident("directives"))
                .map(|attr| {
                    let tokens = attr.tokens.into();
                    syn::parse::<AppliedDirective>(tokens).unwrap().tokens.to_string()
                }).collect::<Vec<String>>().join(" ");

            types_used.insert(fn_return_type.clone());

            let (input_definitions, input_conversion) = method.sig.inputs
                .iter_mut()
                .skip(2)
                .filter_map(|arg| match arg {
                    FnArg::Typed(PatType {
                        ty,
                        pat,
                        ..
                    }) => {
                        types_used.insert(*ty.clone());
                        Some((
                            quote_spanned!(ty.span()=>{
                                (stringify!(#pat).into(), ::castle_api::types::InputDefinition {
                                    ident: stringify!(#pat).into(),
                                    input_kind: <#ty as ::castle_api::types::HasKind>::kind(),
                                    default: ::core::option::Option::None,
                                    directives: vec![],
                                })
                            }),
                            quote_spanned!(ty.span() => field.inputs.get(stringify!(#pat)).unwrap().try_into()?),
                        ))
                    }
                    _ => panic!("unexpected argument type"),
                })
                .into_iter()
                .unzip_n::<Vec<_>, Vec<_>>();

            (
                quote_spanned!(fn_name.span() => stringify!(#fn_name) => ::castle_api::types::ConvertFrom::from(self.#fn_name(ctx #( , #input_conversion )*)#await_suffix)),
                syn::parse_quote_spanned!(fn_name.span() =>
                    (stringify!(#fn_name).into(), ::castle_api::types::FieldDefinition {
                        ident: stringify!(#fn_name).into(),
                        input_definitions: [#( #input_definitions, )*].into(),
                        return_kind: <#fn_return_type as ::castle_api::types::HasKind>::kind(),
                        directives: castle_api::parse_directives_from_str(#directives),
                    })),
            )
        }
        _ => panic!("Only methods are supported"),
    }).unzip_n::<Vec<_>, Vec<_>>();


    (
        *self_name.clone(),
        field_definitions,
        [
            syn::Item::Impl(item_impl),
            syn::parse_quote_spanned!{ self_name.span() =>
                #[castle_api::async_trait]
                impl ::castle_api::types::ResolvesFields for #self_name {
                    async fn resolve(&self, field: &::castle_api::types::Field, ctx: &::castle_api::types::State) -> ::core::result::Result<::castle_api::types::Value, ::castle_api::Error> {
                        match &*field.ident {
                            #(
                                #matched_fns,
                            )*
                            _ => unreachable!("Should not reachable if property validated")
                        }
                    }
                }
            }
        ].into()
    )
}