#![recursion_limit = "128"]
extern crate proc_macro;
use itertools::izip;
use proc_macro::TokenStream;
use quote::quote;
use syn::{Data, DeriveInput, Ident};
#[proc_macro_derive(FromMap)]
pub fn from_map(input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as DeriveInput);
let fields = match ast.data {
Data::Struct(st) => st.fields,
_ => panic!("Implementation must be a struct"),
};
let idents: Vec<&Ident> = fields
.iter()
.filter_map(|field| field.ident.as_ref())
.collect::<Vec<&Ident>>();
let keys: Vec<String> = idents
.clone()
.iter()
.map(|ident| ident.to_string())
.collect::<Vec<String>>();
let typenames = fields
.iter()
.map(|field| {
let t = field.ty.clone();
let s = quote! {#t}.to_string();
s
})
.collect::<Vec<String>>();
let name: &Ident = &ast.ident;
let (impl_generics, ty_generics, where_clause) = ast.generics.split_for_impl();
let datetime_re = regex::Regex::new(r"DateTime").unwrap();
let duration_re = regex::Regex::new(r"Duration").unwrap();
let base64_re = regex::Regex::new(r"Vec").unwrap();
let mut assignments = Vec::new();
for (key, typename, ident) in izip!(keys, typenames, idents) {
match &typename[..] {
"f64" => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::Double(v) = entry.get() {
settings.#ident = *v;
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
"i64" => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::Long(v) = entry.get() {
settings.#ident = *v;
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
"u64" => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::UnsignedLong(v) = entry.get() {
settings.#ident = *v;
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
"bool" => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::Bool(v) = entry.get() {
settings.#ident = *v;
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
"String" => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::String(v) = entry.get() {
settings.#ident = v.clone();
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
x if duration_re.is_match(x) => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::Duration(v) = entry.get() {
settings.#ident = *v;
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
x if datetime_re.is_match(x) => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::TimeRFC(v) = entry.get() {
settings.#ident = *v;
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
x if base64_re.is_match(x) => {
assignments.push(quote! {
let mut key = String::from(#key);
if !hashmap.contains_key(&key) {
key = format!("_{}", key);
}
match hashmap.entry(key) {
::std::collections::btree_map::Entry::Occupied(entry) => {
if let Value::Base64Binary(v) = entry.get() {
settings.#ident = *v;
}
},
_ => panic!("Cannot parse out map entry"),
}
})
}
x => {
panic!("{} is not handled", x);
}
}
}
let tokens = quote! {
use influxdb2_structmap::value::Value;
use influxdb2_structmap::{GenericMap};
impl #impl_generics FromMap for #name #ty_generics #where_clause {
fn from_genericmap(mut hashmap: GenericMap) -> #name {
let mut settings = #name::default();
#(
#assignments
)*
settings
}
}
};
TokenStream::from(tokens)
}
#[cfg(test)]
mod tests {
#[test]
fn ui() {
let t = trybuild::TestCases::new();
t.pass("tests/struct.rs");
}
}