11use proc_macro2:: TokenStream ;
2- use quote:: { format_ident , quote} ;
2+ use quote:: quote;
33use syn:: {
44 parse:: { Parse , ParseStream } ,
55 punctuated:: Punctuated ,
@@ -9,27 +9,32 @@ use syn::{
99#[ derive( Debug ) ]
1010struct WriteableField {
1111 ident : Ident ,
12+ is_time : bool ,
1213 is_tag : bool ,
1314 is_ignore : bool ,
1415}
1516
1617mod kw {
1718 use syn:: custom_keyword;
1819
20+ custom_keyword ! ( time) ;
1921 custom_keyword ! ( tag) ;
2022 custom_keyword ! ( ignore) ;
2123}
2224
2325#[ allow( dead_code) ] // TODO do we need to store the keywords?
2426enum FieldAttr {
27+ Time ( kw:: time ) ,
2528 Tag ( kw:: tag ) ,
2629 Ignore ( kw:: ignore ) ,
2730}
2831
2932impl Parse for FieldAttr {
3033 fn parse ( input : ParseStream < ' _ > ) -> syn:: Result < Self > {
3134 let lookahead = input. lookahead1 ( ) ;
32- if lookahead. peek ( kw:: tag) {
35+ if lookahead. peek ( kw:: time) {
36+ Ok ( Self :: Time ( input. parse ( ) ?) )
37+ } else if lookahead. peek ( kw:: tag) {
3338 Ok ( Self :: Tag ( input. parse ( ) ?) )
3439 } else if lookahead. peek ( kw:: ignore) {
3540 Ok ( Self :: Ignore ( input. parse ( ) ?) )
@@ -52,6 +57,7 @@ impl TryFrom<Field> for WriteableField {
5257
5358 fn try_from ( field : Field ) -> syn:: Result < WriteableField > {
5459 let ident = field. ident . expect ( "fields without ident are not supported" ) ;
60+ let mut has_time_attr = false ;
5561 let mut is_tag = false ;
5662 let mut is_ignore = false ;
5763
@@ -60,6 +66,7 @@ impl TryFrom<Field> for WriteableField {
6066 Meta :: List ( list) if list. path . is_ident ( "influxdb" ) => {
6167 for attr in syn:: parse2 :: < FieldAttrs > ( list. tokens ) ?. 0 {
6268 match attr {
69+ FieldAttr :: Time ( _) => has_time_attr = true ,
6370 FieldAttr :: Tag ( _) => is_tag = true ,
6471 FieldAttr :: Ignore ( _) => is_ignore = true ,
6572 }
@@ -69,8 +76,23 @@ impl TryFrom<Field> for WriteableField {
6976 }
7077 }
7178
79+ if [ has_time_attr, is_tag, is_ignore]
80+ . iter ( )
81+ . filter ( |& & b| b)
82+ . count ( )
83+ > 1
84+ {
85+ panic ! ( "only one of time, tag, or ignore can be used" ) ;
86+ }
87+
88+ // A field is considered a time field if:
89+ // 1. It has the #[influxdb(time)] attribute, OR
90+ // 2. It's named "time" and doesn't have #[influxdb(ignore)]
91+ let is_time = has_time_attr || ( ident == "time" && !is_ignore) ;
92+
7293 Ok ( WriteableField {
7394 ident,
95+ is_time,
7496 is_tag,
7597 is_ignore,
7698 } )
@@ -97,39 +119,52 @@ pub fn expand_writeable(input: DeriveInput) -> syn::Result<TokenStream> {
97119 }
98120 } ;
99121
100- let time_field = format_ident ! ( "time" ) ;
101- let time_field_str = time_field. to_string ( ) ;
102- #[ allow( clippy:: cmp_owned) ] // that's not how idents work clippy
103- let fields = match fields {
122+ let writeable_fields: Vec < WriteableField > = match fields {
104123 Fields :: Named ( fields) => fields
105124 . named
106125 . into_iter ( )
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 ( )
121- } )
126+ . map ( WriteableField :: try_from)
122127 . collect :: < syn:: Result < Vec < _ > > > ( ) ?,
123- _ => panic ! ( "a struct without named fields is not supported" ) ,
128+ _ => panic ! ( "A struct without named fields is not supported! " ) ,
124129 } ;
125130
131+ // Find the time field
132+ let mut time_field = None ;
133+ for wf in & writeable_fields {
134+ if wf. is_time {
135+ if time_field. is_some ( ) {
136+ panic ! ( "multiple time fields found!" ) ;
137+ }
138+ time_field = Some ( wf. ident . clone ( ) ) ;
139+ }
140+ }
141+
142+ // There must be exactly one time field
143+ let time_field = time_field. expect ( "no time field found" ) ;
144+
145+ // Generate field assignments (excluding time and ignored fields)
146+ let field_assignments = writeable_fields
147+ . into_iter ( )
148+ . filter_map ( |wf| {
149+ if wf. is_ignore || wf. is_time {
150+ None
151+ } else {
152+ let ident = wf. ident ;
153+ Some ( match wf. is_tag {
154+ true => quote ! ( query. add_tag( stringify!( #ident) , self . #ident) ) ,
155+ false => quote ! ( query. add_field( stringify!( #ident) , self . #ident) ) ,
156+ } )
157+ }
158+ } )
159+ . collect :: < Vec < _ > > ( ) ;
160+
126161 Ok ( quote ! {
127162 impl #impl_generics :: influxdb:: InfluxDbWriteable for #ident #ty_generics #where_clause {
128163 fn into_query<I : Into <String >>( self , name: I ) -> :: influxdb:: WriteQuery {
129164 let timestamp: :: influxdb:: Timestamp = self . #time_field. into( ) ;
130165 let mut query = timestamp. into_query( name) ;
131166 #(
132- query = #fields ;
167+ query = #field_assignments ;
133168 ) *
134169 query
135170 }
0 commit comments