builder-pattern-macro 0.4.2

A macro crate for builder-pattern. Do not use this crate directly.
Documentation
use crate::{
    attributes::{FieldVisibility, Setters},
    field::Field,
    struct_input::StructInput,
};

use core::str::FromStr;
use proc_macro2::{Ident, Span, TokenStream};
use quote::ToTokens;
use syn::{parse_quote, spanned::Spanned, Attribute};

pub struct BuilderFunctions<'a> {
    pub input: &'a StructInput,
}

impl<'a> ToTokens for BuilderFunctions<'a> {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let all_builder_fields = self
            .input
            .required_fields
            .iter()
            .chain(self.input.optional_fields.iter())
            .map(|f| {
                let ident = &f.ident;
                quote! { #ident: self.#ident }
            })
            .collect::<Vec<_>>();

        let mut index = 0;
        self.input
            .required_fields
            .iter()
            .chain(self.input.optional_fields.iter())
            .for_each(move |f| {
                if f.attrs.vis == FieldVisibility::Hidden {
                    index += 1;
                    return;
                }
                let mut builder_fields = all_builder_fields.clone();

                if !(f.attrs.setters & Setters::VALUE).is_empty() {
                    self.write_value_setter(tokens, f, index, &mut builder_fields);
                }
                if !(f.attrs.setters & Setters::LAZY).is_empty() {
                    self.write_lazy_setter(tokens, f, index, &mut builder_fields);
                }
                if !(f.attrs.setters & Setters::ASYNC).is_empty() {
                    self.write_async_setter(tokens, f, index, &mut builder_fields);
                }
                index += 1;
            });
    }
}

impl<'a> BuilderFunctions<'a> {
    pub fn new(input: &'a StructInput) -> Self {
        Self { input }
    }

    fn documents(f: &Field, _setter: Setters) -> Vec<Attribute> {
        let mut docs: Vec<Attribute> = Vec::new();

        let default = match f.attrs.default.as_ref() {
            Some((expr, _)) => format!("\n - Default: `{}`", expr.into_token_stream().to_string()),
            None => String::from(""),
        };
        let doc = format!(
            " # {}\n - Type: `{}`{}\n\n",
            f.ident,
            f.type_documents(),
            default
        );
        docs.push(parse_quote!(#[doc=#doc]));

        docs.append(f.documents().as_mut());

        docs
    }

    fn get_generics(
        &self,
        f: &Field,
        index: usize,
    ) -> (Vec<TokenStream>, Vec<TokenStream>, Vec<TokenStream>) {
        let all_generics = self.input.all_generics().collect::<Vec<_>>();
        let ty = &f.ty;
        let mut other_generics = all_generics.clone();
        other_generics.remove(index);
        let mut before_generics = all_generics.clone();
        before_generics[index] = TokenStream::from_str("()").unwrap();
        let mut after_generics = all_generics;
        after_generics[index] = quote! {#ty};

        (other_generics, before_generics, after_generics)
    }

    fn write_value_setter(
        &self,
        tokens: &mut TokenStream,
        f: &Field,
        index: usize,
        builder_fields: &mut Vec<TokenStream>,
    ) {
        let (ident, ty, vis) = (&f.ident, &f.ty, &f.vis);
        let builder_name = self.input.builder_name();
        let where_clause = &self.input.generics.where_clause;
        let lifetimes = self.input.lifetimes();
        let fn_lifetime = self.input.fn_lifetime();
        let impl_tokens = self.input.tokenize_impl();
        let ty_tokens = self.input.tokenize_types();
        let (other_generics, before_generics, after_generics) = self.get_generics(f, index);
        let (arg_type_gen, arg_type) = if f.attrs.use_into {
            (
                Some(quote! {<IntoType: Into<#ty>>}),
                TokenStream::from_str("IntoType").unwrap(),
            )
        } else {
            (None, quote! {#ty})
        };
        let documents = Self::documents(f, Setters::VALUE);

        let (ret_type, ret_expr) = match &f.attrs.validator {
            Some(v) => {
                builder_fields[index] = quote! {
                    #ident: Some(
                        ::builder_pattern::setter::Setter::Value(value)
                    )
                };
                (
                    quote! {
                        Result<#builder_name <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#after_generics,)* AsyncFieldMarker, ValidatorOption>, String>
                    },
                    quote_spanned! { v.span() =>
                        #[allow(clippy::useless_conversion)]
                        match #v (value.into()) {
                            Ok(value) => Ok(
                                #builder_name {
                                    _phantom: ::core::marker::PhantomData,
                                    #(#builder_fields),*
                                }),
                            Err(e) => Err(format!("Validation failed: {:?}", e))
                        }
                    },
                )
            }
            None => {
                builder_fields[index] = quote! {
                    #ident: Some(
                        ::builder_pattern::setter::Setter::Value(value.into())
                    )
                };
                (
                    quote! {
                        #builder_name <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#after_generics,)* AsyncFieldMarker, ValidatorOption>
                    },
                    quote! {
                        #builder_name {
                            _phantom: ::core::marker::PhantomData,
                            #(#builder_fields),*
                        }
                    },
                )
            }
        };

        tokens.extend(quote! {
            impl <#fn_lifetime, #impl_tokens #(#other_generics,)* AsyncFieldMarker, ValidatorOption> #builder_name <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#before_generics,)* AsyncFieldMarker, ValidatorOption>
                #where_clause
            {
                #(#documents)*
                #vis fn #ident #arg_type_gen(self, value: #arg_type) -> #ret_type {
                    #ret_expr
                }
            }
        });
    }

    fn write_lazy_setter(
        &self,
        tokens: &mut TokenStream,
        f: &Field,
        index: usize,
        builder_fields: &mut Vec<TokenStream>,
    ) {
        let (ident, ty, vis) = (&f.ident, &f.ty, &f.vis);
        let seter_name = Ident::new(&format!("{}_lazy", &ident.to_string()), Span::call_site());
        let builder_name = self.input.builder_name();
        let where_clause = &self.input.generics.where_clause;
        let lifetimes = self.input.lifetimes();
        let fn_lifetime = self.input.fn_lifetime();
        let impl_tokens = self.input.tokenize_impl();
        let ty_tokens = self.input.tokenize_types();
        let (other_generics, before_generics, after_generics) = self.get_generics(f, index);
        let arg_type_gen = if f.attrs.use_into {
            quote! {<IntoType: Into<#ty>, ValType: #fn_lifetime + ::core::ops::Fn() -> IntoType>}
        } else {
            quote! {<ValType: #fn_lifetime + ::core::ops::Fn() -> #ty>}
        };
        let arg_type = quote! {ValType};
        let documents = Self::documents(f, Setters::VALUE);

        builder_fields[index] = match &f.attrs.validator {
            Some(v) => quote_spanned! { v.span() =>
                #ident: Some(
                    ::builder_pattern::setter::Setter::LazyValidated(
                        Box::new(move || #v((value)().into()))
                    )
                )
            },
            None => quote! {
                #ident: Some(
                    ::builder_pattern::setter::Setter::Lazy(
                        Box::new(move || (value)().into())
                    )
                )
            },
        };
        let ret_expr_val = quote! {
            #builder_name {
                _phantom: ::core::marker::PhantomData,
                #(#builder_fields),*
            }
        };

        let validator_option = if f.attrs.validator.is_some() {
            quote! {::builder_pattern::setter::HavingLazyValidator}
        } else {
            quote! {ValidatorOption}
        };

        let ret_type = quote! {
            #builder_name <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#after_generics,)* AsyncFieldMarker, #validator_option>
        };

        tokens.extend(quote! {
            impl <#fn_lifetime, #impl_tokens #(#other_generics,)* AsyncFieldMarker, ValidatorOption> #builder_name <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#before_generics,)* AsyncFieldMarker, ValidatorOption>
                #where_clause
            {
                #(#documents)*
                #vis fn #seter_name #arg_type_gen(self, value: #arg_type) -> #ret_type {
                    #[allow(useless_conversion)]
                    #ret_expr_val
                }
            }
        });
    }

