juniper-eager-loading-code-gen 0.5.1

Eliminate N+1 query bugs when using Juniper
Documentation
use bae::FromAttributes;
use heck::SnakeCase;
use proc_macro2::{Span, TokenStream};
use proc_macro_error::*;
use quote::{format_ident, quote};
use std::ops::{Deref, DerefMut};
use syn::{self, Ident};

macro_rules! token_stream_getter {
    ( $name:ident ) => {
        pub fn $name(&self) -> TokenStream {
            let value = &self.$name;
            quote! { #value }
        }
    }
}

#[derive(Debug, FromAttributes)]
pub struct EagerLoading {
    model: Option<syn::Type>,
    id: Option<syn::Type>,
    context: syn::Type,
    error: syn::Type,
    root_model_field: Option<syn::Ident>,
    print: Option<()>,
    primary_key_field: Option<syn::Ident>,
}

impl EagerLoading {
    token_stream_getter!(context);
    token_stream_getter!(error);

    pub fn model(&self, struct_name: &syn::Ident) -> TokenStream {
        if let Some(inner) = &self.model {
            quote! { #inner }
        } else {
            quote! { models::#struct_name }
        }
    }

    pub fn id(&self) -> TokenStream {
        if let Some(inner) = &self.id {
            quote! { #inner }
        } else {
            quote! { i32 }
        }
    }

    pub fn root_model_field(&self, struct_name: &syn::Ident) -> TokenStream {
        if let Some(inner) = &self.root_model_field {
            quote! { #inner }
        } else {
            let struct_name = struct_name.to_string().to_snake_case();
            let struct_name = Ident::new(&struct_name, Span::call_site());
            quote! { #struct_name }
        }
    }

    pub fn print(&self) -> bool {
        self.print.is_some()
    }

    pub fn primary_key_field(&self) -> syn::Ident {
        if let Some(id) = &self.primary_key_field {
            id.clone()
        } else {
            format_ident!("id")
        }
    }
}

#[derive(Debug, Clone, FromAttributes)]
pub struct HasOne {
    print: Option<()>,
    skip: Option<()>,
    field_arguments: Option<syn::TypePath>,
    foreign_key_field: Option<syn::Ident>,
    root_model_field: Option<syn::Ident>,
    graphql_field: Option<syn::Ident>,
    default: Option<()>,
    child_primary_key_field: Option<syn::Ident>,
}

impl HasOne {
    pub fn child_primary_key_field(&self) -> syn::Ident {
        let child_primary_key_field = &self.child_primary_key_field;

        if let Some(id) = child_primary_key_field {
            id.clone()
        } else {
            format_ident!("id")
        }
    }
}

#[derive(Debug, Clone, FromAttributes)]
pub struct OptionHasOne {
    print: Option<()>,
    skip: Option<()>,
    foreign_key_field: Option<syn::Ident>,
    root_model_field: Option<syn::Ident>,
    graphql_field: Option<syn::Ident>,
    default: Option<()>,
    field_arguments: Option<syn::TypePath>,
    child_primary_key_field: Option<syn::Ident>,
}

impl OptionHasOne {
    pub fn child_primary_key_field(&self) -> syn::Ident {
        let child_primary_key_field = &self.child_primary_key_field;

        if let Some(id) = child_primary_key_field {
            id.clone()
        } else {
            format_ident!("id")
        }
    }
}

#[derive(Debug, Clone, FromAttributes)]
pub struct HasMany {
    print: Option<()>,
    skip: Option<()>,
    field_arguments: Option<syn::TypePath>,
    foreign_key_field: Option<syn::Ident>,
    pub foreign_key_optional: Option<()>,
    root_model_field: Option<syn::Ident>,
    predicate_method: Option<syn::Ident>,
    graphql_field: Option<syn::Ident>,
}

impl HasMany {
    pub fn predicate_method(&self) -> &Option<syn::Ident> {
        &self.predicate_method
    }
}

#[derive(Debug, Clone, FromAttributes)]
pub struct HasManyThrough {
    print: Option<()>,
    skip: Option<()>,
    field_arguments: Option<syn::TypePath>,
    model_field: Option<syn::Type>,
    join_model: Option<syn::TypePath>,
    foreign_key_field: Option<syn::Ident>,
    predicate_method: Option<syn::Ident>,
    graphql_field: Option<syn::Ident>,
    child_primary_key_field_on_join_model: Option<syn::Ident>,
    child_primary_key_field: Option<syn::Ident>,
}

impl HasManyThrough {
    pub fn join_model(&self, span: Span) -> syn::Type {
        self.join_model
            .as_ref()
            .cloned()
            .map(syn::Type::Path)
            .unwrap_or_else(|| abort!(span, "`#[has_many_through]` missing `join_model`"))
    }

    pub fn model_field(&self, inner_type: &syn::Type) -> TokenStream {
        if let Some(inner) = &self.model_field {
            quote! { #inner }
        } else {
            let inner_type = type_to_string(inner_type).to_snake_case();
            let inner_type = Ident::new(&inner_type, Span::call_site());
            quote! { #inner_type }
        }
    }

    pub fn child_primary_key_field_on_join_model(&self, inner_type: &syn::Type) -> Ident {
        if let Some(id) = &self.child_primary_key_field_on_join_model {
            id.clone()
        } else {
            Ident::new(
                &format!("{}_id", self.model_field(inner_type)),
                Span::call_site(),
            )
        }
    }

    pub fn predicate_method(&self) -> &Option<syn::Ident> {
        &self.predicate_method
    }

    pub fn child_primary_key_field(&self) -> syn::Ident {
        if let Some(id) = &self.child_primary_key_field {
            id.clone()
        } else {
            format_ident!("id")
        }
    }
}

#[derive(Debug, Clone)]
pub enum FieldArgs {
    HasOne(Spanned<HasOne>),
    OptionHasOne(Spanned<OptionHasOne>),
    HasMany(Spanned<HasMany>),
    HasManyThrough(Spanned<Box<HasManyThrough>>),
}

impl FieldArgs {
    pub fn skip(&self) -> bool {
        match self {
            FieldArgs::HasOne(inner) => inner.skip.is_some(),
            FieldArgs::OptionHasOne(inner) => inner.skip.is_some(),
            FieldArgs::HasMany(inner) => inner.skip.is_some(),
            FieldArgs::HasManyThrough(inner) => inner.skip.is_some(),
        }
    }

    pub fn print(&self) -> bool {
        match self {
            FieldArgs::HasOne(inner) => inner.print.is_some(),
            FieldArgs::OptionHasOne(inner) => inner.print.is_some(),
            FieldArgs::HasMany(inner) => inner.print.is_some(),
            FieldArgs::HasManyThrough(inner) => inner.print.is_some(),
        }
    }

    pub fn graphql_field(&self) -> &Option<syn::Ident> {
        match self {
            FieldArgs::HasOne(inner) => &inner.graphql_field,
            FieldArgs::OptionHasOne(inner) => &inner.graphql_field,
            FieldArgs::HasMany(inner) => &inner.graphql_field,
            FieldArgs::HasManyThrough(inner) => &inner.graphql_field,
        }
    }

    pub fn field_arguments(&self) -> syn::Type {
        let field_arguments = match self {
            FieldArgs::HasOne(inner) => &inner.field_arguments,
            FieldArgs::OptionHasOne(inner) => &inner.field_arguments,
            FieldArgs::HasMany(inner) => &inner.field_arguments,
            FieldArgs::HasManyThrough(inner) => &inner.field_arguments,
        };

        if let Some(field_arguments) = field_arguments {
            syn::parse2(quote! { #field_arguments<'a> }).unwrap()
        } else {
            syn::parse_str("()").unwrap()
        }
    }

    pub fn foreign_key_field(&self, field_name: &Ident) -> TokenStream {
        let foreign_key_field = match self {
            FieldArgs::HasOne(inner) => &inner.foreign_key_field,
            FieldArgs::OptionHasOne(inner) => &inner.foreign_key_field,
            FieldArgs::HasMany(inner) => &inner.foreign_key_field,
            FieldArgs::HasManyThrough(inner) => &inner.foreign_key_field,
        };

        if let Some(inner) = foreign_key_field {
            quote! { #inner }
        } else {
            let field_name = field_name.to_string().to_snake_case();
            let field_name = format_ident!("{}_id", field_name);
            quote! { #field_name }
        }
    }
}

pub trait RootModelField {
    fn get_root_model_field(&self) -> &Option<Ident>;

    fn root_model_field(&self, field_name: &Ident) -> TokenStream {
        if let Some(inner) = self.get_root_model_field() {
            quote! { #inner }
        } else {
            let field_name = field_name.to_string().to_snake_case();
            let field_name = Ident::new(&field_name, Span::call_site());
            quote! { #field_name }
        }
    }
}

impl RootModelField for HasOne {
    fn get_root_model_field(&self) -> &Option<Ident> {
        &self.root_model_field
    }
}

impl RootModelField for OptionHasOne {
    fn get_root_model_field(&self) -> &Option<Ident> {
        &self.root_model_field
    }
}

impl RootModelField for HasMany {
    fn get_root_model_field(&self) -> &Option<Ident> {
        &self.root_model_field
    }
}

fn type_to_string(ty: &syn::Type) -> String {
    use quote::ToTokens;
    let mut tokenized = quote! {};
    ty.to_tokens(&mut tokenized);
    tokenized.to_string()
}

#[derive(Debug, Clone)]
pub struct Spanned<T>(Span, T);

impl<T> Spanned<T> {
    pub fn new(span: Span, t: T) -> Self {
        Self(span, t)
    }

    pub fn span(&self) -> Span {
        self.0
    }
}

impl<T> Deref for Spanned<T> {
    type Target = T;

    fn deref(&self) -> &T {
        &self.1
    }
}

impl<T> DerefMut for Spanned<T> {
    fn deref_mut(&mut self) -> &mut T {
        &mut self.1
    }
}