dynamic-graphql-derive 0.10.2

Dynamic GraphQL schema macro
Documentation
use darling::FromAttributes;
use proc_macro2::Ident;
use proc_macro2::TokenStream;
use quote::ToTokens;
use quote::quote;
use syn::Generics;
use syn::Path;

use crate::args::common;
use crate::args::common::ArgImplementor;
use crate::args::common::FieldImplementor;
use crate::utils::attributes::Attributes;
use crate::utils::common::CommonArg;
use crate::utils::common::CommonField;
use crate::utils::common::CommonMethod;
use crate::utils::common::CommonObject;
use crate::utils::common::GetArgs;
use crate::utils::common::GetFields;
use crate::utils::crate_name::get_crate_name;
use crate::utils::deprecation::Deprecation;
use crate::utils::error::IntoTokenStream;
use crate::utils::impl_block::BaseFnArg;
use crate::utils::impl_block::BaseItemImpl;
use crate::utils::impl_block::BaseMethod;
use crate::utils::macros::*;
use crate::utils::rename_rule::RenameRule;
use crate::utils::type_utils::get_type_path;
use crate::utils::with_attributes::WithAttributes;
use crate::utils::with_context::MakeContext;
use crate::utils::with_context::WithContext;
use crate::utils::with_doc::WithDoc;
use crate::utils::with_index::WithIndex;

#[derive(FromAttributes, Debug, Clone)]
#[darling(attributes(graphql))]
pub struct ResolvedObjectFieldsArgAttrs {
    #[darling(default)]
    pub name: Option<String>,

    #[darling(default)]
    pub ctx: bool,

    /// Description for this argument exposed in the GraphQL schema. Takes
    #[darling(default)]
    pub desc: Option<String>,
}

impl Attributes for ResolvedObjectFieldsArgAttrs {
    const ATTRIBUTES: &'static [&'static str] = &["graphql"];
}

#[derive(Default, Debug, Clone)]
pub struct ResolvedObjectFieldsArgContext {
    pub rename_args: Option<RenameRule>,
}

from_fn_arg!(
    ResolvedObjectFieldsArg,
    WithAttributes<
        ResolvedObjectFieldsArgAttrs,
        WithIndex<WithContext<ResolvedObjectFieldsArgContext, BaseFnArg>>,
    >,
);

#[derive(FromAttributes, Debug, Clone)]
#[darling(attributes(graphql))]
pub struct ResolvedObjectFieldsMethodAttrs {
    #[darling(default)]
    pub skip: bool,

    #[darling(default)]
    pub name: Option<String>,

    #[darling(default)]
    pub rename_args: Option<RenameRule>,

    #[darling(default)]
    pub deprecation: Deprecation,
}

impl Attributes for ResolvedObjectFieldsMethodAttrs {
    const ATTRIBUTES: &'static [&'static str] = &["graphql"];
}

#[derive(Default, Debug, Clone)]
pub struct ResolvedObjectFieldsMethodContext {
    pub rename_args: Option<RenameRule>,
    pub rename_fields: Option<RenameRule>,
}

from_impl_item_method!(
    ResolvedObjectFieldsMethod,
    WithAttributes<
        WithDoc<ResolvedObjectFieldsMethodAttrs>,
        WithContext<ResolvedObjectFieldsMethodContext, BaseMethod<ResolvedObjectFieldsArg>>,
    >,
    inner = args,
);

impl MakeContext<ResolvedObjectFieldsArgContext> for ResolvedObjectFieldsMethod {
    fn make_context(&self) -> ResolvedObjectFieldsArgContext {
        ResolvedObjectFieldsArgContext {
            rename_args: self.attrs.rename_args.or(self.ctx.rename_args),
        }
    }
}

#[derive(FromAttributes, Debug, Clone)]
#[darling(attributes(graphql))]
pub struct ResolvedObjectFieldsAttrs {
    #[darling(default)]
    pub rename_fields: Option<RenameRule>,

    #[darling(default)]
    pub rename_args: Option<RenameRule>,
}

impl Attributes for ResolvedObjectFieldsAttrs {
    const ATTRIBUTES: &'static [&'static str] = &["graphql"];
}

from_item_impl!(
    ResolvedObjectFields,
    WithAttributes<
        WithDoc<ResolvedObjectFieldsAttrs>,
        BaseItemImpl<ResolvedObjectFieldsMethod, Generics>,
    >,
    ctx,
);

impl MakeContext<ResolvedObjectFieldsMethodContext> for ResolvedObjectFields {
    fn make_context(&self) -> ResolvedObjectFieldsMethodContext {
        ResolvedObjectFieldsMethodContext {
            rename_args: self.attrs.rename_args,
            rename_fields: self.attrs.rename_fields,
        }
    }
}

impl CommonObject for ResolvedObjectFields {
    fn get_name(&self) -> Option<&str> {
        unreachable!("ResolvedObjectFields does not have a name");
    }

    fn should_impl_type_name(&self) -> bool {
        false
    }

    fn get_ident(&self) -> &Ident {
        unreachable!("ResolvedObjectFields does not have an ident");
    }

    fn get_type(&self) -> darling::Result<Path> {
        get_type_path(&self.ty).cloned()
    }

    fn get_generics(&self) -> darling::Result<&Generics> {
        Ok(&self.generics)
    }

    fn get_doc(&self) -> darling::Result<Option<String>> {
        Ok(self.attrs.doc.clone())
    }
}

impl CommonField for ResolvedObjectFieldsMethod {
    fn get_name(&self) -> Option<&str> {
        self.attrs.name.as_deref()
    }