    fn write_async_setter(
        &self,
        tokens: &mut TokenStream,
        f: &Field,
        index: usize,
        builder_fields: &mut Vec<TokenStream>,
    ) {
        let (ident, ty, vis) = (&f.ident, &f.ty, &f.vis);
        let seter_name = Ident::new(&format!("{}_async", &ident.to_string()), Span::call_site());
        let builder_name = self.input.builder_name();
        let where_clause = &self.input.generics.where_clause;
        let lifetimes = self.input.lifetimes();
        let fn_lifetime = self.input.fn_lifetime();
        let impl_tokens = self.input.tokenize_impl();
        let ty_tokens = self.input.tokenize_types();
        let (other_generics, before_generics, after_generics) = self.get_generics(f, index);
        let arg_type_gen = if f.attrs.use_into {
            quote! {<
                IntoType: Into<#ty>,
                ReturnType: #fn_lifetime + ::core::future::Future<Output = IntoType>,
                ValType: #fn_lifetime + ::core::ops::Fn() -> ReturnType
            >}
        } else {
            quote! {<
                ReturnType: #fn_lifetime + ::core::future::Future<Output = #ty>,
                ValType: #fn_lifetime + ::core::ops::Fn() -> ReturnType
            >}
        };
        let arg_type = quote! {ValType};
        let documents = Self::documents(f, Setters::VALUE);

        builder_fields[index] = match &f.attrs.validator {
            Some(v) => quote_spanned! { v.span() =>
                #ident: Some(
                    ::builder_pattern::setter::Setter::AsyncValidated(
                        Box::new(move || {
                            Box::pin(async move { #v((value)().await.into()) })
                        })
                    )
                )
            },
            None => quote! {
                #ident: Some(
                    ::builder_pattern::setter::Setter::Async(
                        Box::new(move || Box::pin(async move { (value)().await.into() }))
                    )
                )
            },
        };
        let ret_expr_val = quote! {
            #builder_name {
                _phantom: ::core::marker::PhantomData,
                #(#builder_fields),*
            }
        };

        let validator_option = if f.attrs.validator.is_some() {
            quote! {::builder_pattern::setter::HavingLazyValidator}
        } else {
            quote! {ValidatorOption}
        };

        let ret_type = quote! {
            #builder_name <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#after_generics,)* ::builder_pattern::setter::AsyncBuilderMarker, #validator_option>
        };

        tokens.extend(quote! {
            impl <#fn_lifetime, #impl_tokens #(#other_generics,)* AsyncFieldMarker, ValidatorOption> #builder_name <#fn_lifetime, #(#lifetimes,)* #ty_tokens #(#before_generics,)* AsyncFieldMarker, ValidatorOption>
                #where_clause
            {
                #(#documents)*
                #vis fn #seter_name #arg_type_gen(self, value: #arg_type) -> #ret_type {
                    #[allow(useless_conversion)]
                    #ret_expr_val
                }
            }
        });
    }
}