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