cido-macros 0.2.0

Macros for generating code that enables easier interfacing with cido
Documentation
use super::err;
use crate::parse::deps::*;
pub use crate::parse::{OrderBy, OrderByVariant};
use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
  Ident,
  parse::{Parse, Parser},
};

type Result = syn::Result<TokenStream>;

impl crate::codegen::CodegenWrapper<OrderBy> {
  pub fn generate_order_by(&self) -> syn::Result<TokenStream> {
    crate::parse::embed_generated_code(
      self.embed_generated_code,
      &self.name,
      self.generate_order_by_inner()?,
      "order_by",
    )
  }

  fn generate_order_by_inner(&self) -> Result {
    let vis = &self.vis;
    let order_by_ident = &self.name;
    let order_by_ident_name = order_by_ident.to_string();
    let short_order_by_ident = format_ident!("{order_by_ident}Short");

    let mut to_value_match_exprs = vec![];
    let mut parse_match_exprs = vec![];
    let mut short_parse_match_exprs = vec![];
    let mut display_exprs = vec![];
    let mut short_display_exprs = vec![];
    let mut variants = vec![];
    let mut short_variants = vec![];
    let mut alias_consts_exprs = vec![];
    let mut db_field_name_matcher = vec![];
    let mut field_name_strs = vec![];
    let mut enum_value_generator = vec![];

    let num_default = self.variants.iter().filter(|v| v.default).count();
    if num_default != 1 {
      return Err(err(
        &self.name,
        "OrderBy enum must have a default variant, use #[orderby(default)] to mark it",
      ));
    }

    let num_id = self.variants.iter().filter(|v| v.id).count();
    if num_id > 1 {
      return Err(err(
        &self.name,
        "OrderBy enum must have at most one field marked as an id",
      ));
    }

    let id = self
      .variants
      .iter()
      .find(|v| v.id)
      .map(|v| {
        let name = format_ident!("{}", v.name.to_string().to_pascal_case());
        quote! {
          #order_by_ident::#name
        }
      })
      .unwrap_or(quote! { <Self as ::core::default::Default>::default() });

    for v in self.variants.iter() {
      if !v.hidden {
        let field_name_str = v.graphql_name.to_string();
        let variant_name = v.name.clone();
        let variant_name_str = v.name.to_string();
        let sql_name = v.sql_name.to_string();
        v.aliases.iter().for_each(|alias| {
          let alias_field_name_str = alias.to_string();
          // TODO: don't add this if the field is an entity/event
          field_name_strs.push(alias_field_name_str.clone());
          let alias_variant_name = format_ident!("{}", alias.to_string().to_pascal_case());

          alias_consts_exprs.push(quote! {
            pub const #alias_variant_name: Self = Self::#variant_name;
          });

          parse_match_exprs.push(quote! {
            #alias_field_name_str => ::core::result::Result::Ok(#order_by_ident::#variant_name),
          });

          enum_value_generator.push(quote! { insert(#alias_field_name_str); });
        });

        to_value_match_exprs.push(quote! {
          #order_by_ident::#variant_name => ::cido::__internal::async_graphql::Value::Enum(::cido::__internal::async_graphql::Name::new(#field_name_str)),
        });
        let parse_match_quote = quote! {
          #field_name_str => ::core::result::Result::Ok(Self::#variant_name),
        };
        parse_match_exprs.push(parse_match_quote.clone());

        let display_quote = quote! {
          Self::#variant_name => ::core::fmt::Formatter::write_str(f, #variant_name_str),
        };
        display_exprs.push(display_quote.clone());

        if v.default {
          let default = quote! { #[default] };
          variants.push(default.clone());
          short_variants.push(default);
        }
        let variant_quote = quote! { #variant_name, };
        variants.push(variant_quote.clone());

        db_field_name_matcher.push(quote! {
          Self::#variant_name => #sql_name,
        });

        enum_value_generator.push(quote! { insert(#field_name_str); });

        if let Some(graphql_type) = &v.graphql_type {
          // TODO: this is super hacky - figure out a better way to refer to this type
          let ee_order_by = format_ident!(
            "{}OrderByShort",
            Ident::parse.parse2(quote!(#graphql_type)).unwrap()
          );
          let ee_name = format_ident!("{}__", variant_name);
          let enum_format = format!("{field_name_str}__{{t}}");
          let prefix = format!("{field_name_str}__");
          to_value_match_exprs.push(quote! {
            Self::#ee_name(t) => ::cido::__internal::async_graphql::Value::Enum(
              ::cido::__internal::async_graphql::Name::new(format!(#enum_format))
            ),
          });
          parse_match_exprs.push(quote! {
            s if s.starts_with(#prefix) => ::core::result::Result::Ok(Self::#ee_name(s.strip_prefix(#prefix).unwrap().parse()?)),
          });
          display_exprs.push(quote! {
            Self::#ee_name(t) => ::core::fmt::Formatter::write_str(f, #enum_format),
          });
          variants.push(quote! { #ee_name(#ee_order_by), });
          // TODO: this just turns the sub fields into the id that is available and doesn't order by the sub fields
          // if we want to actually support this then this needs to be changed somehow
          db_field_name_matcher.push(quote! {
            Self::#ee_name(_) => #sql_name,
          });
          enum_value_generator.push(quote! {
            {
              static VALUES: ::std::sync::OnceLock<Vec<String>> = ::std::sync::OnceLock::new();
              let values = VALUES.get_or_init(||#ee_order_by::iter().map(|t|format!(#enum_format)).collect());
              values.iter().map(|s|s.as_str()).for_each(|v|insert(v));
            }
          });
        } else {
          field_name_strs.push(field_name_str.clone());
          short_parse_match_exprs.push(parse_match_quote);
          short_display_exprs.push(display_quote);
          short_variants.push(variant_quote);
        }
      }
    }

    let parse_match_exprs_quote = quote! {
      x => ::core::result::Result::Err(::std::format!("unknown {} field: {}", #order_by_ident_name, x).into())
    };
    parse_match_exprs.push(parse_match_exprs_quote.clone());
    short_parse_match_exprs.push(parse_match_exprs_quote);

    let attrs = self.attrs.outer_attributes();

    let fn_is_block_number = self
      .variants
      .iter()
      .find(|v| v.is_block_number)
      .map(|v| {
        let block_number_name = &v.name;
        quote! {
          fn is_block_number(&self) -> bool {
            matches!(self, Self::#block_number_name)
          }
        }
      })
      .unwrap_or_else(|| {
        quote! {
          fn is_block_number(&self) -> bool {
            false
          }
        }
      });
    let expect_non_camel_case = order_by_ident
      .to_string()
      .contains("_")
      .then(|| quote! {#[expect(non_camel_case_types)]});
    let expect_non_camel_case_short = short_order_by_ident
      .to_string()
      .contains("_")
      .then(|| quote! {#[expect(non_camel_case_types)]});

    Ok(quote! {
      #(#attrs)*
      #[derive(::core::default::Default, ::core::marker::Copy, ::core::clone::Clone, ::core::cmp::Eq, ::core::cmp::PartialEq, ::core::cmp::Ord, ::core::cmp::PartialOrd, ::core::hash::Hash, ::core::fmt::Debug)]
      #expect_non_camel_case
      #vis enum #order_by_ident {
        #(#variants)*
      }

      #(#attrs)*
      #[derive(::core::default::Default, ::core::marker::Copy, ::core::clone::Clone, ::core::cmp::Eq, ::core::cmp::PartialEq, ::core::cmp::Ord, ::core::cmp::PartialOrd, ::core::hash::Hash, ::core::fmt::Debug)]
      #expect_non_camel_case_short
      #vis enum #short_order_by_ident {
        #(#short_variants)*
      }

      impl #short_order_by_ident {
        pub fn iter() -> impl Iterator<Item = &'static str> {
          [#(#field_name_strs),*].into_iter()
        }
      }

      #[automatically_derived]
      impl ::core::fmt::Display for #order_by_ident {
        fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
          match self {
            #(#display_exprs)*
          }
        }
      }

      #[automatically_derived]
      impl ::core::fmt::Display for #short_order_by_ident {
        fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result {
          match self {
            #(#short_display_exprs)*
          }
        }
      }

      #[automatically_derived]
      impl ::cido::__internal::GraphqlOrderByVariant for #order_by_ident {
        fn id() -> Self {
          #id
        }

        /// The type name of the Transformer this is
        fn order_by_type_name() -> &'static str {
          #order_by_ident_name
        }

        fn enum_values() -> ::cido::__internal::async_graphql::indexmap::IndexMap<&'static str, ::cido::__internal::async_graphql::registry::MetaEnumValue> {
          let mut map = ::cido::__internal::async_graphql::indexmap::IndexMap::new();
          let mut insert = |field: &'static str| {
            map.insert(field, ::cido::__internal::async_graphql::registry::MetaEnumValue {
              name: field.to_string(),
              description: ::core::option::Option::None,
              deprecation: ::cido::__internal::async_graphql::registry::Deprecation::NoDeprecated,
              visible: if field.ends_with("__BlockNumber") {::std::option::Option::Some(|_|false)} else {::std::option::Option::None},
              inaccessible: false,
              tags: vec![], directive_invocations: vec![],
            });
          };
          #(#enum_value_generator)*
          map
        }

        fn db_field_name(&self) -> &'static str {
          match self {
            #(#db_field_name_matcher)*
          }
        }

        #fn_is_block_number
      }

      #[automatically_derived]
      impl ::std::str::FromStr for #order_by_ident {
        type Err = ::std::boxed::Box<dyn ::std::error::Error + ::core::marker::Send + ::core::marker::Sync + 'static>;

        fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
          match s.trim() {
            #(#parse_match_exprs)*
          }
        }
      }

      #[automatically_derived]
      impl ::std::str::FromStr for #short_order_by_ident {
        type Err = ::std::boxed::Box<dyn ::std::error::Error + ::core::marker::Send + ::core::marker::Sync + 'static>;

        fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
          match s.trim() {
            #(#short_parse_match_exprs)*
          }
        }
      }

      #[automatically_derived]
      impl ::cido::__internal::async_graphql::InputType for #order_by_ident {
        type RawValueType = Self;

        fn type_name() -> ::std::borrow::Cow<'static, str> {
          ::std::borrow::Cow::Borrowed(#order_by_ident_name)
        }

        fn create_type_info(registry: &mut ::cido::__internal::async_graphql::registry::Registry) -> ::std::string::String {
          registry.create_input_type::<Self, _>(
            ::cido::__internal::async_graphql::registry::MetaTypeId::Enum,
            |_| ::cido::__internal::async_graphql::registry::MetaType::Enum {
              name: <Self as ::cido::__internal::async_graphql::InputType>::type_name().into_owned(),
              description: ::core::option::Option::None,
              enum_values: <Self as ::cido::__internal::GraphqlOrderByVariant>::enum_values().into_iter().map(|(k, v)|(k.to_string(), v)).collect(),
              visible: ::std::option::Option::None,
              inaccessible: false,
              tags: vec![], directive_invocations: vec![],
              rust_typename: ::std::option::Option::Some(::std::any::type_name::<Self>()),
            }
          )
        }

        fn parse(
          value: ::std::option::Option<::cido::__internal::async_graphql::Value>,
        ) -> ::cido::__internal::async_graphql::InputValueResult<Self> {
          match value {
            ::std::option::Option::None => ::cido::__internal::async_graphql::InputValueResult::Err(::cido::__internal::async_graphql::InputValueError::expected_type(value.unwrap_or_default())),
            ::std::option::Option::Some(val) => match val {
              ::cido::__internal::async_graphql::Value::String(val) => val.trim().parse().map_err(::cido::__internal::async_graphql::InputValueError::custom),
              ::cido::__internal::async_graphql::Value::Enum(val) => val.trim().parse().map_err(::cido::__internal::async_graphql::InputValueError::custom),
              x => ::cido::__internal::async_graphql::InputValueResult::Err(::cido::__internal::async_graphql::InputValueError::expected_type(x)),
            },
          }
        }

        fn to_value(&self) -> ::cido::__internal::async_graphql::Value {
          match self {
            #(#to_value_match_exprs)*
          }
        }

        fn as_raw_value(&self) -> ::std::option::Option<&Self::RawValueType> {
          ::std::option::Option::Some(self)
        }
      }

      #[automatically_derived]
      impl ::cido::__internal::async_graphql::InputObjectType for #order_by_ident {}
    })
  }
}