1extern crate proc_macro;
2
3use proc_macro::TokenStream;
4use quote::{quote, quote_spanned};
5use syn;
6use syn::spanned::Spanned;
7
8use proc_macro_roids::{namespace_parameter, DeriveInputStructExt, FieldExt};
9
10#[proc_macro_derive(PointSerialize, attributes(point))]
11pub fn point_serialize_derive(input: TokenStream) -> TokenStream {
12 let namespace: syn::Path = syn::parse_quote!(point);
14 let field_path: syn::Path = syn::parse_quote!(field);
15 let tag_path: syn::Path = syn::parse_quote!(tag);
16 let timestamp_path: syn::Path = syn::parse_quote!(timestamp);
17
18 let ast = syn::parse_macro_input!(input as syn::DeriveInput);
20 let name = &ast.ident;
21
22 let measurement: String = if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue{
23 path, lit, ..
24 })) =
25 namespace_parameter(&ast.attrs, &namespace).expect("Missing measurement tag, use #[point(measurement = \"something\")] before struct declaration")
26 {
27 if path.segments[0].ident == "measurement" {
28 if let syn::Lit::Str(lit_str) = lit {
29 lit_str.value()
30 } else {
31 let span = lit.span();
32 return (quote_spanned! { span => compile_error!("Measurement should be a string"); }).into();
33 }
34 } else {
35 let span = path.segments[0].ident.span();
36 return (quote_spanned! { span => compile_error!("Top attribute is not measurement, which was expected") }).into();
37 }
38 } else {
39 let span = ast.attrs[0].path.segments[0].ident.span();
40 return (quote_spanned! { span => compile_error!("Did not find a suitable measurement tag should be in format '#[point(measurement = \"name\")]'"); }).into();
41 };
42
43 let ast_fields = ast.fields();
44
45 macro_rules! field_splitter {
46 ($names:ident, $tokens:ident, $field:ident) => {
47 let ident: &syn::Ident = &$field.ident.as_ref().unwrap();
48 let field_name: String =
49 if let syn::NestedMeta::Meta(syn::Meta::NameValue(syn::MetaNameValue {
50 lit, ..
51 })) = namespace_parameter(&$field.attrs, &namespace).unwrap()
52 {
53 if let syn::Lit::Str(lit_str) = lit {
54 lit_str.value()
55 } else {
56 let span = lit.span();
57 return (quote_spanned! { span => compile_error!("Attribute must be a string type"); })
58 .into();
59 }
60 } else {
61 ident.to_string()
62 };
63 $names.push(field_name);
64 $tokens.push(ident);
65 };
66 }
67
68 macro_rules! string_vec_joiner {
69 ($vec:ident, $quotes:expr) => {
70 $vec.iter()
71 .map(|it| {
72 if $quotes {
73 format!("{}={{:?}}", it)
74 } else {
75 format!("{}={{}}", it)
76 }
77 })
78 .collect::<Vec<String>>()
79 .join(",")
80 };
81 }
82
83 let mut field_names: Vec<String> = Vec::new();
85 let mut field_tokens: Vec<&syn::Ident> = Vec::new();
86 let fields = ast_fields
87 .iter()
88 .filter(|field| field.contains_tag(&namespace, &field_path));
89 if fields.clone().count() == 0 {
90 return (quote!{ compile_error!("Fields are not optional, there needs to be atleast one!"); }).into();
91 }
92 for field in fields {
93 field_splitter!(field_names, field_tokens, field);
94 }
95 let field_names_combined = string_vec_joiner!(field_names, true);
96
97 let mut tag_names: Vec<String> = Vec::new();
98 let mut tag_tokens: Vec<&syn::Ident> = Vec::new();
99 for field in ast_fields
100 .iter()
101 .filter(|field| field.contains_tag(&namespace, &tag_path))
102 {
103 field_splitter!(tag_names, tag_tokens, field);
104 }
105 let tag_names_combined = string_vec_joiner!(tag_names, false);
106
107 let complete_text = if tag_names_combined != "" {
108 format!("{{}},{} {}", tag_names_combined, field_names_combined)
109 } else {
110 format!("{{}} {}", field_names_combined)
111 };
112
113 let timestamp = ast_fields
114 .iter()
115 .find(|field| field.contains_tag(&namespace, ×tamp_path))
116 .expect("Missing timestamp field! Use #[point(timestamp)] over the timestamp field");
117
118 let timestamp_field = ×tamp.ty;
119 if "Timestamp" != quote! { #timestamp_field }.to_string() {
120 let span = timestamp.ty.span();
121 return (quote_spanned! { span => compile_error!("Timestamp field must have 'Timestamp' type!"); })
122 .into();
123 }
124
125 let struct_timestamp = timestamp.ident.as_ref().unwrap();
126
127 let tag_tokens_length = tag_tokens.len();
128
129 let serialize_with_timestamp = quote! {
130 fn serialize_with_timestamp(&self, timestamp: Option<Timestamp>) -> String {
131 match timestamp {
132 Some(timestamp) => format!("{} {}", self.serialize(), timestamp.to_string()),
133 None => format!("{} {}", self.serialize(), self.#struct_timestamp.to_string())
134 }
135 }
136 };
137
138 (if tag_tokens_length != 0 {
140 quote! {
141 impl PointSerialize for #name {
142 fn serialize(&self) -> String {
143 format!(#complete_text, #measurement, #(self.#tag_tokens),*, #(self.#field_tokens),*).to_string()
144 }
145 #serialize_with_timestamp
146 }
147 }
148 } else {
149 quote! {
150 impl PointSerialize for #name {
151 fn serialize(&self) -> String {
152 format!(#complete_text, #measurement, #(self.#field_tokens),*).to_string()
153 }
154 #serialize_with_timestamp
155 }
156 }
157 })
158 .into()
159}