kdb-macros 0.1.0

kdb integration macros.
Documentation
#![doc = include_str!("../README.MD")]

extern crate proc_macro;

use convert_case::Casing;
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, parse_macro_input};

/// The KRQLQuery derive macro will create two structure QC and QC_ used to serialize
/// following the format expected by krQL.
///
/// For instance the following structure:
///  
/// ```nocompile
/// pub struct Test
/// {
///   pub print: Option<String>,
///   #[serde(rename = "return")]
///   pub return_value: Option<String>,
/// }
/// ```
///
/// Should be serialized as:
/// ```yaml
/// test:
///   print: "a string"
///   return: "an other string"
/// ```
///
/// But with serde, it would be serialized as:
/// print: "a string"
/// return: "an other string"
///
/// To solve that problem, KRQLQuery generates two structures TestQC and TestQC_.
/// TestQC_ is a copy of Test used for serialization, and TestQC is defined as:
///
/// ```nocompile
/// struct TestQC
/// {
///   test: TestQC_
/// }
/// ```
///
/// Then we can define Test with the following:
///
/// ```nocompile
/// #[derive(Clone, Default, KRQLQuery)]
/// pub struct Test { ... }
/// ```
///
/// That means that Test is serialized using the TestQC class into the proper structure for krQL.
///
/// serde(...) can be used on fields
///
/// ```nocompilation
/// # use kdb_connection::KRQLQuery;
/// #[derive(Clone, Default, KRQLQuery)]
/// pub struct Test {
///   #[serde(rename="some field")]
///   some_field: String,
///  }
/// ```
/// krql(key = "...", tag = "...") can be used to set the krQL key, otherwise default to snake case
/// version of the class name. While tag is passed to serde, for enums.
#[proc_macro_derive(KRQLQuery, attributes(krql, serde))]
pub fn krql_query(input: TokenStream) -> TokenStream
{
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;
    let name_qc = proc_macro2::Ident::new(&format!("{}QC", name), proc_macro2::Span::call_site());
    let name_qc_ = proc_macro2::Ident::new(&format!("{}QC_", name), proc_macro2::Span::call_site());
    let mut key_str = name.to_string().to_case(convert_case::Case::Snake);
    let name_cc = proc_macro2::Ident::new(key_str.as_str(), proc_macro2::Span::call_site());
    let mut tag_str = None;
    for attribute in input.attrs
    {
        if attribute.path().is_ident("krql")
        {
            attribute
                .parse_nested_meta(|meta| {
                    // this parses the `(`
                    if meta.path.is_ident("key")
                    {
                        // this parses the `key`
                        let value = meta.value()?; // this parses the `=`
                        let s: syn::LitStr = value.parse()?; // this parses the value
                        key_str = s.value();
                        Ok(())
                    }
                    else if meta.path.is_ident("tag")
                    {
                        // this parses the `tag`
                        let value = meta.value()?; // this parses the `=`
                        let s: syn::LitStr = value.parse()?; // this parses the value
                        tag_str = Some(s.value());
                        Ok(())
                    }
                    else
                    {
                        Err(meta.error("unsupported attribute"))
                    }
                })
                .unwrap();
        }
    }
    let key = proc_macro2::Literal::string(key_str.as_str());
    let serde_qc_ = match tag_str
    {
        Some(tag) =>
        {
            let tag = proc_macro2::Literal::string(tag.as_str());
            quote! {
              #[serde(tag = #tag)]
            }
        }
        None => proc_macro2::TokenStream::new(),
    };
    let out = match input.data
    {
        Data::Enum(e) =>
        {
            let variants_iter = e.variants.iter();
            let variants_elements_for_match: Vec<
                syn::punctuated::Punctuated<syn::Ident, syn::Token![,]>,
            > = e
                .variants
                .iter()
                .map(|v| v.fields.iter().map(|f| f.ident.clone().unwrap()).collect())
                .collect();
            let variants_elements_iter_a = variants_elements_for_match.iter();
            let variants_elements_iter_b = variants_elements_for_match.iter();
            let variants_names_for_match: Vec<syn::Ident> =
                e.variants.iter().map(|v| v.ident.clone()).collect();
            let variants_names_iter_a = variants_names_for_match.iter();
            let variants_names_iter_b = variants_names_for_match.iter();
            quote! {
              impl KRQLQuery for #name
              {
              }
              #[derive(serde::Serialize)]
              struct #name_qc
              {
                #[serde(rename = #key)]
                pub #name_cc: #name_qc_
              }

              impl From<#name> for #name_qc
              {
                fn from(value: #name) -> Self
                {
                  Self {
                     #name_cc: #name_qc_::from(value)
                   }
                }
              }

              #[skip_serializing_none]
              #[derive(serde::Serialize)]
              #serde_qc_
              enum #name_qc_
              {
                #(
                  #variants_iter,
                )*
              }

              impl From<#name> for #name_qc_
              {
                fn from(value: #name) -> Self
                {
                  match value {
                    #(
                      #name ::#variants_names_iter_a { #variants_elements_iter_a } => #name_qc_ ::#variants_names_iter_b { #variants_elements_iter_b},
                    )*
                  }
                }
              }
              impl serde::Serialize for #name
              {
                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
                where
                  S: serde::Serializer,
                {
                  let t: #name_qc = self.clone().into();
                  t.serialize(serializer)
                }
              }
            }
        }
        Data::Struct(s) =>
        {
            let fields_iter = s.fields.iter();
            let fields_names_iter = s.fields.iter().map(|field| field.ident.as_ref().unwrap());
            quote! {
              impl KRQLQuery for #name
              {
              }
              #[derive(serde::Serialize, serde::Deserialize)]
              struct #name_qc
              {
                #[serde(rename = #key)]
                pub #name_cc: #name_qc_
              }

              impl From<#name> for #name_qc
              {
                fn from(value: #name) -> Self
                {
                  Self { #name_cc: #name_qc_::from(value) }
                }
              }

              #[skip_serializing_none]
              #[derive(serde::Serialize, serde::Deserialize)]
              #serde_qc_
              struct #name_qc_
              {
                #(
                  #fields_iter,
                )*
              }

              impl From<#name> for #name_qc_
              {
                fn from(value: #name) -> Self
                {
                  Self {
                    #(
                      #fields_names_iter: value.#fields_names_iter,
                    )*
                  }
                }
              }

              impl serde::Serialize for #name
              {
                fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
                where
                  S: serde::Serializer,
                {
                  let t: #name_qc = self.clone().into();
                  t.serialize(serializer)
                }
              }

            }
        }
        _ => todo!(),
    };
    quote! {
      #out

      impl TryFrom<&#name> for crate::dbc::Query
      {
          type Error = crate::Error;
          fn try_from(value: &#name) -> std::result::Result<Self, Self::Error>
          {
              let krql_query = serde_saphyr::to_string(value)?;
              Ok(crate::dbc::Query::new(
                  krql_query,
                  Default::default(),
                  crate::dbc::QueryType::KRQL,
              ))
          }
      }

      impl TryFrom<#name> for crate::dbc::Query
      {
          type Error = crate::Error;
          fn try_from(value: #name) -> std::result::Result<Self, Self::Error>
          {
              (&value).try_into()
          }
      }
    }
    .into()
}