genies_derive 1.8.0

A powerful derive macro for configuration management in Rust
Documentation
/*
 * @Author: tzw
 * @Date: 2021-10-31 01:41:53
 * @LastEditors: tzw
 * @LastEditTime: 2022-01-16 03:14:29
 */
use crate::helpers::{find_attribute, find_struct_field, list_field_idents};
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::quote;
use syn::spanned::Spanned;
use syn::{DataEnum, DataStruct, DataUnion, DeriveInput, Fields};

pub fn derive_aggregate_type_for_struct(
    ast: &DeriveInput,
    struct_data: &DataStruct,
) -> TokenStream {
    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
    let tname = &ast.ident;
    let aggtype = find_attribute("aggregate_type", &ast.attrs)
        .map(|attr| attr.tokens.clone())
        .unwrap_or_else(|| quote!(stringify!(#tname)));

    let mut output = quote! {
        #[allow(unused_qualifications, unused_parens)]
        #[automatically_derived]
        impl #impl_generics genies::ddd::aggregate::AggregateType for #tname #ty_generics #where_clause {
            fn aggregate_type(&self) ->String {
                #aggtype.to_string()
            }
            fn atype() ->String {
                #aggtype.to_string()
            }
        }
    };

    let maybe_init_attr = find_attribute("initialize_with_defaults", &ast.attrs);
    let maybe_id_field_name = find_attribute("id_field", &ast.attrs)
        .map(|attr| attr.parse_args::<Ident>().expect("field identifier"));

    if let Some(id_field_name) = &maybe_id_field_name {
        let maybe_id_field = find_struct_field(&id_field_name.to_string(), &struct_data.fields);

        if let Some(id_field) = maybe_id_field {
            let idfname = id_field.ident.as_ref().unwrap();
            let idftype = &id_field.ty;

            output.extend(quote! {
                #[allow(unused_qualifications, unused_parens)]
                #[automatically_derived]
                impl #impl_generics genies::ddd::aggregate::WithAggregateId for #tname #ty_generics #where_clause {
                    type Id = #idftype;
                    fn aggregate_id(&self) -> &Self::Id {
                        &self.#idfname
                    }
                }
            });

            if let Some(_init_attr) = maybe_init_attr {
                match &struct_data.fields {
                    Fields::Named(named_fields) => {
                        let fldnames =
                            list_field_idents(named_fields).filter(|&ident| ident != idfname);

                        output.extend(quote! {
                            #[allow(unused_qualifications, unused_parens)]
                            #[automatically_derived]
                            impl #impl_generics genies::ddd::aggregate::InitializeAggregate for #tname #ty_generics #where_clause {
                                type State = Self;
                                fn initialize(aggregate_id: #idftype) -> Self::State {
                                    Self {
                                        #idfname: aggregate_id,
                                        #(#fldnames: Default::default(),)*
                                    }
                                }
                            }
                        });
                    }
                    Fields::Unnamed(_) => panic!("tuple structs not supported"),
                    Fields::Unit => panic!("unit structs not supported"),
                };
            }
        } else {
            output.extend(
                syn::Error::new(
                    id_field_name.span(),
                    format!("no field {} on struct {}", id_field_name, tname),
                )
                .to_compile_error(),
            );
        }
    } else if let Some(init_attr) = maybe_init_attr {
        output.extend(syn::Error::new(
            init_attr.span(),
            "missing id_field attribute! initialize_with_defaults attribute only allowed in combination with the id_field attribute"
        ).to_compile_error());
    }

    output.into()
}

pub fn derive_aggregate_type_for_enum(_ast: &DeriveInput, _enum_data: &DataEnum) -> TokenStream {
    panic!("#[derive(Aggregate)] is only defined for struct types, but not enum or union types")
}

pub fn derive_aggregate_type_for_union(_ast: &DeriveInput, _union_data: &DataUnion) -> TokenStream {
    panic!("#[derive(Aggregate)] is only defined for struct types, but not enum or union types")
}