1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
use proc_macro::TokenStream;
use syn::{punctuated::Punctuated, token::Comma, DeriveInput, Field};

/// Implement the builder pattern for the target struct
#[proc_macro_derive(Builder)]
pub fn builder_derive_macro(item: TokenStream) -> TokenStream {
    // parse
    let ast: DeriveInput = syn::parse(item).unwrap();

    // generate
    BuilderBuilder::new(ast).build_builder()
}

struct BuilderBuilder {
    /// The AST of the struct we're deriving the builder for
    ast: DeriveInput,
}

impl BuilderBuilder {
    pub fn new(ast: DeriveInput) -> Self {
        Self { ast }
    }

    fn ident(&self) -> &syn::Ident {
        &self.ast.ident
    }

    fn builder_name(&self) -> syn::Ident {
        quote::format_ident!("{}Builder", self.ident())
    }

    fn builder_fields(&self) -> Vec<proc_macro2::TokenStream> {
        self.target_fields()
            .iter()
            .map(|field| {
                let field_name = field.ident.as_ref().expect("Expected field name");
                let field_type = &field.ty;
                quote::quote!(
                    #field_name: Option::<#field_type>,
                )
                .into()
            })
            .collect::<Vec<_>>()
    }

    fn builder_field_values(&self) -> Vec<proc_macro2::TokenStream> {
        self.target_fields()
            .iter()
            .map(|field| {
                let field_name = field.ident.as_ref().expect("Expected field name");
                quote::quote!(
                    #field_name: None,
                )
            })
            .collect()
    }

    fn builder_methods(&self) -> Vec<proc_macro2::TokenStream> {
        self.target_fields()
            .iter()
            .map(|field| {
                let field_name = field.ident.as_ref().expect("Expected field name");
                let field_type = &field.ty;
                quote::quote!(
                    fn #field_name(&mut self, value: #field_type) -> &mut Self {
                        self.#field_name = Some(value);
                        self
                    }
                )
            })
            .collect()
    }

    fn builder_build_checks(&self) -> Vec<proc_macro2::TokenStream> {
        self.target_fields().iter().map(|field| {
            let field_name = field.ident.as_ref().expect("Expected field name");
            quote::quote!(
                if self.#field_name.is_none() {
                    return Err(format!("Expected field to be set: {}", stringify!(#field_name).to_string()));
                }
            )
        }).collect::<Vec<_>>()
    }

    fn target_fields(&self) -> &Punctuated<Field, Comma> {
        match self.ast.data {
            syn::Data::Struct(syn::DataStruct {
                fields: syn::Fields::Named(syn::FieldsNamed { ref named, .. }),
                ..
            }) => named,
            _ => panic!("Builder macro only supports structs with named fields"),
        }
    }

    fn target_field_values(&self) -> Vec<proc_macro2::TokenStream> {
        self.target_fields().iter().map(|field| {
            let field_name = field.ident.as_ref().expect("Expected field name");
            quote::quote!(
                #field_name: self.#field_name.clone().expect(format!("Expected field to be set: {}", stringify!(#field_name).to_string()).as_str()),
            )
        }).collect()
    }

    fn build_builder(&self) -> TokenStream {
        let ident = &self.ast.ident;

        // Generate builder struct
        let builder_struct_name = self.builder_name();
        let builder_fields = self.builder_fields();
        let builder_field_values = self.builder_field_values();
        let builder_methods = self.builder_methods();
        let instance_field_values = self.target_field_values();
        let checks_for_builder = self.builder_build_checks();

        quote::quote!(
            #[derive(Clone, Debug)]
            struct #builder_struct_name {
                #(#builder_fields)*
            }

            impl #builder_struct_name {
                fn new() -> #builder_struct_name {
                    #builder_struct_name {
                        #(#builder_field_values)*
                    }
                }

                #(#builder_methods)*

                fn build(&self) -> Result<#ident, String> {
                    #(#checks_for_builder)*
                    Ok(#ident {
                        #(#instance_field_values)*
                    })
                }
            }

        )
        .into()
    }
}