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