struct-builder 0.3.0

Derive a builder for your structs
Documentation
use crate::struct_builder::{BuilderContext, GenericsContext};
use proc_macro2::TokenStream;
use quote::ToTokens;
use syn::{parse_quote, Fields, ItemImpl, ItemStruct};

pub struct ImplFromParamsForSubject {
    ctx: BuilderContext,
    unit: bool
}

impl From<&ItemStruct> for ImplFromParamsForSubject {
    fn from(value: &ItemStruct) -> Self {
        let ctx: BuilderContext = value.into();
        let unit = matches!(&value.fields, Fields::Unit);

        Self { ctx, unit }
    }
}

impl ToTokens for ImplFromParamsForSubject {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let BuilderContext {
            subject,
            params,
            generics,
            fields_metadata,
            ..
        } = &self.ctx;
        let GenericsContext {
            generics_def,
            generics_expr,
            where_clause
        } = &generics;
        
        let include_params_generics = fields_metadata.generic_required_fields_count > 0;

        if !self.unit {
            let item_impl: ItemImpl = if include_params_generics {
                parse_quote! {
                    impl #generics_def From<#params #generics_expr> for #subject #generics_expr #where_clause {
                        fn from(value: #params #generics_expr) -> Self {
                            Self::builder(value).build()
                        }
                    }
                }
            } else {
                parse_quote! {
                    impl #generics_def From<#params> for #subject #generics_expr #where_clause {
                        fn from(value: #params) -> Self {
                            Self::builder(value).build()
                        }
                    }
                }               
            };

            item_impl.to_tokens(tokens);
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::components::ImplFromParamsForSubject;
    use crate::test_util::{sample_named_item_struct, sample_unit_item_struct, sample_unnamed_item_struct};
    use proc_macro2::TokenStream;
    use quote::ToTokens;
    use syn::{parse_quote, ItemImpl};

    #[test]
    fn test_with_named_fields() {
        let item_struct = sample_named_item_struct();
        let expected: ItemImpl = parse_quote! {
            impl<T, I: Send, W> From<MyStructParams<T, I, W>> for MyStruct<T, I, W>
            where
                W: Sync
            {
                fn from(value: MyStructParams<T, I, W>) -> Self {
                    Self::builder(value).build()
                }
            }
        };
        
        let impl_from_params_for_subject = ImplFromParamsForSubject::from(&item_struct);

        assert_eq!(
            impl_from_params_for_subject.to_token_stream().to_string(),
            expected.to_token_stream().to_string()
        );
    }
    
    #[test]
    fn test_with_unnamed_fields() {
        let item_struct = sample_unnamed_item_struct();
        let expected: ItemImpl = parse_quote! {
            impl<T, I: Send, W> From<MyStructParams<T, I, W>> for MyStruct<T, I, W>
            where
                W: Sync
            {
                fn from(value: MyStructParams<T, I, W>) -> Self {
                    Self::builder(value).build()
                }
            }
        };

        let impl_from_params_for_subject = ImplFromParamsForSubject::from(&item_struct);

        assert_eq!(
            impl_from_params_for_subject.to_token_stream().to_string(),
            expected.to_token_stream().to_string()
        );
    }
    
    #[test]
    fn test_with_unit_struct() {
        let item_struct = sample_unit_item_struct();

        let subject_impl = ImplFromParamsForSubject::from(&item_struct);

        assert_eq!(
            subject_impl.to_token_stream().to_string(),
            TokenStream::new().to_string()
        );
    }
}