field-meta 0.1.1

A procedural macro to derive metadata for struct fields
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;

pub fn field_meta_derive(input: TokenStream) -> TokenStream {
    let input = syn::parse2::<DeriveInput>(input).unwrap();
    let name = &input.ident;

    let fields_info = match input.data {
        syn::Data::Struct(data) => match data.fields {
            syn::Fields::Named(fields) => fields
                .named
                .iter()
                .map(|f| {
                    let name = f.ident.as_ref().unwrap().to_string();
                    let ty = f.ty.clone();
                    let attrs = FieldAttrs::from_attrs(&f.attrs);
                    FieldInfo { name, ty, attrs }
                })
                .collect::<Vec<_>>(),
            _ => panic!("#[derive(FieldMeta)] is only defined for structs with named fields."),
        },
        _ => panic!("#[derive(FieldMeta)] is only defined for structs."),
    };

    let field_names = fields_info
        .iter()
        .filter(|f| !f.attrs.skip)
        .map(|f| &f.name)
        .collect::<Vec<_>>();

    let mut aliases = std::collections::HashMap::new();
    for f in &fields_info {
        if let Some(alias) = &f.attrs.alias {
            if aliases.contains_key(alias) {
                panic!("Duplicate alias '{alias}' found in struct '{name}'");
            }
            aliases.insert(alias.clone(), f.name.clone());
        }
    }

    let field_count = field_names.len();
    let aliases = aliases
        .iter()
        .map(|(alias, original)| {
            quote! {
                (#alias, #original)
            }
        })
        .collect::<Vec<_>>();
    let alisas_count = aliases.len();

    quote! {
        impl #name {
            const FIELDS: [&'static str; #field_count] = [#(#field_names),*];
            const FIELD_ALIASES: [(&'static str, &'static str); #alisas_count] = [#(#aliases),*];


            pub fn fields() -> &'static [&'static str] {
                &Self::FIELDS
            }

            pub fn contains_field(field: &str) -> bool {
                if Self::FIELDS.contains(&field) {
                    true
                } else {
                    Self::FIELD_ALIASES.iter().any(|(alias, _)| alias == &field)
                }
            }

            pub fn field_count() -> usize {
                #field_count
            }
        }
    }
}

#[allow(dead_code)]
#[derive(Debug, Clone)]
struct FieldInfo {
    name: String,
    ty: syn::Type,
    attrs: FieldAttrs,
}

#[derive(Debug, Clone, Default)]
struct FieldAttrs {
    skip: bool,
    alias: Option<String>,
}

impl FieldAttrs {
    fn from_attrs(attrs: &[syn::Attribute]) -> Self {
        let mut ret = Self::default();
        for attr in attrs {
            if attr.path().is_ident("field_meta") {
                let _ = attr.parse_nested_meta(|meta| {
                    if meta.path.is_ident("skip") {
                        ret.skip = true;
                    }
                    if meta.path.is_ident("alias") {
                        let value: syn::LitStr = meta.value()?.parse()?;
                        ret.alias = Some(value.value());
                    }
                    Ok(())
                });
            }
        }
        ret
    }
}