diff --git a/influxdb_derive/Cargo.toml b/influxdb_derive/Cargo.toml index 5374031..731a751 100644 --- a/influxdb_derive/Cargo.toml +++ b/influxdb_derive/Cargo.toml @@ -16,6 +16,6 @@ repository = "https://github.com/influxdb-rs/influxdb-rust" proc-macro = true [dependencies] -proc-macro2 = "1.0.9" -quote = "1.0.3" -syn = { version = "1.0.16", features = ["extra-traits", "full"] } +proc-macro2 = "1.0" +quote = "1.0" +syn = { version = "2.0", features = ["extra-traits", "full"] } diff --git a/influxdb_derive/src/lib.rs b/influxdb_derive/src/lib.rs index a6bbba8..5025b33 100644 --- a/influxdb_derive/src/lib.rs +++ b/influxdb_derive/src/lib.rs @@ -1,15 +1,12 @@ use proc_macro::TokenStream; -use proc_macro2::TokenStream as TokenStream2; -use quote::quote; mod writeable; +use syn::parse_macro_input; use writeable::expand_writeable; -fn krate() -> TokenStream2 { - quote!(::influxdb) -} - #[proc_macro_derive(InfluxDbWriteable, attributes(influxdb))] -pub fn derive_writeable(tokens: TokenStream) -> TokenStream { - expand_writeable(tokens) +pub fn derive_writeable(input: TokenStream) -> TokenStream { + expand_writeable(parse_macro_input!(input)) + .unwrap_or_else(syn::Error::into_compile_error) + .into() } diff --git a/influxdb_derive/src/writeable.rs b/influxdb_derive/src/writeable.rs index 04a7bb0..870f947 100644 --- a/influxdb_derive/src/writeable.rs +++ b/influxdb_derive/src/writeable.rs @@ -1,7 +1,11 @@ -use proc_macro::TokenStream; -use proc_macro2::{TokenStream as TokenStream2, TokenTree}; +use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use syn::{parse_macro_input, Field, Fields, Ident, ItemStruct}; +use std::convert::TryFrom; +use syn::{ + parse::{Parse, ParseStream}, + punctuated::Punctuated, + Data, DeriveInput, Field, Fields, Ident, Meta, Token, +}; #[derive(Debug)] struct WriteableField { @@ -10,101 +14,119 @@ struct WriteableField { is_ignore: bool, } -impl From for WriteableField { - fn from(field: Field) -> WriteableField { - let ident = field.ident.expect("fields without ident are not supported"); +mod kw { + use syn::custom_keyword; - let check_influx_aware = |attr: &syn::Attribute| -> bool { - attr.path - .segments - .iter() - .last() - .map(|seg| seg.ident.to_string()) - .unwrap_or_default() - == "influxdb" - }; + custom_keyword!(tag); + custom_keyword!(ignore); +} - let check_for_attr = |token_tree, ident_cmp: &str| -> bool { - match token_tree { - TokenTree::Group(group) => group - .stream() - .into_iter() - .next() - .map(|token_tree| match token_tree { - TokenTree::Ident(ident) => ident == ident_cmp, - _ => false, - }) - .unwrap(), - _ => false, - } - }; +enum FieldAttr { + Tag(kw::tag), + Ignore(kw::ignore), +} - let is_ignore = field.attrs.iter().any(|attr| { - if !check_influx_aware(attr) { - return false; - } +impl Parse for FieldAttr { + fn parse(input: ParseStream<'_>) -> syn::Result { + let lookahead = input.lookahead1(); + if lookahead.peek(kw::tag) { + Ok(Self::Tag(input.parse()?)) + } else if lookahead.peek(kw::ignore) { + Ok(Self::Ignore(input.parse()?)) + } else { + Err(lookahead.error()) + } + } +} + +struct FieldAttrs(Punctuated); + +impl Parse for FieldAttrs { + fn parse(input: ParseStream<'_>) -> syn::Result { + Ok(Self(Punctuated::parse_terminated(input)?)) + } +} - attr.tokens - .clone() - .into_iter() - .next() - .map(|token_tree| check_for_attr(token_tree, "ignore")) - .unwrap() - }); +impl TryFrom for WriteableField { + type Error = syn::Error; - let is_tag = field.attrs.iter().any(|attr| { - if !check_influx_aware(attr) { - return false; + fn try_from(field: Field) -> syn::Result { + let ident = field.ident.expect("fields without ident are not supported"); + let mut is_tag = false; + let mut is_ignore = false; + + for attr in field.attrs { + match attr.meta { + Meta::List(list) if list.path.is_ident("influxdb") => { + for attr in syn::parse2::(list.tokens)?.0 { + match attr { + FieldAttr::Tag(_) => is_tag = true, + FieldAttr::Ignore(_) => is_ignore = true, + } + } + } + _ => {} } - attr.tokens - .clone() - .into_iter() - .next() - .map(|token_tree| check_for_attr(token_tree, "tag")) - .unwrap() - }); + } - WriteableField { + Ok(WriteableField { ident, is_tag, is_ignore, - } + }) } } -pub fn expand_writeable(tokens: TokenStream) -> TokenStream { - let krate = super::krate(); - let input = parse_macro_input!(tokens as ItemStruct); +pub fn expand_writeable(input: DeriveInput) -> syn::Result { let ident = input.ident; - let generics = input.generics; + let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); + + let fields = match input.data { + Data::Struct(strukt) => strukt.fields, + Data::Enum(inum) => { + return Err(syn::Error::new( + inum.enum_token.span, + "#[derive(InfluxDbWriteable)] can only be used on structs", + )) + } + Data::Union(cdu) => { + return Err(syn::Error::new( + cdu.union_token.span, + "#[derive(InfluxDbWriteable)] can only be used on structs", + )) + } + }; let time_field = format_ident!("time"); + let time_field_str = time_field.to_string(); #[allow(clippy::cmp_owned)] // that's not how idents work clippy - let fields: Vec = match input.fields { + let fields = match fields { Fields::Named(fields) => fields .named .into_iter() - .map(WriteableField::from) - .filter(|field| !field.is_ignore) - .filter(|field| field.ident.to_string() != time_field.to_string()) - .map(|field| { - let ident = field.ident; - #[allow(clippy::match_bool)] - match field.is_tag { - true => quote!(query.add_tag(stringify!(#ident), self.#ident)), - false => quote!(query.add_field(stringify!(#ident), self.#ident)), - } + .filter_map(|f| { + WriteableField::try_from(f) + .map(|wf| { + if !wf.is_ignore && wf.ident.to_string() != time_field_str { + let ident = wf.ident; + Some(match wf.is_tag { + true => quote!(query.add_tag(stringify!(#ident), self.#ident)), + false => quote!(query.add_field(stringify!(#ident), self.#ident)), + }) + } else { + None + } + }) + .transpose() }) - .collect(), + .collect::>>()?, _ => panic!("a struct without named fields is not supported"), }; - let output = quote! { - impl #generics #krate::InfluxDbWriteable for #ident #generics - { - fn into_query>(self, name : I) -> #krate::WriteQuery - { - let timestamp : #krate::Timestamp = self.#time_field.into(); + Ok(quote! { + impl #impl_generics ::influxdb::InfluxDbWriteable for #ident #ty_generics #where_clause { + fn into_query>(self, name: I) -> ::influxdb::WriteQuery { + let timestamp: ::influxdb::Timestamp = self.#time_field.into(); let mut query = timestamp.into_query(name); #( query = #fields; @@ -112,6 +134,5 @@ pub fn expand_writeable(tokens: TokenStream) -> TokenStream { query } } - }; - output.into() + }) }