    fn get_ident(&self) -> darling::Result<&Ident> {
        Ok(&self.ident)
    }

    fn get_type(&self) -> darling::Result<&syn::Type> {
        self.output_type.as_ref().ok_or_else(|| {
            darling::Error::custom("Field must have return type").with_span(&self.ident)
        })
    }

    fn get_skip(&self) -> bool {
        self.attrs.skip
    }

    fn get_doc(&self) -> darling::Result<Option<String>> {
        Ok(self.attrs.doc.clone())
    }
    fn get_deprecation(&self) -> darling::Result<Deprecation> {
        Ok(self.attrs.deprecation.clone())
    }
    fn get_field_rename_rule(&self) -> Option<&RenameRule> {
        self.ctx.rename_fields.as_ref()
    }
    fn get_args_rename_rule(&self) -> Option<&RenameRule> {
        self.attrs
            .rename_args
            .as_ref()
            .or(self.ctx.rename_args.as_ref())
    }
}

impl CommonArg for ResolvedObjectFieldsArg {
    fn get_name(&self) -> Option<&str> {
        self.attrs.name.as_deref()
    }

    fn get_index(&self) -> usize {
        self.index
    }

    fn get_arg(&self) -> &BaseFnArg {
        self
    }

    fn get_arg_rename_rule(&self) -> Option<&RenameRule> {
        self.ctx.rename_args.as_ref()
    }

    fn is_marked_as_ctx(&self) -> bool {
        self.attrs.ctx
    }

    fn get_doc(&self) -> darling::Result<Option<String>> {
        Ok(self.attrs.desc.clone())
    }
}

impl CommonMethod for ResolvedObjectFieldsMethod {
    fn is_async(&self) -> bool {
        self.asyncness
    }
}

impl GetArgs<ResolvedObjectFieldsArg> for ResolvedObjectFieldsMethod {
    fn get_args(&self) -> darling::Result<&Vec<ResolvedObjectFieldsArg>> {
        Ok(&self.args)
    }
}

impl GetFields<ResolvedObjectFieldsMethod> for ResolvedObjectFields {
    fn get_fields(&self) -> darling::Result<&Vec<ResolvedObjectFieldsMethod>> {
        Ok(&self.methods)
    }
}

impl ArgImplementor for ResolvedObjectFieldsArg {
    fn get_self_arg_definition(&self) -> darling::Result<TokenStream> {
        let crate_name = get_crate_name();
        let arg_ident = common::get_arg_ident(self);

        Ok(quote! {
            let parent = ctx.parent_value.try_downcast_ref::<<Self as #crate_name::internal::ParentType>::Type>()?.into();
            let #arg_ident = parent;
        })
    }

    fn get_typed_arg_definition(&self) -> darling::Result<TokenStream> {
        common::get_typed_arg_definition(self)
    }

    fn get_self_arg_usage(&self) -> darling::Result<TokenStream> {
        common::get_self_arg_usage(self)
    }

    fn get_typed_arg_usage(&self) -> darling::Result<TokenStream> {
        common::get_typed_arg_usage(self)
    }
}

impl FieldImplementor for ResolvedObjectFieldsMethod {
    fn define_field(&self) -> darling::Result<TokenStream> {
        common::define_field(self)
    }
    fn get_execute_code(&self) -> darling::Result<TokenStream> {
        execute_code(self)
    }

    fn get_resolve_code(&self) -> darling::Result<TokenStream> {
        common::resolve_value_code()
    }

    fn get_field_argument_definition(&self) -> darling::Result<TokenStream> {
        common::get_argument_definitions(self.get_args()?)
    }

    fn get_field_description_code(&self) -> darling::Result<TokenStream> {
        common::field_description(self)
    }

    fn get_field_deprecation_code(&self) -> darling::Result<TokenStream> {
        common::field_deprecation_code(self)
    }

    fn get_field_usage_code(&self) -> darling::Result<TokenStream> {
        Ok(quote! {
            let object = object.field(field);
        })
    }
}

fn execute_code<F, A>(method: &F) -> darling::Result<TokenStream>
where
    F: CommonMethod + GetArgs<A>,
    A: CommonArg + ArgImplementor,
{
    let field_ident = method.get_ident()?;

    let args = common::get_args_usage(method)?;

    if method.is_async() {
        Ok(quote! {
            let value = Self::#field_ident(#args).await;
        })
    } else {
        Ok(quote! {
            let value = Self::#field_ident(#args);
        })
    }
}

fn impl_register(object: &ResolvedObjectFields) -> darling::Result<TokenStream> {
    let crate_name = get_crate_name();
    let ty = get_type_path(&object.ty)?;
    let register_nested_types = common::get_nested_type_register_code(object).into_token_stream();
    let define_object = common::impl_define_object();
    let define_fields = common::get_define_fields_code(object)?;
    let register_object_code = common::register_object_code();
    let register_fns = common::call_register_fns();
    let (impl_generics, _, where_clause) = object.get_generics()?.split_for_impl();

    Ok(quote! {
        impl #impl_generics #crate_name::internal::Register for #ty #where_clause {
            fn register(registry: #crate_name::internal::Registry) -> #crate_name::internal::Registry {

                #register_nested_types

                #define_object

                #define_fields

                #register_fns

                #register_object_code
            }
        }
    })
}

impl ToTokens for ResolvedObjectFields {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let impl_register = impl_register(self).into_token_stream();
        tokens.extend(quote! {
            #impl_register
        });
    }
}