lookupvec_derive 0.1.0

Derive macro for LookupVec items
Documentation
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Data, Fields, Type, Error};

#[proc_macro_derive(Lookup, attributes(lookup_key))]
pub fn derive_lookup(input: TokenStream) -> TokenStream {
    let input = parse_macro_input!(input as DeriveInput);
    let name = input.ident;

    match validate_and_get_key_field(&input.data) {
        Ok((key_field, key_type)) => {
            let expanded = quote! {
                impl Lookup for #name {
                    type Key = #key_type;
                    
                    fn key(&self) -> Self::Key {
                        self.#key_field.clone()
                    }
                }
            };
            TokenStream::from(expanded)
        }
        Err(err) => err.to_compile_error().into(),
    }
}

fn validate_and_get_key_field(data: &Data) -> Result<(syn::Ident, Type), Error> {
    match data {
        Data::Struct(data_struct) => {
            match &data_struct.fields {
                Fields::Named(fields) => {
                    let key_fields: Vec<_> = fields.named.iter()
                        .filter(|field| {
                            field.attrs.iter()
                                .any(|attr| attr.path().is_ident("lookup_key"))
                        })
                        .collect();

                    match key_fields.len() {
                        0 => Err(Error::new_spanned(
                            fields,
                            "struct must have exactly one field marked with #[lookup_key]"
                        )),
                        1 => Ok((
                            key_fields[0].ident.clone().unwrap(),
                            key_fields[0].ty.clone()
                        )),
                        _ => Err(Error::new_spanned(
                            fields,
                            "multiple #[lookup_key] attributes found, only one is allowed"
                        )),
                    }
                },
                _ => Err(Error::new_spanned(
                    data_struct.struct_token,
                    "only named fields are supported"
                )),
            }
        },
        Data::Enum(data_enum) => Err(Error::new_spanned(
            data_enum.enum_token,
            "Lookup can only be derived for structs"
        )),
        Data::Union(data_union) => Err(Error::new_spanned(
            data_union.union_token,
            "Lookup can only be derived for structs"
        )),
    }
}