use crate::attrs::{SiocField, SiocInput};
use darling::FromDeriveInput;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
pub fn expand(input: syn::DeriveInput) -> darling::Result<TokenStream> {
let input = SiocInput::from_derive_input(&input)?;
let fields = match input.data {
darling::ast::Data::Struct(f) => f,
darling::ast::Data::Enum(..) => {
return Err(
darling::Error::unsupported_shape_with_expected("enum", &"struct")
.with_span(&input.ident),
);
}
};
let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
let ident = &input.ident;
let body = generate_body(&fields, input.strict.is_present());
Ok(quote! {
impl #impl_generics ::sioc::prelude::DeserializePayload for #ident #type_generics #where_clause {
fn deserialize_payload<'de, S>(__seq: &mut S) -> ::std::result::Result<Self, S::Error>
where
S: ::serde::de::SeqAccess<'de>,
{
#body
}
}
})
}
fn var_for_field(f: &SiocField, pos: usize) -> TokenStream {
match &f.ident {
Some(name) => quote! { #name },
None => {
let v = format_ident!("__field_{}", pos);
quote! { #v }
}
}
}
fn generate_body(fields: &darling::ast::Fields<SiocField>, strict: bool) -> TokenStream {
let field_count = fields.iter().filter(|f| !f.flatten.is_present()).count();
let vars: Vec<_> = fields
.iter()
.enumerate()
.map(|(pos, f)| var_for_field(f, pos))
.collect();
let decls = fields.iter().zip(vars.iter()).enumerate().map(|(i, (field, var))| {
if field.flatten.is_present() {
let field_type = &field.ty;
quote! {
let mut #var: #field_type = ::std::default::Default::default();
while let ::std::option::Option::Some(el) = __seq.next_element()? {
#var.push(el);
}
}
} else {
quote! {
let #var = __seq.next_element()?
.ok_or_else(|| ::serde::de::Error::invalid_length(#i, &"expected element"))?;
}
}
});
let drain = if strict {
quote! {
let mut __extra = 0usize;
while __seq.next_element::<::serde::de::IgnoredAny>()?.is_some() {
__extra += 1;
}
if __extra > 0 {
return ::std::result::Result::Err(
::serde::de::Error::invalid_length(
#field_count + __extra,
&::std::format!("exactly {} elements", #field_count).as_str(),
)
);
}
}
} else {
quote! {
while __seq.next_element::<::serde::de::IgnoredAny>()?.is_some() {}
}
};
let construct = match fields.style {
darling::ast::Style::Struct => quote! { Self { #(#vars),* } },
darling::ast::Style::Tuple => quote! { Self(#(#vars),*) },
darling::ast::Style::Unit => quote! { Self },
};
quote! {
#(#decls)*
#drain
::std::result::Result::Ok(#construct)
}
}