cido-macros 0.2.0

Macros for generating code that enables easier interfacing with cido
Documentation
use crate::parse::deps::*;
pub use crate::parse::{
  CachePolicy, Conversion, DerivedField, StructType, TransformerField, TransformerStruct,
};
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use std::collections::HashSet;
use syn::{Ident, Type};

use crate::codegen::orderby::OrderByVariant;

type Result = syn::Result<TokenStream>;

mod db_impl;
mod from_impl;
mod gen_structs;
mod graphql_impl;

fn dyn_error() -> TokenStream {
  quote! {
    ::std::boxed::Box<dyn ::std::error::Error + ::core::marker::Send + ::core::marker::Sync + 'static>
  }
}

impl crate::codegen::CodegenWrapper<TransformerField> {
  pub fn self_type(&self) -> Type {
    if self.entity_or_event.is_some() {
      let field_ty = &self.field.ty;
      syn::parse_quote! { <#field_ty as ::cido::__internal::ToIdentifiable>::Identifiable }
    } else {
      self.field.ty.clone()
    }
  }

  pub fn db_type(&self) -> Type {
    if self.entity_or_event.is_some() {
      let ty = &self.field.ty;
      syn::parse_quote! { <#ty as ::cido::__internal::ToIdentifiable>::Identifiable }
    } else {
      self.db_config.ty.as_ref().clone()
    }
  }

  pub fn graphql_input_type(&self) -> Type {
    self
      .graphql_config
      .input_type
      .as_ref()
      .cloned()
      .unwrap_or_else(|| self.db_type())
  }

  pub fn graphql_input_name(&self) -> &Ident {
    &self.graphql_config.name
  }

  pub fn graphql_output_name(&self) -> &Ident {
    &self.graphql_config.name
  }

  pub fn graphql_output_type(&self) -> Type {
    self.self_type()
  }

  pub fn graphql_comparison_sdl_name_string(&self) -> String {
    self.graphql_config.name.to_string()
  }

  pub fn graphql_sdl_output_name(&self) -> String {
    self.graphql_config.name.to_string()
  }

  pub fn graphql_sdl_output_type(&self) -> Type {
    self
      .graphql_config
      .output_type
      .as_ref()
      .cloned()
      .unwrap_or_else(|| self.graphql_output_type())
  }

  pub fn graphql_order_by_name(&self) -> String {
    self.graphql_sdl_output_name()
  }
}

impl crate::codegen::CodegenWrapper<TransformerStruct> {
  pub fn is_entity(&self) -> bool {
    self.struct_type.is_entity()
  }

  pub fn is_event(&self) -> bool {
    self.struct_type.is_event()
  }

  pub fn id_field(&self) -> &TransformerField {
    &self.fields[self.id_offset]
  }

  pub fn struct_name(&self) -> &Ident {
    &self.name
  }

  pub fn sql_table_name(&self) -> &str {
    &self.struct_opts.sql_name
  }

  pub fn db_name(&self) -> &Ident {
    &self.struct_opts.db_opts.name
  }

  pub fn graphql_output_name(&self) -> &Ident {
    &self.struct_opts.graphql_opts.name
  }

  pub fn graphql_input_name(&self) -> Ident {
    format_ident!("{}Input", self.struct_opts.graphql_opts.name)
  }

  pub fn default_graphql_name_from_ident(name: &Ident) -> Ident {
    format_ident!("{}Graphql", name)
  }

  pub fn id_type(&self) -> &Type {
    &self.id_field().field.ty
  }

  pub fn codegen(&self) -> Result {
    let mut token_stream = TokenStream::new();
    self.gen_structs(&mut token_stream);
    self.gen_orderby(&mut token_stream)?;
    self.gen_from_impls(&mut token_stream);
    self.gen_trait_impls(&mut token_stream)?;
    let struct_type = match self.struct_type {
      StructType::Entity => "entity",
      StructType::Event => "event",
    };
    crate::parse::embed_generated_code(
      self.struct_opts.embed_generated_code,
      self.struct_name(),
      token_stream,
      struct_type,
    )
  }

  fn gen_orderby(&self, token_stream: &mut TokenStream) -> syn::Result<()> {
    let mut variants = vec![];
    for f in &self.fields {
      let graphql_orderby_field_name = f.graphql_order_by_name();
      let variant = OrderByVariant {
        name: syn::parse_str(&f.name.to_string().to_pascal_case())
          .expect("Valid ident is still valid"),
        sql_name: f.sql_config.name.clone(),
        graphql_name: format_ident!("{}", graphql_orderby_field_name),
        graphql_type: f.graphql_config.comparison.clone(),
        attrs: Default::default(),
        aliases: f.alias.iter().cloned().collect(),
        default: f.id,
        id: f.id,
        hidden: f.graphql_config.hidden,
        entity_event: f.entity_or_event.is_some(),
        is_block_number: false,
      };
      variants.push(variant);
    }
    variants.push(OrderByVariant {
      name: format_ident!("__BlockNumber"),
      sql_name: crate::parse::SqlSafeName::new(if self.is_entity() {
        db_impl::BLOCK_RANGE_COL
      } else {
        db_impl::BLOCK_NUMBER_COL
      }),
      graphql_name: format_ident!("__BlockNumber"),
      graphql_type: None,
      attrs: Default::default(),
      aliases: HashSet::new(),
      default: false,
      id: false,
      hidden: false,
      entity_event: false,
      is_block_number: true,
    });
    crate::codegen::CodegenWrapper(crate::codegen::orderby::OrderBy {
      // TODO: Add a trait to refer to this type so that we don't depend on it being exactly some form.
      name: format_ident!("{}OrderBy", self.struct_name()),
      variants,
      attrs: Default::default(),
      vis: self.vis.clone(),
      embed_generated_code: self.struct_opts.embed_generated_code,
    })
    .generate_order_by()?
    .to_tokens(token_stream);
    Ok(())
  }

  fn gen_trait_impls(&self, token_stream: &mut TokenStream) -> syn::Result<()> {
    self.gen_transformer_trait()?.to_tokens(token_stream);
    self.gen_cache_policy_trait().to_tokens(token_stream);
    self.impl_persistent()?.to_tokens(token_stream);
    self.impl_graphql()?.to_tokens(token_stream);
    Ok(())
  }

  fn gen_transformer_trait(&self) -> Result {
    let id = self.id_field();
    let self_name = self.struct_name();
    let id_type = self.id_type();
    // let id_borrow_type = self.id_borrow_type();
    // let persistent_type = &self.struct_opts.db_opts.name;
    let graphql_output_type = self.graphql_output_name();
    let graphql_input_type = self.graphql_input_name();
    let cidomap = &self.struct_opts.cidomap;
    let id_name = &id.name;
    // let try_from_persistent_id = id.db_config.db_to_field.conversion(&format_ident!("id"));

    let transform_type = match self.struct_type {
      StructType::Entity => quote! {::cido::__internal::RecordType::Entity},
      StructType::Event => quote! {::cido::__internal::RecordType::Event},
    };

    let error_ty = dyn_error();
    let self_name_str = self_name.to_string();

    Ok(quote! {
      impl ::cido::__internal::Identifiable for #self_name {
        type Id = #id_type;
        fn id(&self) -> &Self::Id {
          &self.#id_name
        }
      }

      impl ::cido::__internal::Transformer for #self_name {
        type Cidomap = #cidomap;
        type GraphqlInput = #graphql_input_type;
        type GraphqlOutput = #graphql_output_type;

        const TRACKING_LEVEL: ::cido::__internal::TrackingLevel = ::cido::__internal::TrackingLevel::Block;
        const TRANSFORM_TYPE: ::cido::__internal::RecordType = #transform_type;

        fn table_descriptor() -> &'static ::cido::__internal::TableDescriptor::<Self> {
          Self::__table_descriptor_inner()
        }

        fn bind_array<'q>(
          iter: impl Iterator<Item = &'q Self> + Clone + Send + 'q,
          query: &mut ::cido::__internal::sqlx::query_builder::Separated<'_, 'q, ::cido::__internal::sqlx::Postgres, &'static str>,
        ) {
          Self::__bind_array_inner(iter, query)
        }

        fn name() -> &'static str {
          #self_name_str
        }
      }
    })
  }

  fn gen_cache_policy_trait(&self) -> TokenStream {
    let struct_name = self.struct_name();
    let policy_impl = match self.struct_opts.cache_policy {
      CachePolicy::Always => quote! {
        impl ::cido::__internal::CachePolicy for #struct_name {
          type Transformer = #struct_name;
          type CacheId = <Self::Transformer as ::cido::__internal::Identifiable>::Id;
          type CacheLoader = ();
          const QUERY_DATABASE: bool = false;
          const QUERY_DATABASE_IF_NOT_FULL: bool = false;
          const LOAD_DATABASE: bool = true;
          const LOAD_MOST_RECENT_FIRST: bool = false;
          const SIZE: usize = usize::MAX;
          const MAX_SIZE: usize = usize::MAX;
        }
      },
      CachePolicy::Lru { size, max_size } => {
        quote! {
          impl ::cido::__internal::CachePolicy for #struct_name {
            type Transformer = #struct_name;
            type CacheId = <Self::Transformer as ::cido::__internal::Identifiable>::Id;
            type CacheLoader = usize;
            const QUERY_DATABASE: bool = true;
            const QUERY_DATABASE_IF_NOT_FULL: bool = false;
            const LOAD_DATABASE: bool = true;
            const LOAD_MOST_RECENT_FIRST: bool = true;
            const SIZE: usize = #size;
            const MAX_SIZE: usize = #max_size;
          }
        }
      }
      CachePolicy::Block => {
        quote! {
          impl ::cido::__internal::CachePolicy for #struct_name {
            type Transformer = #struct_name;
            type CacheId = <Self::Transformer as ::cido::__internal::Identifiable>::Id;
            type CacheLoader = ::core::convert::Infallible;
            const QUERY_DATABASE: bool = false;
            const QUERY_DATABASE_IF_NOT_FULL: bool = false;
            const LOAD_DATABASE: bool = false;
            const LOAD_MOST_RECENT_FIRST: bool = false;
            const SIZE: usize = 0;
            const MAX_SIZE: usize = 1;
          }
        }
      }
      CachePolicy::Never => {
        quote! {
          impl ::cido::__internal::CachePolicy for #struct_name {
            type Transformer = #struct_name;
            type CacheId = <Self::Transformer as ::cido::__internal::Identifiable>::Id;
            type CacheLoader = ::core::convert::Infallible;
            const QUERY_DATABASE: bool = false;
            const QUERY_DATABASE_IF_NOT_FULL: bool = false;
            const LOAD_DATABASE: bool = false;
            const LOAD_MOST_RECENT_FIRST: bool = false;
            const SIZE: usize = 0;
            const MAX_SIZE: usize = 0;
          }
        }
      }
      CachePolicy::Custom => return quote! {},
    };
    quote! {
      #policy_impl

      impl ::cido::__internal::CacheKey<#struct_name> for <#struct_name as ::cido::__internal::Identifiable>::Id {
        fn eq_key(&self, cache_id: &<#struct_name as CachePolicy>::CacheId) -> bool {
          self == cache_id
        }
        fn hash_cache<H: ::std::hash::Hasher>(&self, state: &mut H) {
          <Self as ::std::hash::Hash>::hash(self, state);
        }
        fn to_id(&self) -> Self {
          self.clone()
        }
        fn eq_id(&self, id: &Self) -> bool {
          self == id
        }
        fn to_cache_id(&self) -> <#struct_name as CachePolicy>::CacheId {
          self.clone()
        }
      }
    }
  }
}