-
Notifications
You must be signed in to change notification settings - Fork 80
/
Copy pathwriteable.rs
147 lines (131 loc) · 4.3 KB
/
writeable.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use std::convert::TryFrom;
use syn::{
parse::{Parse, ParseStream},
punctuated::Punctuated,
Data, DeriveInput, Field, Fields, Ident, Meta, Token,
};
#[derive(Debug)]
struct WriteableField {
ident: Ident,
is_tag: bool,
is_ignore: bool,
}
mod kw {
use syn::custom_keyword;
custom_keyword!(tag);
custom_keyword!(ignore);
}
enum FieldAttr {
Tag(kw::tag),
Ignore(kw::ignore),
}
impl Parse for FieldAttr {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
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<FieldAttr, Token![,]>);
impl Parse for FieldAttrs {
fn parse(input: ParseStream<'_>) -> syn::Result<Self> {
Ok(Self(Punctuated::parse_terminated(input)?))
}
}
impl TryFrom<Field> for WriteableField {
type Error = syn::Error;
fn try_from(field: Field) -> syn::Result<WriteableField> {
let ident = match field.ident {
Some(i) => i,
None => {
return Err(syn::Error::new_spanned(
&field,
"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::<FieldAttrs>(list.tokens)?.0 {
match attr {
FieldAttr::Tag(_) => is_tag = true,
FieldAttr::Ignore(_) => is_ignore = true,
}
}
}
_ => {}
}
}
Ok(WriteableField {
ident,
is_tag,
is_ignore,
})
}
}
pub fn expand_writeable(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = input.ident;
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 = match fields {
Fields::Named(fields) => fields
.named
.into_iter()
.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::<syn::Result<Vec<_>>>()?,
_ => panic!("a struct without named fields is not supported"),
};
Ok(quote! {
impl #impl_generics ::influxdb::InfluxDbWriteable for #ident #ty_generics #where_clause {
fn into_query<I: Into<String>>(self, name: I) -> ::influxdb::WriteQuery {
let timestamp: ::influxdb::Timestamp = self.#time_field.into();
let mut query = timestamp.into_query(name);
#(
query = #fields;
)*
query
}
}
})
}