1
- use proc_macro:: TokenStream ;
2
- use proc_macro2:: { TokenStream as TokenStream2 , TokenTree } ;
1
+ use proc_macro2:: TokenStream ;
3
2
use 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
+ } ;
5
9
6
10
#[ derive( Debug ) ]
7
11
struct WriteableField {
@@ -10,108 +14,125 @@ struct WriteableField {
10
14
is_ignore : bool ,
11
15
}
12
16
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;
16
19
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
+ }
26
23
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
+ }
41
28
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
+ }
46
49
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 ;
54
52
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
+ _ => { }
58
69
}
59
- attr. tokens
60
- . clone ( )
61
- . into_iter ( )
62
- . next ( )
63
- . map ( |token_tree| check_for_attr ( token_tree, "tag" ) )
64
- . unwrap ( )
65
- } ) ;
70
+ }
66
71
67
- WriteableField {
72
+ Ok ( WriteableField {
68
73
ident,
69
74
is_tag,
70
75
is_ignore,
71
- }
76
+ } )
72
77
}
73
78
}
74
79
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 > {
78
81
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
+ } ;
80
99
81
100
let time_field = format_ident ! ( "time" ) ;
101
+ let time_field_str = time_field. to_string ( ) ;
82
102
#[ allow( clippy:: cmp_owned) ] // that's not how idents work clippy
83
- let fields: Vec < TokenStream2 > = match input . fields {
103
+ let fields = match fields {
84
104
Fields :: Named ( fields) => fields
85
105
. named
86
106
. 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 ( )
97
121
} )
98
- . collect ( ) ,
122
+ . collect :: < syn :: Result < Vec < _ > > > ( ) ? ,
99
123
_ => panic ! ( "a struct without named fields is not supported" ) ,
100
124
} ;
101
125
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( ) ;
108
130
let mut query = timestamp. into_query( name) ;
109
131
#(
110
132
query = #fields;
111
133
) *
112
134
query
113
135
}
114
136
}
115
- } ;
116
- output. into ( )
137
+ } )
117
138
}
0 commit comments