btmgmt-packet-macros 0.2.2

btmgmt-packet helper macros.
Documentation
use proc_macro2::TokenStream;
use quote::quote;
use syn::{Data, DataEnum, DataStruct, DeriveInput, Fields, Ident};

fn derive_unit(item: &DeriveInput, _: &DataStruct) -> syn::Result<TokenStream> {
    let ident = &item.ident;

    let code = quote! {
        impl ::btmgmt_packet_helper::pack::Unpack for #ident {
            fn unpack<R>(_: &mut R) -> ::btmgmt_packet_helper::pack::Result<Self> where R: ::std::io::Read {
                Ok(Self)
            }
        }
    };
    Ok(code)
}

fn derive_tuple(item: &DeriveInput, data: &DataStruct) -> syn::Result<TokenStream> {
    let ident = &item.ident;
    let fields = (0..data.fields.len())
        .map(|_| TokenStream::new())
        .collect::<Vec<_>>();
    let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();

    let code = quote! {
        impl #impl_generics ::btmgmt_packet_helper::pack::Unpack for #ident #type_generics #where_clause {
            fn unpack<R>(read: &mut R) -> ::btmgmt_packet_helper::pack::Result<Self> where R: ::std::io::Read {
                Ok(Self(
                    #( #fields ::btmgmt_packet_helper::pack::Unpack::unpack(read)?, )*
                ))
            }
        }
    };
    Ok(code)
}

fn derive_standard(item: &DeriveInput, data: &DataStruct) -> syn::Result<TokenStream> {
    let ident = &item.ident;
    let fields = data
        .fields
        .iter()
        .map(|f| f.ident.as_ref().unwrap())
        .collect::<Vec<_>>();
    let (impl_generics, type_generics, where_clause) = item.generics.split_for_impl();

    let code = quote! {
        impl #impl_generics ::btmgmt_packet_helper::pack::Unpack for #ident #type_generics #where_clause {
            fn unpack<R>(read: &mut R) -> ::btmgmt_packet_helper::pack::Result<Self> where R: ::std::io::Read {
                Ok(Self {
                    #( #fields: ::btmgmt_packet_helper::pack::Unpack::unpack(read)?, )*
                })
            }
        }
    };
    Ok(code)
}

fn derive_enum(item: &DeriveInput, data: &DataEnum) -> syn::Result<TokenStream> {
    let mut ty = None;
    for attr in &item.attrs {
        if attr.path.is_ident("pack") {
            ty = Some(attr.parse_args::<Ident>()?);
        }
    }
    let ty = if let Some(ty) = ty {
        ty
    } else {
        return Err(syn::Error::new_spanned(item, "no `pack` specified."));
    };

    let ident = &item.ident;
    let fields = data
        .variants
        .iter()
        .map(|v| {
            if let Fields::Unit = v.fields {
                Ok(&v.ident)
            } else {
                Err(syn::Error::new_spanned(v, "supports unit variant only."))
            }
        })
        .collect::<syn::Result<Vec<_>>>()?;

    let code = quote! {
        impl ::btmgmt_packet_helper::pack::Unpack for #ident {
            fn unpack<R>(read: &mut R) -> ::btmgmt_packet_helper::pack::Result<Self> where R: ::std::io::Read {
                #![allow(non_upper_case_globals)]
                #( const #fields: #ty = #ident::#fields as #ty; )*
                Ok(match #ty::unpack(read)? {
                    #( #fields => Self::#fields, )*
                    unknown => return Err(::btmgmt_packet_helper::pack::Error::UnexpectedValue(format!("{}", unknown))),
                })
            }
        }
    };
    Ok(code)
}

fn derive(input: TokenStream) -> syn::Result<TokenStream> {
    let input = syn::parse2::<DeriveInput>(input)?;
    match &input.data {
        Data::Struct(
            data
            @ DataStruct {
                fields: Fields::Unit,
                ..
            },
        ) => derive_unit(&input, data),
        Data::Struct(
            data
            @
            DataStruct {
                fields: Fields::Unnamed(..),
                ..
            },
        ) => derive_tuple(&input, data),
        Data::Struct(
            data
            @
            DataStruct {
                fields: Fields::Named(..),
                ..
            },
        ) => derive_standard(&input, data),
        Data::Enum(data) => derive_enum(&input, data),
        _ => todo!(),
    }
}

pub fn unpack(input: TokenStream) -> TokenStream {
    match derive(input) {
        Ok(tokens) => tokens,
        Err(err) => err.to_compile_error(),
    }
}