Skip to main content

prometheus_client_derive_encode/
lib.rs

1#![deny(dead_code)]
2#![deny(missing_docs)]
3#![deny(unused)]
4#![forbid(unsafe_code)]
5#![warn(missing_debug_implementations)]
6
7//! Derive crate for `prometheus_client`.
8
9use proc_macro::TokenStream;
10use proc_macro2::TokenStream as TokenStream2;
11use quote::quote;
12use syn::DeriveInput;
13
14fn error_spanned<T: quote::ToTokens>(tokens: &T, msg: &str) -> TokenStream {
15    syn::Error::new_spanned(tokens, msg)
16        .to_compile_error()
17        .into()
18}
19
20/// Derive `prometheus_client::encoding::EncodeLabelSet`.
21#[proc_macro_derive(EncodeLabelSet, attributes(prometheus))]
22pub fn derive_encode_label_set(input: TokenStream) -> TokenStream {
23    let ast: DeriveInput = syn::parse(input).unwrap();
24    let name = &ast.ident;
25
26    let body: TokenStream2 = match ast.clone().data {
27        syn::Data::Struct(s) => match s.fields {
28            syn::Fields::Named(syn::FieldsNamed { named, .. }) => {
29                let body = named
30                    .into_iter()
31                    .map(|f| {
32                        let ident = f.ident.unwrap();
33                        let flatten = match f
34                            .attrs
35                            .iter()
36                            .find(|a| a.path().is_ident("prometheus"))
37                        {
38                            None => false,
39                            Some(a) => match a.parse_args::<syn::Ident>() {
40                                Ok(ident) if ident == "flatten" => true,
41                                Ok(other) => {
42                                    return Err(error_spanned(
43                                        &other,
44                                        &format!(
45                                            "Provided attribute '{other}', but only 'flatten' is supported"
46                                        ),
47                                    ));
48                                }
49                                Err(_) => {
50                                    return Err(error_spanned(
51                                        a,
52                                        "Attribute on `#[prometheus(...)]` must be an identifier, e.g. `#[prometheus(flatten)]`",
53                                    ));
54                                }
55                            },
56                        };
57
58                        if flatten {
59                            Ok(quote! {
60                                 EncodeLabelSet::encode(&self.#ident, encoder)?;
61                            })
62                        } else {
63                            let ident_string = KEYWORD_IDENTIFIERS
64                                .iter()
65                                .find(|pair| ident == pair.1)
66                                .map(|pair| pair.0.to_string())
67                                .unwrap_or_else(|| ident.to_string());
68
69                            Ok(quote! {
70                                let mut label_encoder = encoder.encode_label();
71                                let mut label_key_encoder = label_encoder.encode_label_key()?;
72                                EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?;
73
74                                let mut label_value_encoder = label_key_encoder.encode_label_value()?;
75                                EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?;
76
77                                label_value_encoder.finish()?;
78                            })
79                        }
80                    })
81                    .collect::<Result<TokenStream2, TokenStream>>();
82
83                match body {
84                    Ok(body) => body,
85                    Err(err) => return err,
86                }
87            }
88            syn::Fields::Unnamed(_) => {
89                return error_spanned(
90                    &ast,
91                    "Can not derive Encode for struct with unnamed fields.",
92                );
93            }
94            syn::Fields::Unit => {
95                return error_spanned(&ast, "Can not derive Encode for struct with unit field.");
96            }
97        },
98        syn::Data::Enum(syn::DataEnum { .. }) => {
99            return error_spanned(&ast, "Can not derive Encode for enum.");
100        }
101        syn::Data::Union(_) => {
102            return error_spanned(&ast, "Can not derive Encode for union.");
103        }
104    };
105
106    let gen = quote! {
107        impl ::prometheus_client::encoding::EncodeLabelSet for #name {
108            fn encode(&self, encoder: &mut ::prometheus_client::encoding::LabelSetEncoder) -> ::core::result::Result<(), ::core::fmt::Error> {
109                use ::prometheus_client::encoding::EncodeLabel;
110                use ::prometheus_client::encoding::EncodeLabelKey;
111                use ::prometheus_client::encoding::EncodeLabelValue;
112
113                #body
114
115                ::core::result::Result::Ok(())
116            }
117        }
118    };
119
120    gen.into()
121}
122
123/// Derive `prometheus_client::encoding::EncodeLabelValue`.
124#[proc_macro_derive(EncodeLabelValue)]
125pub fn derive_encode_label_value(input: TokenStream) -> TokenStream {
126    let ast: DeriveInput = syn::parse(input).unwrap();
127    let name = &ast.ident;
128
129    let body = match ast.clone().data {
130        syn::Data::Struct(_) => {
131            return error_spanned(&ast, "Can not derive EncodeLabel for struct.");
132        }
133        syn::Data::Enum(syn::DataEnum { variants, .. }) => {
134            let match_arms: TokenStream2 = variants
135                .into_iter()
136                .map(|v| {
137                    let ident = v.ident;
138                    quote! {
139                        #name::#ident => encoder.write_str(stringify!(#ident))?,
140                    }
141                })
142                .collect();
143
144            quote! {
145                match self {
146                    #match_arms
147                }
148            }
149        }
150        syn::Data::Union(_) => {
151            return error_spanned(&ast, "Can not derive Encode for union.");
152        }
153    };
154
155    let gen = quote! {
156        impl ::prometheus_client::encoding::EncodeLabelValue for #name {
157            fn encode(&self, encoder: &mut ::prometheus_client::encoding::LabelValueEncoder) -> ::core::result::Result<(), ::core::fmt::Error> {
158                use ::core::fmt::Write;
159
160                #body
161
162                ::core::result::Result::Ok(())
163            }
164        }
165    };
166
167    gen.into()
168}
169
170// Copied from https://github.com/djc/askama (MIT and APACHE licensed) and
171// modified.
172static KEYWORD_IDENTIFIERS: [(&str, &str); 48] = [
173    ("as", "r#as"),
174    ("break", "r#break"),
175    ("const", "r#const"),
176    ("continue", "r#continue"),
177    ("crate", "r#crate"),
178    ("else", "r#else"),
179    ("enum", "r#enum"),
180    ("extern", "r#extern"),
181    ("false", "r#false"),
182    ("fn", "r#fn"),
183    ("for", "r#for"),
184    ("if", "r#if"),
185    ("impl", "r#impl"),
186    ("in", "r#in"),
187    ("let", "r#let"),
188    ("loop", "r#loop"),
189    ("match", "r#match"),
190    ("mod", "r#mod"),
191    ("move", "r#move"),
192    ("mut", "r#mut"),
193    ("pub", "r#pub"),
194    ("ref", "r#ref"),
195    ("return", "r#return"),
196    ("static", "r#static"),
197    ("struct", "r#struct"),
198    ("trait", "r#trait"),
199    ("true", "r#true"),
200    ("type", "r#type"),
201    ("unsafe", "r#unsafe"),
202    ("use", "r#use"),
203    ("where", "r#where"),
204    ("while", "r#while"),
205    ("async", "r#async"),
206    ("await", "r#await"),
207    ("dyn", "r#dyn"),
208    ("abstract", "r#abstract"),
209    ("become", "r#become"),
210    ("box", "r#box"),
211    ("do", "r#do"),
212    ("final", "r#final"),
213    ("macro", "r#macro"),
214    ("override", "r#override"),
215    ("priv", "r#priv"),
216    ("typeof", "r#typeof"),
217    ("unsized", "r#unsized"),
218    ("virtual", "r#virtual"),
219    ("yield", "r#yield"),
220    ("try", "r#try"),
221];