1- use proc_macro:: TokenStream ;
2- use proc_macro2:: { TokenStream as TokenStream2 , TokenTree } ;
1+ use proc_macro2:: TokenStream ;
32use quote:: { format_ident, quote} ;
4- use syn:: { parse_macro_input, Field , Fields , Ident , ItemStruct } ;
3+ use std:: convert:: TryFrom ;
4+ use syn:: {
5+ parse:: { Parse , ParseStream } ,
6+ punctuated:: Punctuated ,
7+ Data , DeriveInput , Field , Fields , Ident , Meta , Token ,
8+ } ;
59
610#[ derive( Debug ) ]
711struct WriteableField {
@@ -10,108 +14,125 @@ struct WriteableField {
1014 is_ignore : bool ,
1115}
1216
13- impl From < Field > for WriteableField {
14- fn from ( field : Field ) -> WriteableField {
15- let ident = field. ident . expect ( "fields without ident are not supported" ) ;
17+ mod kw {
18+ use syn:: custom_keyword;
1619
17- let check_influx_aware = |attr : & syn:: Attribute | -> bool {
18- attr. path
19- . segments
20- . iter ( )
21- . last ( )
22- . map ( |seg| seg. ident . to_string ( ) )
23- . unwrap_or_default ( )
24- == "influxdb"
25- } ;
20+ custom_keyword ! ( tag) ;
21+ custom_keyword ! ( ignore) ;
22+ }
2623
27- let check_for_attr = |token_tree, ident_cmp : & str | -> bool {
28- match token_tree {
29- TokenTree :: Group ( group) => group
30- . stream ( )
31- . into_iter ( )
32- . next ( )
33- . map ( |token_tree| match token_tree {
34- TokenTree :: Ident ( ident) => ident == ident_cmp,
35- _ => false ,
36- } )
37- . unwrap ( ) ,
38- _ => false ,
39- }
40- } ;
24+ enum FieldAttr {
25+ Tag ( kw:: tag ) ,
26+ Ignore ( kw:: ignore ) ,
27+ }
4128
42- let is_ignore = field. attrs . iter ( ) . any ( |attr| {
43- if !check_influx_aware ( attr) {
44- return false ;
45- }
29+ impl Parse for FieldAttr {
30+ fn parse ( input : ParseStream < ' _ > ) -> syn:: Result < Self > {
31+ let lookahead = input. lookahead1 ( ) ;
32+ if lookahead. peek ( kw:: tag) {
33+ Ok ( Self :: Tag ( input. parse ( ) ?) )
34+ } else if lookahead. peek ( kw:: ignore) {
35+ Ok ( Self :: Ignore ( input. parse ( ) ?) )
36+ } else {
37+ Err ( lookahead. error ( ) )
38+ }
39+ }
40+ }
41+
42+ struct FieldAttrs ( Punctuated < FieldAttr , Token ! [ , ] > ) ;
43+
44+ impl Parse for FieldAttrs {
45+ fn parse ( input : ParseStream < ' _ > ) -> syn:: Result < Self > {
46+ Ok ( Self ( Punctuated :: parse_terminated ( input) ?) )
47+ }
48+ }
4649
47- attr. tokens
48- . clone ( )
49- . into_iter ( )
50- . next ( )
51- . map ( |token_tree| check_for_attr ( token_tree, "ignore" ) )
52- . unwrap ( )
53- } ) ;
50+ impl TryFrom < Field > for WriteableField {
51+ type Error = syn:: Error ;
5452
55- let is_tag = field. attrs . iter ( ) . any ( |attr| {
56- if !check_influx_aware ( attr) {
57- return false ;
53+ fn try_from ( field : Field ) -> syn:: Result < WriteableField > {
54+ let ident = field. ident . expect ( "fields without ident are not supported" ) ;
55+ let mut is_tag = false ;
56+ let mut is_ignore = false ;
57+
58+ for attr in field. attrs {
59+ match attr. meta {
60+ Meta :: List ( list) if list. path . is_ident ( "influxdb" ) => {
61+ for attr in syn:: parse2 :: < FieldAttrs > ( list. tokens ) ?. 0 {
62+ match attr {
63+ FieldAttr :: Tag ( _) => is_tag = true ,
64+ FieldAttr :: Ignore ( _) => is_ignore = true ,
65+ }
66+ }
67+ }
68+ _ => { }
5869 }
59- attr. tokens
60- . clone ( )
61- . into_iter ( )
62- . next ( )
63- . map ( |token_tree| check_for_attr ( token_tree, "tag" ) )
64- . unwrap ( )
65- } ) ;
70+ }
6671
67- WriteableField {
72+ Ok ( WriteableField {
6873 ident,
6974 is_tag,
7075 is_ignore,
71- }
76+ } )
7277 }
7378}
7479
75- pub fn expand_writeable ( tokens : TokenStream ) -> TokenStream {
76- let krate = super :: krate ( ) ;
77- let input = parse_macro_input ! ( tokens as ItemStruct ) ;
80+ pub fn expand_writeable ( input : DeriveInput ) -> syn:: Result < TokenStream > {
7881 let ident = input. ident ;
79- let generics = input. generics ;
82+ let ( impl_generics, ty_generics, where_clause) = input. generics . split_for_impl ( ) ;
83+
84+ let fields = match input. data {
85+ Data :: Struct ( strukt) => strukt. fields ,
86+ Data :: Enum ( inum) => {
87+ return Err ( syn:: Error :: new (
88+ inum. enum_token . span ,
89+ "#[derive(InfluxDbWriteable)] can only be used on structs" ,
90+ ) )
91+ }
92+ Data :: Union ( cdu) => {
93+ return Err ( syn:: Error :: new (
94+ cdu. union_token . span ,
95+ "#[derive(InfluxDbWriteable)] can only be used on structs" ,
96+ ) )
97+ }
98+ } ;
8099
81100 let time_field = format_ident ! ( "time" ) ;
101+ let time_field_str = time_field. to_string ( ) ;
82102 #[ allow( clippy:: cmp_owned) ] // that's not how idents work clippy
83- let fields: Vec < TokenStream2 > = match input . fields {
103+ let fields = match fields {
84104 Fields :: Named ( fields) => fields
85105 . named
86106 . into_iter ( )
87- . map ( WriteableField :: from)
88- . filter ( |field| !field. is_ignore )
89- . filter ( |field| field. ident . to_string ( ) != time_field. to_string ( ) )
90- . map ( |field| {
91- let ident = field. ident ;
92- #[ allow( clippy:: match_bool) ]
93- match field. is_tag {
94- true => quote ! ( query. add_tag( stringify!( #ident) , self . #ident) ) ,
95- false => quote ! ( query. add_field( stringify!( #ident) , self . #ident) ) ,
96- }
107+ . filter_map ( |f| {
108+ WriteableField :: try_from ( f)
109+ . map ( |wf| {
110+ if !wf. is_ignore && wf. ident . to_string ( ) != time_field_str {
111+ let ident = wf. ident ;
112+ Some ( match wf. is_tag {
113+ true => quote ! ( query. add_tag( stringify!( #ident) , self . #ident) ) ,
114+ false => quote ! ( query. add_field( stringify!( #ident) , self . #ident) ) ,
115+ } )
116+ } else {
117+ None
118+ }
119+ } )
120+ . transpose ( )
97121 } )
98- . collect ( ) ,
122+ . collect :: < syn :: Result < Vec < _ > > > ( ) ? ,
99123 _ => panic ! ( "a struct without named fields is not supported" ) ,
100124 } ;
101125
102- let output = quote ! {
103- impl #generics #krate:: InfluxDbWriteable for #ident #generics
104- {
105- fn into_query<I : Into <String >>( self , name : I ) -> #krate:: WriteQuery
106- {
107- let timestamp : #krate:: Timestamp = self . #time_field. into( ) ;
126+ Ok ( quote ! {
127+ impl #impl_generics :: influxdb:: InfluxDbWriteable for #ident #ty_generics #where_clause {
128+ fn into_query<I : Into <String >>( self , name: I ) -> :: influxdb:: WriteQuery {
129+ let timestamp: :: influxdb:: Timestamp = self . #time_field. into( ) ;
108130 let mut query = timestamp. into_query( name) ;
109131 #(
110132 query = #fields;
111133 ) *
112134 query
113135 }
114136 }
115- } ;
116- output. into ( )
137+ } )
117138}
0 commit comments