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 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 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}