lenient_derive 0.1.2

A procedural macro to derive fault-tolerant Serde deserialization using #[lenient] and #[optional] annotations.
Documentation
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{parse_macro_input, Data, DeriveInput, Fields};

#[proc_macro_derive(LenientDeserialize, attributes(lenient, optional))]
pub fn derive_lenient(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let struct_name = input.ident;
    let inner_name = format_ident!("{}Internal", struct_name);

    let Data::Struct(data_struct) = input.data else {
        panic!("#[derive(LenientDeserialize)] only works on structs");
    };

    let Fields::Named(fields_named) = data_struct.fields else {
        panic!("LenientDeserialize only supports named fields");
    };

    let mut inner_fields = vec![];
    let mut from_fields = vec![];

    for field in fields_named.named {
        let ident = field.ident.clone().unwrap();
        let ty = field.ty;
        let mut is_lenient = false;
        let mut is_optional = false;

        for attr in &field.attrs {
            if attr.path().is_ident("lenient") {
                is_lenient = true;
            } else if attr.path().is_ident("optional") {
                is_optional = true;
            }
        }

        let wrapped_ty = if is_optional {
            quote! { ::lenient::Optional<#ty> }
        } else if is_lenient {
            quote! { ::lenient::Lenient<#ty> }
        } else {
            quote! { #ty }
        };

        let serde_default = if is_lenient || is_optional {
            quote! { #[serde(default)] }
        } else {
            quote! {}
        };

        let unwrap_code = if is_optional {
            quote! { #ident: inner.#ident.0.unwrap_or_default() }
        } else if is_lenient {
            quote! { #ident: inner.#ident.0 }
        } else {
            quote! { #ident: inner.#ident }
        };

        inner_fields.push(quote! {
            #serde_default
            #ident: #wrapped_ty
        });

        from_fields.push(unwrap_code);
    }

    let expanded = quote! {
        #[derive(::serde::Deserialize)]
        struct #inner_name {
            #(#inner_fields,)*
        }

        impl<'de> ::serde::Deserialize<'de> for #struct_name {
            fn deserialize<D>(de: D) -> Result<Self, D::Error>
            where
                D: ::serde::Deserializer<'de>,
            {
                let inner = #inner_name::deserialize(de)?;
                Ok(Self {
                    #(#from_fields,)*
                })
            }
        }
    };

    TokenStream::from(expanded)
}