influxdb2_structmap_derive/
lib.rs

1//! Implements the functionality to enable conversion between a struct type a 
2//! map container type in Rust through the use of a procedural macros.
3#![recursion_limit = "128"]
4
5extern crate proc_macro;
6
7use itertools::izip;
8use proc_macro::TokenStream;
9use quote::quote;
10use syn::{Data, DeriveInput, Ident};
11
12/// Implements the functionality for converting entries in a BTreeMap into 
13/// attributes and values of a struct. It will consume a tokenized version of 
14/// the initial struct declaration, and use code generation to implement the 
15/// `FromMap` trait for instantiating the contents of the struct.
16#[proc_macro_derive(FromMap)]
17pub fn from_map(input: TokenStream) -> TokenStream {
18    let ast = syn::parse_macro_input!(input as DeriveInput);
19
20    // parse out all the field names in the struct as `Ident`s
21    let fields = match ast.data {
22        Data::Struct(st) => st.fields,
23        _ => panic!("Implementation must be a struct"),
24    };
25    let idents: Vec<&Ident> = fields
26        .iter()
27        .filter_map(|field| field.ident.as_ref())
28        .collect::<Vec<&Ident>>();
29
30    // convert all the field names into strings
31    let keys: Vec<String> = idents
32        .clone()
33        .iter()
34        .map(|ident| ident.to_string())
35        .collect::<Vec<String>>();
36
37    let typenames = fields
38        .iter()
39        .map(|field| { /*match field.ty.clone() {
40            Type::Path(typepath) => {
41                typepath
42                /*
43                let typename = quote! {#typepath}.to_string();
44                typename
45                */
46            }
47            _ => unimplemented!(),
48            */
49            let t = field.ty.clone();
50            let s = quote! {#t}.to_string();
51            s
52        })
53        .collect::<Vec<String>>();
54
55    // parse out all the primitive types in the struct as Idents
56    /*
57    let typecalls: Vec<Ident> = fields
58        .iter()
59        .map(|field| match field.ty.clone() {
60            Type::Path(typepath) => {
61                // TODO: options and results
62                // TODO: vecs
63                // TODO: genericized numerics
64
65                // get the type of the specified field, lowercase
66                let typename: String = quote! {#typepath}.to_string().to_lowercase();
67
68                // initialize new Ident for codegen
69                Ident::new(&typename, Span::mixed_site())
70            }
71            _ => unimplemented!(),
72        })
73        .collect::<Vec<Ident>>();
74    */
75
76    // get the name identifier of the struct input AST
77    let name: &Ident = &ast.ident;
78    let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
79
80    let datetime_re = regex::Regex::new(r"DateTime").unwrap();
81    let duration_re = regex::Regex::new(r"Duration").unwrap();
82    let base64_re = regex::Regex::new(r"Vec").unwrap();
83    let mut assignments = Vec::new();
84    for (key, typename, ident) in izip!(keys, typenames, idents) {
85        match &typename[..] {
86            "f64" => {
87                assignments.push(quote! {
88                    let mut key = String::from(#key);
89                    if !hashmap.contains_key(&key) {
90                        key = format!("_{}", key);
91                    }
92                    match hashmap.entry(key) {
93                        ::std::collections::btree_map::Entry::Occupied(entry) => {
94                            if let Value::Double(v) = entry.get() {
95                                settings.#ident = *v;
96                            }
97                        },
98                        _ => panic!("Cannot parse out map entry"),
99                    }
100                })
101            }
102            "i64" => {
103                assignments.push(quote! {
104                    let mut key = String::from(#key);
105                    if !hashmap.contains_key(&key) {
106                        key = format!("_{}", key);
107                    }
108                    match hashmap.entry(key) {
109                        ::std::collections::btree_map::Entry::Occupied(entry) => {
110                            if let Value::Long(v) = entry.get() {
111                                settings.#ident = *v;
112                            }
113                        },
114                        _ => panic!("Cannot parse out map entry"),
115                    }
116                })
117            }
118            "u64" => {
119                assignments.push(quote! {
120                    let mut key = String::from(#key);
121                    if !hashmap.contains_key(&key) {
122                        key = format!("_{}", key);
123                    }
124                    match hashmap.entry(key) {
125                        ::std::collections::btree_map::Entry::Occupied(entry) => {
126                            if let Value::UnsignedLong(v) = entry.get() {
127                                settings.#ident = *v;
128                            }
129                        },
130                        _ => panic!("Cannot parse out map entry"),
131                    }
132                })
133            }
134            "bool" => {
135                assignments.push(quote! {
136                    let mut key = String::from(#key);
137                    if !hashmap.contains_key(&key) {
138                        key = format!("_{}", key);
139                    }
140                    match hashmap.entry(key) {
141                        ::std::collections::btree_map::Entry::Occupied(entry) => {
142                            if let Value::Bool(v) = entry.get() {
143                                settings.#ident = *v;
144                            }
145                        },
146                        _ => panic!("Cannot parse out map entry"),
147                    }
148                })
149            }
150            "String" => {
151                assignments.push(quote! {
152                    let mut key = String::from(#key);
153                    if !hashmap.contains_key(&key) {
154                        key = format!("_{}", key);
155                    }
156                    match hashmap.entry(key) {
157                        ::std::collections::btree_map::Entry::Occupied(entry) => {
158                            if let Value::String(v) = entry.get() {
159                                settings.#ident = v.clone();
160                            }
161                        },
162                        _ => panic!("Cannot parse out map entry"),
163                    }
164                })
165            }
166            x if duration_re.is_match(x) => {
167                assignments.push(quote! {
168                    let mut key = String::from(#key);
169                    if !hashmap.contains_key(&key) {
170                        key = format!("_{}", key);
171                    }
172                    match hashmap.entry(key) {
173                        ::std::collections::btree_map::Entry::Occupied(entry) => {
174                            if let Value::Duration(v) = entry.get() {
175                                settings.#ident = *v;
176                            }
177                        },
178                        _ => panic!("Cannot parse out map entry"),
179                    }
180                })
181            }
182            x if datetime_re.is_match(x) => {
183                assignments.push(quote! {
184                    let mut key = String::from(#key);
185                    if !hashmap.contains_key(&key) {
186                        key = format!("_{}", key);
187                    }
188                    match hashmap.entry(key) {
189                        ::std::collections::btree_map::Entry::Occupied(entry) => {
190                            if let Value::TimeRFC(v) = entry.get() {
191                                settings.#ident = *v;
192                            }
193                        },
194                        _ => panic!("Cannot parse out map entry"),
195                    }
196                })
197            }
198            x if base64_re.is_match(x) => {
199                assignments.push(quote! {
200                    let mut key = String::from(#key);
201                    if !hashmap.contains_key(&key) {
202                        key = format!("_{}", key);
203                    }
204                    match hashmap.entry(key) {
205                        ::std::collections::btree_map::Entry::Occupied(entry) => {
206                            if let Value::Base64Binary(v) = entry.get() {
207                                settings.#ident = *v;
208                            }
209                        },
210                        _ => panic!("Cannot parse out map entry"),
211                    }
212                })
213            }
214            x => {
215                panic!("{} is not handled", x);
216            }
217        }
218    }
219
220    // start codegen of a generic or non-generic impl for the given struct using quasi-quoting
221    let tokens = quote! {
222        use influxdb2_structmap::value::Value;
223        use influxdb2_structmap::{GenericMap};
224
225        impl #impl_generics FromMap for #name #ty_generics #where_clause {
226
227            fn from_genericmap(mut hashmap: GenericMap) -> #name {
228                let mut settings = #name::default();
229
230                #(
231                    #assignments
232                )*
233
234                settings
235            }
236
237        }
238    };
239    TokenStream::from(tokens)
240}
241
242#[cfg(test)]
243mod tests {
244    #[test]
245    fn ui() {
246        let t = trybuild::TestCases::new();
247        t.pass("tests/struct.rs");
248    }
249}
250