use crate::ast::{
container::ContainerAttributes, prop::Prop, r#enum::EnumProp, r#enum::FieldKind, DeriveItem,
};
use proc_macro2::Span;
use proc_macro2::TokenStream;
use quote::{format_ident, quote, ToTokens};
use std::str::FromStr;
use syn::Ident;
use syn::LitInt;
pub(crate) fn generate_decode_trait_impls(input: &DeriveItem) -> TokenStream {
match &input {
DeriveItem::Struct(kf_struct, _attrs) => {
let field_tokens = generate_struct_fields(&kf_struct.props, &kf_struct.struct_ident);
let ident = &kf_struct.struct_ident;
let (impl_generics, ty_generics, where_clause) = kf_struct.generics.split_for_impl();
quote! {
impl #impl_generics fluvio_protocol::Decoder for #ident #ty_generics #where_clause {
fn decode<T>(&mut self, src: &mut T,version: fluvio_protocol::Version) -> Result<(),std::io::Error> where T: fluvio_protocol::bytes::Buf {
log::trace!("decoding struct: {}",stringify!(#ident));
#field_tokens
Ok(())
}
}
}
}
DeriveItem::Enum(kf_enum, attrs) => {
let ident = &kf_enum.enum_ident;
let (impl_generics, ty_generics, where_clause) = kf_enum.generics.split_for_impl();
let int_type = if let Some(int_type_name) = &attrs.repr_type_name {
format_ident!("{}", int_type_name)
} else {
Ident::new("u8", Span::call_site())
};
let enum_tokens = generate_variant_tokens(&int_type);
let try_enum = generate_try_enum_from_kf_enum(&kf_enum.props, &int_type, ident, &attrs);
let res = quote! {
impl #impl_generics fluvio_protocol::Decoder for #ident #ty_generics #where_clause {
fn decode<T>(&mut self, src: &mut T,version: fluvio_protocol::Version) -> Result<(),std::io::Error> where T: fluvio_protocol::bytes::Buf {
#enum_tokens
Ok(())
}
}
#try_enum
};
res
}
}
}
pub(crate) fn generate_struct_fields(props: &[Prop], struct_ident: &Ident) -> TokenStream {
let recurse = props.iter().map(|prop| {
let fname = format_ident!("{}", prop.field_name);
if prop.varint {
quote! {
log::trace!("start decoding varint field <{}>", stringify!(#fname));
let result = self.#fname.decode_varint(src);
if result.is_ok() {
log::trace!("decoding ok varint <{}> => {:?}",stringify!(#fname),&self.#fname);
} else {
log::trace!("decoding varint error <{}> ==> {}",stringify!(#fname),result.as_ref().unwrap_err());
return result;
}
}
} else {
let base = quote! {
log::trace!("start decoding struct: <{}> field: <{}>",stringify!(#struct_ident),stringify!(#fname));
let result = self.#fname.decode(src,version);
if result.is_ok() {
log::trace!("decoding struct: <{}> field: <{}> => {:#?}",stringify!(#struct_ident),stringify!(#fname),&self.#fname);
} else {
log::trace!("error decoding <{}> ==> {}",stringify!(#fname),result.as_ref().unwrap_err());
return result;
}
};
prop.version_check_token_stream(base)
}
});
quote! {
#(#recurse)*
}
}
fn generate_variant_tokens(int_type: &Ident) -> TokenStream {
quote! {
use std::convert::TryInto;
let mut typ: #int_type = 0;
typ.decode(src, version)?;
log::trace!("decoded type: {}", typ);
let convert: Self = typ.try_into()?;
*self = convert;
}
}
fn generate_try_enum_from_kf_enum(
props: &[EnumProp],
int_type: &Ident,
enum_ident: &Ident,
attrs: &ContainerAttributes,
) -> TokenStream {
let mut variant_expr = vec![];
for (idx, prop) in props.iter().enumerate() {
let id = &format_ident!("{}", prop.variant_name);
let field_idx = if let Some(tag) = &prop.tag {
if let Ok(literal) = TokenStream::from_str(tag) {
literal
} else {
LitInt::new(&idx.to_string(), Span::call_site()).to_token_stream()
}
} else if attrs.encode_discriminant {
if let Some(dsc) = &prop.discriminant {
dsc.as_token_stream()
} else {
LitInt::new(&idx.to_string(), Span::call_site()).to_token_stream()
}
} else {
LitInt::new(&idx.to_string(), Span::call_site()).to_token_stream()
};
let variant_code = match &prop.kind {
FieldKind::Named(expr) => {
let mut decode_variant_fields = vec![];
for (idx, field) in expr.named.iter().enumerate() {
let field_ident = &field.ident;
let var_ident = format_ident!("res_{}", idx);
decode_variant_fields.push(quote! {
let mut #var_ident = #field_ident::default();
#var_ident.decode(src, version)?;
});
}
let mut variant_construction_params = vec![];
for (idx, _) in decode_variant_fields.iter().enumerate() {
let var_ident = format_ident!("res_{}", idx);
variant_construction_params.push(quote! {#var_ident, });
}
quote! {
#field_idx => {
#(#decode_variant_fields)*
Ok(Self::#id{#(#variant_construction_params)*}),
}
}
}
FieldKind::Unnamed(expr) => {
let mut decode_variant_fields = vec![];
for (idx, field) in expr.unnamed.iter().enumerate() {
let field_ident = &field.ident;
let var_ident = format_ident!("res_{}", idx);
decode_variant_fields.push(quote! {
let mut #var_ident = #field_ident::default();
#var_ident.decode(src, version)?;
});
}
let mut variant_construction_params = vec![];
for (idx, _) in decode_variant_fields.iter().enumerate() {
let var_ident = format_ident!("res_{}", idx);
variant_construction_params.push(quote! {#var_ident, });
}
quote! {
#field_idx => {
#(#decode_variant_fields)*
Ok(Self::#id(#(#variant_construction_params)*)),
}
}
}
_ => quote! {
#field_idx => Ok(Self::#id),
},
};
variant_expr.push(variant_code);
}
variant_expr.push(quote! {
_ => return Err(std::io::Error::new(
std::io::ErrorKind::InvalidData,
format!("Unknown {} type {}", stringify!(#enum_ident), typ)
))
});
quote! {
impl std::convert::TryFrom<#int_type> for #enum_ident {
type Error = std::io::Error;
fn try_from(value: #int_type) -> Result<Self, Self::Error> {
let typ = stringify!(#int_type);
match value {
#(#variant_expr)*
}
}
}
}
}
pub(crate) fn generate_default_trait_impls(input: &DeriveItem) -> TokenStream {
match &input {
DeriveItem::Struct(kf_struct, _attrs) => {
let ident = &kf_struct.struct_ident;
let field_tokens = generate_default_impls(&kf_struct.props);
let (impl_generics, ty_generics, where_clause) = kf_struct.generics.split_for_impl();
quote! {
impl #impl_generics Default for #ident #ty_generics #where_clause {
fn default() -> Self {
Self {
#field_tokens
}
}
}
}
}
DeriveItem::Enum(_, _) => quote! {},
}
}
pub(crate) fn generate_default_impls(props: &[Prop]) -> TokenStream {
let recurse = props.iter().map(|prop| {
let fname = format_ident!("{}", prop.field_name);
if let Some(def) = &prop.default_value {
if let Ok(liter) = TokenStream::from_str(def) {
quote! {
#fname: #liter,
}
} else {
quote! {
#fname: std::default::Default::default(),
}
}
} else {
quote! {
#fname: std::default::Default::default(),
}
}
});
quote! {
#(#recurse)*
}
}