Skip to main content

tayvo_optional_struct/
lib.rs

1extern crate proc_macro;
2extern crate syn;
3#[macro_use]
4extern crate quote;
5
6use proc_macro::TokenStream;
7use quote::Tokens;
8use syn::MetaItem;
9use std::collections::HashMap;
10use syn::Field;
11use syn::Generics;
12use syn::Ident;
13use syn::Lit;
14
15#[proc_macro_derive(
16    OptionalStruct,
17    attributes(optional_name, optional_derive, opt_nested_original, opt_nested_generated, opt_lenient, opt_skip_serializing_none, opt_some_priority, opt_passthrough)
18)]
19pub fn optional_struct(input: TokenStream) -> TokenStream {
20    let s = input.to_string();
21    let ast = syn::parse_derive_input(&s).unwrap();
22    let gen = generate_optional_struct(&ast);
23    gen.parse().unwrap()
24}
25
26fn generate_optional_struct(ast: &syn::DeriveInput) -> Tokens {
27    let data = parse_attributes(&ast);
28
29    if let syn::Body::Struct(ref variant_data) = ast.body {
30        if let &syn::VariantData::Struct(ref fields) = variant_data {
31            return create_struct(fields, data, &ast.generics);
32        }
33    }
34
35    panic!("OptionalStruct only supports non-tuple structs for now");
36}
37
38struct Data {
39    orignal_struct_name: Ident,
40    optional_struct_name: Ident,
41    derives: Tokens,
42    nested_names: HashMap<String, String>,
43    skip_serializing_none: bool,
44    some_priority: bool,
45}
46
47impl Data {
48    fn explode(self) -> (Ident, Ident, Tokens, HashMap<String, String>, bool, bool) {
49        (
50            self.orignal_struct_name,
51            self.optional_struct_name,
52            self.derives,
53            self.nested_names,
54            self.skip_serializing_none,
55            self.some_priority
56        )
57    }
58}
59
60fn nested_meta_item_to_ident(nested_item: &syn::NestedMetaItem) -> &Ident {
61    match nested_item {
62        &syn::NestedMetaItem::MetaItem(ref item) => match item {
63            &syn::MetaItem::Word(ref ident) => ident,
64            _ => panic!("Only traits name are supported inside optional_struct"),
65        },
66        &syn::NestedMetaItem::Literal(_) => {
67            panic!("Only traits name are supported inside optional_struct")
68        }
69    }
70}
71
72fn create_nested_names_map(orig: Vec<Ident>, gen: Vec<Ident>) -> HashMap<String, String> {
73    let mut map = HashMap::new();
74
75    let orig_gen = orig.iter().zip(gen);
76
77    for (orig, gen) in orig_gen {
78        if gen.to_string().is_empty() {
79            map.insert(orig.to_string(), "Optional".to_owned() + &gen.to_string());
80        } else {
81            map.insert(orig.to_string(), gen.to_string());
82        }
83    }
84
85    map
86}
87
88fn handle_name(
89    name: &Ident,
90    lenient: &mut bool,
91    skip_serializing_none: &mut bool,
92    some_priority: &mut bool,
93) {
94    match name.to_string().as_str() {
95        "opt_lenient" => *lenient = true,
96        "opt_skip_serializing_none" => *skip_serializing_none = true,
97        "opt_some_priority" => *some_priority = true,
98        _ => {
99            if !*lenient {
100                panic!("Only word opt_lenient, opt_skip_serializing_none and opt_some_priority are supported, not {}", name)
101            }
102        },
103    };
104}
105
106fn handle_list(
107    name: &Ident,
108    values: &Vec<syn::NestedMetaItem>,
109    nested_original: &mut Vec<Ident>,
110    nested_generated: &mut Vec<Ident>,
111    derives: &mut Tokens,
112    lenient: &mut bool,
113) {
114    match name.to_string().as_str() {
115        "optional_derive" => {
116            let mut derives_local = quote!{};
117            for value in values {
118                let derive_ident = nested_meta_item_to_ident(value);
119                derives_local = quote!{ #derive_ident, #derives_local }
120            }
121            *derives = derives_local;
122        }
123        "opt_nested_generated" => {
124            for value in values {
125                let generated_nested_name = nested_meta_item_to_ident(value);
126                nested_generated.push(generated_nested_name.clone());
127            }
128        }
129        "opt_nested_original" => {
130            for value in values {
131                let original_nested_name = nested_meta_item_to_ident(value);
132                nested_original.push(original_nested_name.clone());
133            }
134        }
135        _ => {
136            if !*lenient {
137                panic!("Only optional_derive is supported, not {}", name)
138            }
139        },
140    };
141}
142
143fn handle_name_value(name: &Ident, value: &Lit, struct_name: &mut Ident) {
144    match value {
145        &Lit::Str(ref name_value, _) => {
146            if name == "doc" {
147                // Ignore doc strings when parsing.
148                return;
149            } else if name == "optional_name" {
150                *struct_name = Ident::new(name_value.clone())
151            } else {
152                panic!("Only optional_name is supported, not {}", name);
153            }
154        }
155        _ => panic!("optional_name should be a string"),
156    }
157}
158
159fn parse_attributes(ast: &syn::DeriveInput) -> Data {
160    let orignal_struct_name = ast.ident.clone();
161    let mut struct_name = String::from("Optional");
162    struct_name.push_str(&ast.ident.to_string());
163    let mut struct_name = Ident::new(struct_name);
164    let mut derives = quote!{};
165    let mut nested_generated = Vec::new();
166    let mut nested_original = Vec::new();
167    let mut lenient = false;
168    let mut skip_serializing_none = false;
169    let mut some_priority = false;
170
171    for attribute in &ast.attrs {
172        match &attribute.value {
173            &syn::MetaItem::Word(ref name) => handle_name(name, &mut lenient, &mut skip_serializing_none, &mut some_priority),
174            &syn::MetaItem::NameValue(ref name, ref value) => {
175                handle_name_value(name, value, &mut struct_name)
176            }
177            &syn::MetaItem::List(ref name, ref values) => handle_list(
178                name,
179                values,
180                &mut nested_original,
181                &mut nested_generated,
182                &mut derives,
183                &mut lenient,
184            ),
185        }
186    }
187
188    // prevent warnings if no derive is given
189    derives = if derives.to_string().is_empty() {
190        quote!{}
191    } else {
192        quote!{ #[derive(#derives)] }
193    };
194
195    Data {
196        orignal_struct_name: orignal_struct_name,
197        optional_struct_name: struct_name,
198        derives: derives,
199        nested_names: create_nested_names_map(nested_original, nested_generated),
200        skip_serializing_none,
201        some_priority
202    }
203}
204
205fn create_struct(fields: &Vec<Field>, data: Data, generics: &Generics) -> Tokens {
206    let (orignal_struct_name, optional_struct_name, derives, nested_names, skip_serializing_none, some_priority) = data.explode();
207    let (assigners, attributes, empty) = create_fields(&fields, nested_names, skip_serializing_none, some_priority);
208
209    let (_, generics_no_where, _) = generics.split_for_impl();
210
211    quote!{
212        #derives
213        pub struct #optional_struct_name #generics {
214            #attributes
215        }
216
217        impl #generics #orignal_struct_name #generics_no_where {
218            pub fn apply_options(&mut self, optional_struct: #optional_struct_name #generics_no_where) {
219                #assigners
220            }
221        }
222
223        impl #generics #optional_struct_name #generics_no_where {
224            pub fn empty() -> #optional_struct_name #generics_no_where {
225                #optional_struct_name {
226                    #empty
227                }
228            }
229        }
230    }
231}
232
233fn create_fields(
234    fields: &Vec<Field>,
235    nested_names: HashMap<String, String>,
236    skip_serializing_none: bool,
237    some_priority: bool,
238) -> (Tokens, Tokens, Tokens) {
239    let mut attributes = quote!{};
240    let mut assigners = quote!{};
241    let mut empty = quote!{};
242    for field in fields {
243        let ref type_name = &field.ty;
244        let ref field_name = &field.ident.clone().unwrap();
245        let mut next_attribute = quote! { };
246        let next_assigner;
247        let next_empty;
248
249        if skip_serializing_none {
250            next_attribute.append(quote! {
251                #[serde(skip_serializing_if = "Option::is_none")]
252            });
253        }
254
255        let mut existing_field_attributes = quote! {  };
256        let mut include_next_item = false;
257        for attr in &field.attrs {
258            if let MetaItem::Word(ident) = &attr.value {
259                if ident.to_string() == "opt_passthrough" {
260                    include_next_item = true;
261                    continue;
262                }
263            }
264
265            if include_next_item {
266                existing_field_attributes = quote! {
267                    #existing_field_attributes
268                    #attr
269                };
270
271                include_next_item = false;
272            }
273        }
274
275        let type_name_string = quote!{#type_name}.to_string();
276        let type_name_string: String = type_name_string.chars().filter(|&c| c != ' ').collect();
277
278        if type_name_string.starts_with("Option<") {
279            next_attribute.append(quote!{ pub #field_name: #type_name, });
280
281            if some_priority {
282                next_assigner = quote!{
283                    if let Some(attribute) = optional_struct.#field_name {
284                        self.#field_name.replace(attribute);
285                    }
286                };
287            } else {
288                next_assigner = quote!{ self.#field_name = optional_struct.#field_name; };
289            }
290
291            next_empty = quote!{ #field_name: None, };
292        } else if nested_names.contains_key(&type_name_string) {
293            let type_name = Ident::new(nested_names.get(&type_name_string).unwrap().as_str());
294            next_attribute.append(quote!{ pub #field_name: #type_name, });
295            next_assigner = quote!{ self.#field_name.apply_options(optional_struct.#field_name); };
296            next_empty = quote!{ #field_name: #type_name::empty(), };
297        } else {
298            next_attribute.append(quote! { pub #field_name: Option<#type_name>, });
299            next_assigner = quote!{
300                if let Some(attribute) = optional_struct.#field_name {
301                    self.#field_name = attribute;
302                }
303            };
304            next_empty = quote!{ #field_name: None, };
305        }
306
307        assigners = quote!{ #assigners #next_assigner };
308        attributes = quote!{ #attributes #existing_field_attributes #next_attribute };
309        empty = quote!{ #empty #next_empty }
310    }
311
312    (assigners, attributes, empty)
313}