#![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};
#[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| {
if meta.path.is_ident("key")
{
let value = meta.value()?; let s: syn::LitStr = value.parse()?; key_str = s.value();
Ok(())
}
else if meta.path.is_ident("tag")
{
let value = meta.value()?; let s: syn::LitStr = value.parse()?; 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()
}