influxdb_derives/
lib.rs

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    // Paths
13    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    // Struct-level
19    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    // Field-level
84    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, &timestamp_path))
116        .expect("Missing timestamp field! Use #[point(timestamp)] over the timestamp field");
117
118    let timestamp_field = &timestamp.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    // Output
139    (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}