serdev_derive 0.3.2

proc macros for SerdeV - Serde with Validation
Documentation
use proc_macro2::{Span, TokenStream};
use quote::{ToTokens, format_ident, quote};
use syn::{Attribute, Error, Fields, Generics, Ident, Item, ItemEnum, ItemStruct, parse::Parse};

#[derive(Clone)]
pub(crate) enum Target {
    Enum(ItemEnum),
    Struct(ItemStruct),
}

impl Parse for Target {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        match input.parse::<Item>()? {
            Item::Enum(e) => Ok(Self::Enum(e)),
            Item::Struct(s) => Ok(Self::Struct(s)),
            _ => Err(Error::new(Span::call_site(), "")),
        }
    }
}

impl ToTokens for Target {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        match self {
            Self::Enum(e) => e.to_tokens(tokens),
            Self::Struct(s) => s.to_tokens(tokens),
        }
    }
}

impl Target {
    pub(crate) fn generics(&self) -> &Generics {
        match self {
            Self::Enum(e) => &e.generics,
            Self::Struct(s) => &s.generics,
        }
    }

    pub(crate) fn attrs(&self) -> &[Attribute] {
        match self {
            Self::Enum(e) => &e.attrs,
            Self::Struct(s) => &s.attrs,
        }
    }
    pub(crate) fn attrs_mut(&mut self) -> &mut Vec<Attribute> {
        match self {
            Self::Enum(e) => &mut e.attrs,
            Self::Struct(s) => &mut s.attrs,
        }
    }

    pub(crate) fn ident(&self) -> &Ident {
        match self {
            Self::Enum(e) => &e.ident,
            Self::Struct(s) => &s.ident,
        }
    }
    pub(crate) fn ident_mut(&mut self) -> &mut Ident {
        match self {
            Self::Enum(e) => &mut e.ident,
            Self::Struct(s) => &mut s.ident,
        }
    }

    pub(crate) fn create_proxy(&self, name: Ident) -> Self {
        let mut proxy = self.clone();

        *proxy.ident_mut() = name;

        *proxy.attrs_mut() = proxy
            .attrs()
            .iter()
            .filter(|a| a.path().get_ident().is_some_and(|i| i == "serde"))
            .cloned()
            .collect();
        match &mut proxy {
            Self::Struct(s) => {
                for field in &mut s.fields {
                    field.attrs = field
                        .attrs
                        .iter()
                        .filter(|a| a.path().get_ident().is_some_and(|i| i == "serde"))
                        .cloned()
                        .collect();
                }
            }
            Self::Enum(e) => {
                for variant in &mut e.variants {
                    variant.attrs = variant
                        .attrs
                        .iter()
                        .filter(|a| a.path().get_ident().is_some_and(|i| i == "serde"))
                        .cloned()
                        .collect();
                }
            }
        }

        proxy
    }

    pub(crate) fn transmute_expr(
        &self,
        variable_ident: &'static str,
        target_ident: &Ident,
    ) -> TokenStream {
        let var = Ident::new(variable_ident, Span::call_site());

        fn constructor(fields: &Fields) -> TokenStream {
            match fields {
                Fields::Unit => {
                    quote! {}
                }
                Fields::Unnamed(u) => {
                    let idents = (0..u.unnamed.len()).map(|i| format_ident!("field_{i}"));
                    quote! {
                        ( #(#idents),* )
                    }
                }
                Fields::Named(n) => {
                    let idents = n.named.iter().map(|f| f.ident.as_ref().unwrap());
                    quote! {
                        { #(#idents),* }
                    }
                }
            }
        }

        match self {
            Self::Struct(s) => {
                let ident = &s.ident;
                let constructor = constructor(&s.fields);
                quote! {{
                    let #ident #constructor = #var;
                    #target_ident #constructor
                }}
            }
            Self::Enum(e) => {
                let ident = &e.ident;

                let arms = e.variants.iter().map(|v| {
                    let variant = &v.ident;
                    let fields = constructor(&v.fields);
                    quote! {
                        #ident::#variant #fields => #target_ident::#variant #fields
                    }
                });

                quote! {
                    match #var {
                        #(#arms),*
                    }
                }
            }
        }
    }
}