cido-macros 0.2.0

Macros for generating code that enables easier interfacing with cido
Documentation
use crate::parse::deps::*;
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};

pub(super) fn graphql_impl(
  cidomap: &super::CidomapStruct,
  graphql_name: &Ident,
) -> syn::Result<TokenStream> {
  let graphql_inner_name = format_ident!("{graphql_name}Inner");
  let mut graphql_variants = Vec::new();
  let mut graphql_field_impls = Vec::new();
  let cidomap_name = &cidomap.name;
  let opts = &cidomap.opts;
  let fields = &cidomap.fields;
  let vis = &cidomap.vis;
  let block_number_err = format!("Error when querying MetaInfo<{}>: {{}}", cidomap_name);

  for f in fields {
    let ty = &f.field.ty;
    let graphql_input_ty = quote!(<<#ty as ::cido::__internal::Transformer>::GraphqlInput as ::cido::__internal::GraphqlInputType>);
    let filter_struct_ty = quote! ( #graphql_input_ty::Filter );
    let order_by_struct_ty =
      quote!( ::cido::__internal::GraphqlOrderBy<#graphql_input_ty::OrderByVariant> );
    let name = f
      .field
      .ident
      .as_ref()
      .ok_or_else(|| crate::err(&f.field, "Fields without names are not supported"))?;
    let v_name = name;
    let custom_graphql_field = &f.graphql;
    let single_fn = custom_graphql_field
      .single
      .name
      .clone()
      .or_else(|| opts.gql.single.apply_default_name(name))
      .unwrap_or_else(|| name.clone());
    let complexity = custom_graphql_field
      .single
      .complexity
      .as_ref()
      .map(|expr| quote!(#[graphql(complexity = #expr)]))
      .unwrap_or_default();
    let multiple_fn = custom_graphql_field
      .multiple
      .name
      .clone()
      .or_else(|| opts.gql.multiple.apply_default_name(name))
      .unwrap_or_else(|| format_ident!("{name}_multiple"));
    let multiple_complexity = custom_graphql_field
      .multiple
      .complexity
      .as_ref()
      .map(|expr| quote!(#[graphql(complexity = #expr)]))
      .unwrap_or_default();
    let history_fn = custom_graphql_field
      .history
      .name
      .clone()
      .or_else(|| opts.gql.history.apply_default_name(name))
      .unwrap_or_else(|| format_ident!("{name}_history"));
    let history_complexity = custom_graphql_field
      .history
      .complexity
      .as_ref()
      .map(|expr| quote!(#[graphql(complexity = #expr)]))
      .unwrap_or_default();

    let graphql_type = quote! {<#ty as ::cido::__internal::Transformer>::Graphql};
    graphql_variants.push(quote! {#v_name(#graphql_type)});

    let id_ty = custom_graphql_field
      .id
      .clone()
      .unwrap_or_else(|| syn::parse_quote!(<#ty as ::cido::__internal::Identifiable>::Id));
    let block_id_ty = quote!( <<<#ty as ::cido::__internal::Transformer>::Cidomap as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::BlockId );
    let block_number_ty = quote!( <<<#ty as ::cido::__internal::Transformer>::Cidomap as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::BlockNumber );

    let id_borrow = custom_graphql_field
      .to_id_borrow
      .clone()
      .unwrap_or_else(|| syn::parse_quote!({ id }));

    let history_id_borrow = custom_graphql_field
      .to_id_borrow
      .as_ref()
      .map(|expr| {
        quote! {
          if let ::core::option::Option::Some(id) = id {
            ::core::option::Option::Some(#expr)
          } else {
            ::core::option::Option::None
          }
        }
      })
      .unwrap_or(quote! { id });
    let internal_error_converter = quote! {
      map_err(|e| {
        ::cido::__internal::tracing::error!("{:?}", e);
        ::cido::__internal::async_graphql::Error::new("internal error")
      })
    };

    let field_impl = quote! {
        #complexity
        #vis async fn #single_fn<'ctx>(
          &self,
          ctx: &::cido::__internal::async_graphql::Context<'ctx>,
          id: #id_ty,
          #[graphql(desc = "The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the cidomap has progressed to or past the minimum block number. Defaults to the latest block when omitted.")]
          block: ::core::option::Option<#block_id_ty>,
        ) -> ::cido::__internal::async_graphql::Result<::core::option::Option<<#ty as ::cido::__internal::Transformer>::GraphqlOutput>>
        {
          use ::cido::__internal::BlockId;

          let network = ctx.data::<::std::sync::Arc<<<#ty as ::cido::__internal::Transformer>::Cidomap as ::cido::__internal::Cidomap>::Network>>().#internal_error_converter?;
          let block_number = if let Some(block) = block {
            block.to_block_number(&network).await.#internal_error_converter?
          } else {
            <#block_number_ty as ::cido::__internal::BlockNumber>::MAX
          };

          ::cido::__internal::GraphqlQueryBuilder::<#ty>::fetch_optional_by_blocknumber(
            ::cido::__internal::GraphqlQueryBuilder::<_>::new(
              ::std::option::Option::None,
              ::std::option::Option::None,
              ::std::option::Option::None,
              ::std::default::Default::default(),
              ::std::default::Default::default(),
            ),
            ctx,
            &#id_borrow,
            block_number,
          ).await
        }

        #[allow(clippy::too_many_arguments)]
        #multiple_complexity
        #vis async fn #multiple_fn<'ctx>(&self,
          ctx: &::cido::__internal::async_graphql::Context<'ctx>,
          #[graphql(default_with = "::core::option::Option::Some(0_u64)")]
          skip: ::std::option::Option<u64>,
          #[graphql(default_with = "::core::option::Option::Some(100_u64)")]
          first: ::std::option::Option<u64>,
          order_by: ::std::option::Option<#order_by_struct_ty>,
          order_direction: ::std::option::Option<::cido::__internal::GraphqlOrderDirection>,
          #[graphql(name="where")]
          filter: ::std::option::Option<#filter_struct_ty>,
          #[graphql(desc = "The block at which the query should be executed. Can either be a `{ hash: Bytes }` value containing a block hash, a `{ number: Int }` containing the block number, or a `{ number_gte: Int }` containing the minimum block number. In the case of `number_gte`, the query will be executed on the latest block only if the cidomap has progressed to or past the minimum block number. Defaults to the latest block when omitted.")]
          block: ::core::option::Option<#block_id_ty>,
        ) -> ::cido::__internal::async_graphql::Result<::std::vec::Vec<<#ty as ::cido::__internal::Transformer>::GraphqlOutput>>
        {
          use ::cido::__internal::BlockId;

          let network = ctx.data::<::std::sync::Arc<<<#ty as ::cido::__internal::Transformer>::Cidomap as ::cido::__internal::Cidomap>::Network>>().#internal_error_converter?;
          let block_number = if let Some(block) = block {
            block.to_block_number(&network).await.#internal_error_converter?
          } else {
            <#block_number_ty as ::cido::__internal::BlockNumber>::MAX
          };

          let filter = match filter {
            ::core::option::Option::None => ::core::option::Option::None,
            ::core::option::Option::Some(filter) => ::core::option::Option::Some(::cido::__internal::TryMapFilter::try_map(filter)?),
          };

          ::cido::__internal::GraphqlQueryBuilder::<#ty>::new(
            filter.map(::std::sync::Arc::new),
            ::std::option::Option::None,
            order_by.map(::std::sync::Arc::new),
            order_direction.map(::std::sync::Arc::new).unwrap_or_default(),
            ::cido::__internal::GraphqlPagination::new(first, skip)?,
          )
          .fetch_all(ctx, block_number)
          .await
        }
    };

    let history = quote! {
      #[allow(clippy::too_many_arguments)]
      #history_complexity
      #vis async fn #history_fn<'ctx>(&self,
        ctx: &::cido::__internal::async_graphql::Context<'ctx>,
        id: ::core::option::Option<#id_ty>,
        start_block: #block_id_ty,
        end_block: ::std::option::Option<#block_id_ty>,
        order_by: ::std::option::Option<#order_by_struct_ty>,
        order_direction: ::std::option::Option<::cido::__internal::GraphqlOrderDirection>,
        #[graphql(default_with = "::core::option::Option::Some(10_000_u64)")]
        first: ::std::option::Option<u64>,
        skip: ::std::option::Option<u64>,
        #[graphql(name = "where")]
        filter: ::std::option::Option<#filter_struct_ty>,
      ) -> ::cido::__internal::async_graphql::Result<::std::vec::Vec<<#ty as ::cido::__internal::Transformer>::GraphqlOutput>>
      {
        use ::cido::__internal::BlockId;

        let network = ctx.data::<::std::sync::Arc<<<#ty as ::cido::__internal::Transformer>::Cidomap as ::cido::__internal::Cidomap>::Network>>().#internal_error_converter?;

        let start_block_number = start_block.to_block_number(&network).await?;
        let end_block_number = match end_block {
          ::core::option::Option::Some(end_block) => end_block.to_block_number(&network).await?,
          ::core::option::Option::None => {
            let conn = ctx.data::<::cido::__internal::sqlx::PgPool>()?;
            ::cido::__internal::MetaInfo::<<#cidomap_name as ::cido::__internal::Cidomap>::Network>::get(conn).await.inspect_err(|e| {
              ::cido::__internal::tracing::error!(#block_number_err, e);
            })?
            .get_sync_block()
          }
        };

        let filter = match filter {
          ::core::option::Option::None => ::core::option::Option::None,
          ::core::option::Option::Some(filter) => ::core::option::Option::Some(::cido::__internal::TryMapFilter::try_map(filter)?),
        };

        let id_borrow = #history_id_borrow;
        ::cido::__internal::GraphqlQueryBuilder::<#ty>::new(
          filter.map(::std::sync::Arc::new),
          ::std::option::Option::None,
          order_by.map(::std::sync::Arc::new),
          order_direction.map(::std::sync::Arc::new).unwrap_or_default(),
          ::cido::__internal::GraphqlPagination::new(first, skip)?,
        )
        .fetch_history(ctx, id_borrow.as_ref(), start_block_number, end_block_number, false)
        .await
        .map(|(results, _)| results)
      }
    };

    graphql_field_impls.push(quote! { #field_impl #history });
  }

  let extensions = &cidomap.opts.gql.extensions;
  let graphql_attrs = Vec::<syn::Attribute>::new();

  let tokens = quote! {
    #(#graphql_attrs)*
    #[derive(::core::default::Default, ::cido::__internal::async_graphql::MergedObject)]
    #[graphql(name = "Query")]
    #vis struct #graphql_name(#graphql_inner_name, #(#extensions),*);

    #(#graphql_attrs)*
    #[derive(::core::default::Default)]
    #vis struct #graphql_inner_name;

    #[::cido::__internal::async_graphql::Object]
    impl #graphql_inner_name {
      #(#graphql_field_impls)*

      #[graphql(name = "_meta")]
      /// Access to cidomap metadata
      #vis async fn meta<'ctx>(
        &self,
        ctx: &::cido::__internal::async_graphql::Context<'ctx>,
        block: ::core::option::Option<<<#cidomap_name as ::cido::__internal::Cidomap>::Network as ::cido::__internal::Network>::BlockId>,
      )
        -> ::cido::__internal::async_graphql::Result<::core::option::Option<::cido::__internal::GraphqlCidomapMetaInfo<<#cidomap_name as ::cido::__internal::Cidomap>::Network>>> {
        use ::cido::__internal::Network;
        use ::core::convert::From;

        let conn = ctx.data::<::cido::__internal::sqlx::PgPool>()?;
        // TODO: change this to use the BlockId passed in from the function
        let meta = ::cido::__internal::MetaInfo::<<#cidomap_name as ::cido::__internal::Cidomap>::Network>::get(conn).await.map_err(|e| {
          ::cido::__internal::tracing::error!(#block_number_err, e);
          ::cido::__internal::async_graphql::Error::from(::cido::__internal::CidomapError::from(e))
        })?;

        let meta = ::cido::__internal::GraphqlCidomapMetaInfo::<<#cidomap_name as ::cido::__internal::Cidomap>::Network>::new(meta);
        ::std::result::Result::Ok(::core::option::Option::Some(meta))
      }
    }
  };
  Ok(tokens)
}