xml_attributes_derive/
lib.rs

1extern crate proc_macro;
2/**
3* Copyright 2019 Comcast Cable Communications Management, LLC
4*
5* Licensed under the Apache License, Version 2.0 (the "License");
6* you may not use this file except in compliance with the License.
7* You may obtain a copy of the License at
8*
9* http://www.apache.org/licenses/LICENSE-2.0
10*
11* Unless required by applicable law or agreed to in writing, software
12* distributed under the License is distributed on an "AS IS" BASIS,
13* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14* See the License for the specific language governing permissions and
15* limitations under the License.
16*
17* SPDX-License-Identifier: Apache-2.0
18*/
19extern crate proc_macro2;
20#[macro_use]
21extern crate quote;
22extern crate syn;
23
24use proc_macro::TokenStream;
25use proc_macro2::{Literal, Spacing, Span, TokenNode, TokenTree};
26use quote::ToTokens;
27use syn::{Data, Ident, Type};
28
29#[proc_macro_derive(FromXmlAttributes)]
30pub fn from_xml_attributes(input: TokenStream) -> TokenStream {
31    // Parse the input stream
32    let ast = syn::parse(input).unwrap();
33
34    // Build the impl
35    let gen = impl_xml(&ast);
36
37    // Return the generated impl
38    gen.into()
39}
40
41fn impl_xml(ast: &syn::DeriveInput) -> quote::Tokens {
42    let name = &ast.ident;
43    match ast.data {
44        Data::Struct(ref data) => impl_struct_xml_fields(name, &data.fields),
45        Data::Enum(ref data) => enum_fields(name, data),
46        Data::Union(ref data) => union_fields(name, data),
47    }
48}
49
50fn union_fields(_name: &syn::Ident, _data: &syn::DataUnion) -> quote::Tokens {
51    let mut result = Vec::new();
52    result.push(quote! {
53        panic!("not implemented");
54    });
55    quote! {
56        #(#result)*
57    }
58}
59
60fn enum_fields(_name: &syn::Ident, _variants: &syn::DataEnum) -> quote::Tokens {
61    let mut result = Vec::new();
62    result.push(quote! {
63        panic!("not implemented");
64    });
65    quote! {
66        #(#result)*
67    }
68}
69
70fn impl_struct_xml_fields(name: &syn::Ident, fields: &syn::Fields) -> quote::Tokens {
71    let mut result = Vec::new();
72    for field in fields.iter() {
73        let ident = &field.ident;
74        let ident_type = match field.clone().ty {
75            Type::Path(p) => {
76                if let Some(i) = p.path.segments.clone().into_iter().next() {
77                    Some(i.ident)
78                } else {
79                    None
80                }
81            }
82            _ => None,
83        };
84        let u_64 = Ident::new("u64", Span::def_site());
85        let f_64 = Ident::new("f64", Span::def_site());
86        let string = Ident::new("String", Span::def_site());
87        let boolean = Ident::new("bool", Span::def_site());
88
89        // Setup the fields
90        match ident_type {
91            Some(i_type) => {
92                if i_type == u_64 {
93                    result.push(quote! {
94                        let mut #ident = 0;
95                    });
96                } else if i_type == f_64 {
97                    result.push(quote! {
98                        let mut #ident = 0.0;
99                    });
100                } else if i_type == string {
101                    result.push(quote! {
102                        let mut #ident = String::new();
103                    });
104                } else if i_type == boolean {
105                    result.push(quote! {
106                        let mut #ident = false;
107                    });
108                } else {
109                    // Uncomment me to debug why some fields may be missing
110                    //println!("else: {:?} {:?} {:?}", ident, i_type, field.clone().ty);
111                }
112            }
113            None => {
114                // Unable to identify this type
115                println!("Unable to identify type for {:?}", ident);
116            }
117        }
118    }
119
120    // Match the fields
121    result.push(quote! {
122        for a in attrs
123    });
124
125    result.push(
126        TokenTree {
127            span: Span::def_site(),
128            kind: TokenNode::Op('{', Spacing::Joint),
129        }
130        .into_tokens(),
131    );
132
133    result.push(quote! {
134        let item = a?;
135        let val = String::from_utf8_lossy(&item.value);
136        match item.key
137    });
138
139    result.push(
140        TokenTree {
141            span: Span::def_site(),
142            kind: TokenNode::Op('{', Spacing::Joint),
143        }
144        .into_tokens(),
145    );
146
147    for field in fields.iter() {
148        let ident = &field.ident;
149        let ident_type = match field.clone().ty {
150            Type::Path(p) => {
151                if let Some(i) = p.path.segments.clone().into_iter().next() {
152                    Some(i.ident)
153                } else {
154                    None
155                }
156            }
157            _ => None,
158        };
159        let u_64 = Ident::new("u64", Span::def_site());
160        let f_64 = Ident::new("f64", Span::def_site());
161        let string = Ident::new("String", Span::def_site());
162        let boolean = Ident::new("bool", Span::def_site());
163
164        let i = ident.unwrap();
165        let ident_name = {
166            let i = i.as_ref();
167            if i.starts_with("_") {
168                i.trim_start_matches("_")
169            } else {
170                i
171            }
172        };
173
174        match ident_type {
175            Some(i_type) => {
176                if i_type == u_64 {
177                    result.push(
178                        TokenTree {
179                            span: Span::def_site(),
180                            kind: TokenNode::Literal(Literal::byte_string(ident_name.as_bytes())),
181                        }
182                        .into_tokens(),
183                    );
184
185                    result.push(quote! {
186                        => {
187                            #ident = u64::from_str(&val)?;
188                        }
189                    });
190                } else if i_type == f_64 {
191                    result.push(
192                        TokenTree {
193                            span: Span::def_site(),
194                            kind: TokenNode::Literal(Literal::byte_string(ident_name.as_bytes())),
195                        }
196                        .into_tokens(),
197                    );
198
199                    result.push(quote! {
200                        => {
201                            #ident = f64::from_str(&val)?;
202                        }
203                    });
204                } else if i_type == string {
205                    result.push(
206                        TokenTree {
207                            span: Span::def_site(),
208                            kind: TokenNode::Literal(Literal::byte_string(ident_name.as_bytes())),
209                        }
210                        .into_tokens(),
211                    );
212
213                    result.push(quote! {
214                        => {
215                            #ident = val.to_string();
216                        }
217                    });
218                } else if i_type == boolean {
219                    result.push(
220                        TokenTree {
221                            span: Span::def_site(),
222                            kind: TokenNode::Literal(Literal::byte_string(ident_name.as_bytes())),
223                        }
224                        .into_tokens(),
225                    );
226
227                    result.push(quote! {
228                        => {
229                            #ident = bool::from_str(&val)?;
230                        }
231                    });
232                } else {
233                    // Uncomment me to debug why some fields may be missing
234                    println!("else: {:?} {:?}", ident, i_type);
235                }
236            }
237            None => {
238                // Unable to identify this type
239                println!("Unable to identify type for {:?}", ident);
240            }
241        }
242    }
243
244    result.push(quote! {
245        _ => {
246            debug!(
247                "unknown xml attribute: {}",
248                String::from_utf8_lossy(item.key)
249            );
250        }
251    });
252
253    result.push(
254        TokenTree {
255            span: Span::def_site(),
256            kind: TokenNode::Op('}', Spacing::Joint),
257        }
258        .into_tokens(),
259    );
260    result.push(
261        TokenTree {
262            span: Span::def_site(),
263            kind: TokenNode::Op('}', Spacing::Joint),
264        }
265        .into_tokens(),
266    );
267    result.push(quote! {
268        Ok
269    });
270
271    result.push(
272        TokenTree {
273            span: Span::def_site(),
274            kind: TokenNode::Op('(', Spacing::Joint),
275        }
276        .into_tokens(),
277    );
278
279    result.push(quote! {
280        #name
281    });
282
283    result.push(
284        TokenTree {
285            span: Span::def_site(),
286            kind: TokenNode::Op('{', Spacing::Joint),
287        }
288        .into_tokens(),
289    );
290
291    for field in fields.iter() {
292        let ident = &field.ident;
293
294        result.push(quote! {
295            #ident: #ident,
296        });
297    }
298
299    result.push(
300        TokenTree {
301            span: Span::def_site(),
302            kind: TokenNode::Op('}', Spacing::Joint),
303        }
304        .into_tokens(),
305    );
306    result.push(
307        TokenTree {
308            span: Span::def_site(),
309            kind: TokenNode::Op(')', Spacing::Joint),
310        }
311        .into_tokens(),
312    );
313
314    quote! {
315        impl FromXmlAttributes for #name {
316            fn from_xml_attributes(attrs: Attributes) -> MetricsResult<Self> {
317                #(#result)*
318            }
319        }
320    }
321}