mod r#enum;
mod r#struct;
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use syn::{Data, DeriveInput, Field, FieldsNamed, FieldsUnnamed};
#[derive(Copy, Clone)]
enum ItemType {
Struct,
Enum,
}
pub fn derive_deserialize_impl(item: TokenStream) -> TokenStream {
#[expect(clippy::expect_used)]
let item: DeriveInput = syn::parse2(item).expect("to be able to parse tokens");
match &item.data {
Data::Enum(..) => r#enum::derive(&item),
Data::Struct(..) => r#struct::derive(&item),
Data::Union(..) => panic!("Unions are not supported"),
}
}
fn generate_field_deserialization(item_ident: &dyn ToTokens, item_type: ItemType, field_ident: &impl ToTokens, field: &Field, field_index: usize, total_fields: usize) -> TokenStream {
let ident_string = quote! {#item_ident}.to_string().replace(" :: ", "::");
let error_string = format!(
"{} {ident_string} with {total_fields} element{}",
{
match item_type {
ItemType::Struct => "struct",
ItemType::Enum => "enum",
}
},
if total_fields == 1 { "" } else { "s" }
);
if let syn::Type::Array(array_data) = &field.ty {
let len = &array_data.len;
quote! {
let mut #field_ident = [0; #len];
for elem in #field_ident.as_mut() {
*elem = serde::de::SeqAccess::next_element::<_>(&mut seq)?
.ok_or_else(|| serde::de::Error::invalid_length(#field_index, &#error_string))?;
}
}
} else {
quote! {
let #field_ident = serde::de::SeqAccess::next_element::<_>(&mut seq)?
.ok_or_else(|| serde::de::Error::invalid_length(#field_index, &#error_string))?;
}
}
}
fn generate_named_fields_deserialization(item_ident: &dyn ToTokens, item_type: ItemType, fields: &FieldsNamed) -> TokenStream {
let field_de = fields
.named
.iter()
.enumerate()
.map(|(i, field)| {
#[expect(clippy::expect_used)]
let ident = field.ident.clone().expect("for a named field to have an ident");
generate_field_deserialization(item_ident, item_type, &ident, field, i, fields.named.len())
})
.reduce(|acc, field_de| {
quote! {
#acc
#field_de
}
});
let field_idents = fields.named.iter().map(|field| unsafe { field.ident.clone().unwrap_unchecked() });
quote! {
#field_de
Ok(#item_ident { #(#field_idents),* })
}
}
fn generate_unnamed_fields_deserialization(item_ident: &dyn ToTokens, item_type: ItemType, fields: &FieldsUnnamed) -> TokenStream {
let (field_idents, deserializations): (Vec<_>, Vec<_>) = fields
.unnamed
.iter()
.enumerate()
.map(|(i, field)| {
let ident = format_ident!("field_{i}");
let de = generate_field_deserialization(item_ident, item_type, &ident, field, i, fields.unnamed.len());
(ident, de)
})
.unzip();
let field_de = deserializations.into_iter().reduce(|acc, field_de| {
quote! {
#acc
#field_de
}
});
quote! {
#field_de
Ok(#item_ident ( #(#field_idents),* ))
}
}