armour-derive 0.27.3

DDL and serialization for key-value storage
Documentation
extern crate proc_macro2;
extern crate quote;
extern crate syn;

use proc_macro2::{Span, TokenStream};
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Field, Fields, Lit, LitInt, Meta};

// #[proc_macro_derive(IntoValue, attributes(idx, coming))]
pub fn derive_value(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ast = parse_macro_input!(stream as DeriveInput);
    let name = &ast.ident;
    let data = &ast.data;
    match data {
        Data::Struct(data_struct) => {
            let fields = &data_struct.fields;
            match fields {
                Fields::Named(fields) => {
                    let named = &fields.named;
                    let named_len = named.len();
                    let l = syn::Lit::Int(LitInt::new(&named_len.to_string(), Span::call_site()));

                    let mut fields_insert: Vec<(Field, u32)> = Vec::with_capacity(named_len);

                    let mut seq = 0u32;
                    for field in named.iter() {
                        let field_idx = field
                            .attrs
                            .iter()
                            .find_map(|a| {
                                a.path.segments.first().and_then(|segment| {
                                    if segment.ident != "idx" {
                                        return None;
                                    }
                                    match a.parse_meta() {
                                        Ok(Meta::List(list)) => {
                                            let a = list.nested.first().unwrap();
                                            let int: u32 = match a {
                                                NestedMeta::Lit(Lit::Int(i)) => {
                                                    i.base10_parse::<u32>().unwrap()
                                                }
                                                _ => {
                                                    panic!("error meta type")
                                                }
                                            };
                                            Some(int)
                                        }
                                        Ok(Meta::NameValue(nv)) => match nv.lit {
                                            Lit::Int(i) => Some(i.base10_parse::<u32>().unwrap()),
                                            _ => {
                                                panic!("error meta type")
                                            }
                                        },
                                        Ok(_) => None,
                                        Err(_) => None,
                                    }
                                })
                            })
                            .unwrap_or_else(|| {
                                let current_seq = seq;
                                seq += 1;
                                current_seq
                            });

                        fields_insert.push((field.clone(), field_idx));
                    }
                    fields_insert.sort_by(|(_, idx_a), (_, idx_b)| idx_a.cmp(idx_b));

                    let into_value_stream: Vec<TokenStream> = fields_insert
                        .iter()
                        .map(|(field, idx)| {
                            let ident = field.ident.as_ref().unwrap();

                            let idx =
                                syn::Lit::Int(LitInt::new(&idx.to_string(), Span::call_site()));

                            let gen = quote! {
                                let #ident: armour::Value = val.#ident.into();
                                vec.push(armour::dyn_types::value::ValueWithId(#idx, #ident));
                            };

                            gen
                        })
                        .collect();

                    let gen = quote! {
                        impl From<#name> for armour::Value {
                            fn from(val: #name) -> Self {
                                #[cfg(feature = "no_std")]
                                use alloc::vec::Vec;
                                let mut vec: Vec<armour::dyn_types::value::ValueWithId> = Vec::with_capacity(#l);
                                #(#into_value_stream)*
                                armour::Value::Tuple(armour::dyn_types::value::Tuple(vec))
                            }
                        }
                    };

                    proc_macro::TokenStream::from(gen)
                }
                Fields::Unnamed(fields) => {
                    let unnamed = &fields.unnamed;
                    let unnamed_len = unnamed.len();
                    let l = syn::Lit::Int(LitInt::new(&unnamed_len.to_string(), Span::call_site()));
                    let mut into_value_stream: Vec<TokenStream> = Vec::with_capacity(unnamed_len);

                    for (idx, _) in unnamed.iter().enumerate() {
                        let id = syn::Lit::Int(LitInt::new(&idx.to_string(), Span::call_site()));
                        let field_name = syn::Ident::new(&format!("arg{idx}"), Span::call_site());
                        let into = quote! {
                            let #field_name: armour::Value = val.#id.into();
                            vec.push(armour::dyn_types::value::ValueWithId(#id, #field_name));
                        };

                        into_value_stream.push(into);
                    }

                    let gen = quote! {
                        impl From<#name> for armour::Value {
                            fn from(val: #name) -> Self {
                                #[cfg(feature = "no_std")]
                                use alloc::vec::Vec;
                                let mut vec: Vec<armour::dyn_types::value::ValueWithId> = Vec::with_capacity(#l);
                                #(#into_value_stream)*
                                armour::Value::Tuple(armour::dyn_types::value::Tuple(vec))
                            }
                        }
                    };

                    proc_macro::TokenStream::from(gen)
                }
                Fields::Unit => {
                    let gen = quote! {
                        impl From<#name> for armour::Value {
                            fn from(val: #name) -> Self {
                                armour::Value::Void
                            }
                        }
                    };
                    proc_macro::TokenStream::from(gen)
                }
            }
        }
        Data::Enum(data_enum) => {
            let is_simple_enum = data_enum.variants.iter().all(|item| item.fields.is_empty());
            let variants_len = data_enum.variants.len();

            if is_simple_enum {
                let gen = quote! {
                    impl From<#name> for armour::Value {
                        fn from(val: #name) -> Self {
                            #[cfg(feature = "no_std")]
                            use alloc::boxed::Box;
                            let idx = val as u8;
                            armour::Value::Enum(armour::dyn_types::value::Enum((idx, Box::new(armour::Value::Void))))
                        }
                    }
                };

                proc_macro::TokenStream::from(gen)
            } else {
                let mut from_streams: Vec<TokenStream> = Vec::with_capacity(variants_len);

                for variant in &data_enum.variants {
                    if variant.discriminant.is_some() {
                        // why? because discriminant number may not be equal to primitive number
                        panic!("enums variants with discriminant not support in current moment");
                    }
                    let fields = &variant.fields;
                    let field = match fields {
                        Fields::Unit => None,
                        Fields::Unnamed(fields) => {
                            let len = fields.unnamed.len();
                            if len != 1 {
                                panic!("enums variants is currently support only with 1 unnamed fields");
                            }
                            let field = fields.unnamed.first().unwrap();
                            Some(field.clone())
                        }
                        Fields::Named(_) => {
                            panic!("enums named variants is currently not support");
                        }
                    };
                    let variant_name = &variant.ident;

                    let from_stream = match &field {
                        Some(_) => {
                            quote! {
                                #name::#variant_name(v) => {
                                    v.into()
                                }
                            }
                        }
                        None => {
                            quote! {
                                #name::#variant_name => {
                                    armour::Value::Void
                                }
                            }
                        }
                    };
                    from_streams.push(from_stream);
                }

                let gen = quote! {
                    impl From<#name> for armour::Value {
                        fn from(val: #name) -> Self {
                            #[cfg(feature = "no_std")]
                            use alloc::boxed::Box;
                            let idx = val.get_primitive_enum() as u8;
                            let value: armour::Value = match val {
                                #(#from_streams)*
                            };
                            armour::Value::Enum(armour::dyn_types::value::Enum((idx, Box::new(value))))
                        }
                    }
                };

                proc_macro::TokenStream::from(gen)
            }
        }
        Data::Union(_) => {
            panic!("unions not supported, but Rust enums is implemented GetType trait (use Enums instead)")
        }
    }
}