armour-derive 0.28.0

DDL and serialization for key-value storage
Documentation
use darling::FromField;
use proc_macro2::TokenStream;
use quote::quote;
use syn::{DataStruct, Fields, Ident};

use crate::{
    CustomField, FieldAttr,
    rapira_field_attrs::{extract_idx_attr, has_rapira_with_attr, skip_attr},
};

pub fn parse(data: &DataStruct, name: Ident, path: &TokenStream) -> TokenStream {
    let string_name = name.to_string();

    match &data.fields {
        Fields::Named(fields) => {
            let named = &fields.named;

            let mut entries: Vec<(u32, TokenStream)> = Vec::new();
            let mut seq = 0u32;

            for field in named {
                if skip_attr(&field.attrs) {
                    continue;
                }

                let attr = match FieldAttr::from_field(field) {
                    Ok(attr) => attr,
                    Err(err) => return err.write_errors(),
                };

                if has_rapira_with_attr(&field.attrs) && attr.with_type.is_none() {
                    return syn::Error::new_spanned(
                        field,
                        "#[rapira(with = ..)] field requires #[get_type(with_type = T)] under schema-aware GetType",
                    )
                    .to_compile_error();
                }

                let mut attr_count = 0u8;
                if attr.with_type.is_some() {
                    attr_count += 1;
                }
                if attr.unimplemented.is_present() {
                    attr_count += 1;
                }
                if attr.custom.is_some() {
                    attr_count += 1;
                }
                if attr_count > 1 {
                    return syn::Error::new_spanned(
                        field,
                        "only one of `with_type`, `unimplemented`, or `custom` may be set per field",
                    )
                    .to_compile_error();
                }

                let ident = field.ident.as_ref().unwrap().to_string();
                let ty = &field.ty;

                let typ;
                if let Some(with_type) = &attr.with_type {
                    typ = quote! {
                        <#with_type as #path::GetType>::TYPE
                    };
                } else if attr.unimplemented.is_present() {
                    typ = quote! {
                        #path::Typ::Custom("unimplemented", &[])
                    };
                } else if let Some(CustomField { name, types }) = &attr.custom {
                    typ = quote! {
                        #path::Typ::Custom(#name, #types)
                    };
                } else {
                    typ = quote! {
                        <#ty as #path::GetType>::TYPE
                    };
                }

                let field_idx = extract_idx_attr(&field.attrs).unwrap_or_else(|| {
                    let current = seq;
                    seq += 1;
                    current
                });

                entries.push((
                    field_idx,
                    quote! {
                        (#ident, #typ),
                    },
                ));
            }

            entries.sort_by_key(|(idx, _)| *idx);
            let fields_stream: Vec<TokenStream> = entries.into_iter().map(|(_, ts)| ts).collect();

            quote! {
                impl #path::GetType for #name {
                    const TYPE: #path::Typ = {
                        let fields = &[
                            #(#fields_stream)*
                        ];

                        #path::Typ::Struct(#path::StructType {
                            name: #string_name,
                            fields: #path::Fields::Named(fields),
                        })
                    };
                }
            }
        }
        Fields::Unnamed(fields) => {
            let unnamed = &fields.unnamed;

            if unnamed.len() == 1 {
                let field = &unnamed[0];
                let attr = match FieldAttr::from_field(field) {
                    Ok(attr) => attr,
                    Err(err) => return err.write_errors(),
                };
                let is_flatten = attr.flatten.is_present();
                if is_flatten {
                    let typ = &field.ty;

                    return quote! {
                        impl #path::GetType for #name {
                            const TYPE: #path::Typ = <#typ as #path::GetType>::TYPE;
                        }
                    };
                }
            }

            let fields_stream: Vec<TokenStream> = unnamed
                .iter()
                .map(|field| {
                    let typ = &field.ty;

                    quote! {
                        <#typ as #path::GetType>::TYPE,
                    }
                })
                .collect();

            quote! {
                impl #path::GetType for #name {
                    const TYPE: #path::Typ = {
                        let fields = &[
                            #(#fields_stream)*
                        ];

                        #path::Typ::Struct(#path::StructType {
                            name: #string_name,
                            fields: #path::Fields::Unnamed(fields),
                        })
                    };
                }
            }
        }
        Fields::Unit => quote! {
            impl #path::GetType for #name {
                const TYPE: #path::Typ = #path::Typ::Scalar(#path::ScalarTyp::Void);
            }
        },
    }
}