-
Notifications
You must be signed in to change notification settings - Fork 84
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Override derived label value case #136
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ | |
use proc_macro::TokenStream; | ||
use proc_macro2::TokenStream as TokenStream2; | ||
use quote::quote; | ||
use syn::DeriveInput; | ||
use syn::{parse::Parse, DeriveInput, Ident, LitStr, Token}; | ||
|
||
/// Derive `prometheus_client::encoding::EncodeLabelSet`. | ||
#[proc_macro_derive(EncodeLabelSet, attributes(prometheus))] | ||
|
@@ -88,22 +88,74 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { | |
} | ||
|
||
/// Derive `prometheus_client::encoding::EncodeLabelValue`. | ||
#[proc_macro_derive(EncodeLabelValue)] | ||
/// | ||
/// This macro only applies to `enum`s and will panic if you attempt to use it on structs. | ||
/// | ||
/// At the enum level you can use `#[prometheus(value_case = "lower")]` or `"upper"` to set the | ||
/// default case of the enum variants. | ||
/// | ||
/// ```rust | ||
/// # use prometheus_client::encoding::EncodeLabelValue; | ||
/// #[derive(Clone, Hash, PartialEq, Eq, EncodeLabelValue, Debug)] | ||
/// #[prometheus(value_case = "upper")] | ||
/// enum Method { | ||
/// Get, | ||
/// Put, | ||
/// } | ||
/// ``` | ||
/// | ||
/// Will encode to label values "GET" and "PUT" in prometheus metrics. | ||
/// | ||
/// For variants you can use `#[prometheus(lower)]` or `#[prometheus(upper)]` to set the case for | ||
/// only that variant. | ||
#[proc_macro_derive(EncodeLabelValue, attributes(prometheus))] | ||
pub fn derive_encode_label_value(input: TokenStream) -> TokenStream { | ||
let ast: DeriveInput = syn::parse(input).unwrap(); | ||
let name = &ast.ident; | ||
|
||
let config: LabelConfig = ast | ||
.attrs | ||
.iter() | ||
.find_map(|attr| { | ||
if attr.path.is_ident("prometheus") { | ||
match attr.parse_args::<LabelConfig>() { | ||
Ok(config) => Some(config), | ||
Err(e) => panic!("invalid prometheus attribute: {e}"), | ||
} | ||
} else { | ||
None | ||
} | ||
}) | ||
.unwrap_or_default(); | ||
|
||
let body = match ast.clone().data { | ||
syn::Data::Struct(_) => { | ||
panic!("Can not derive EncodeLabel for struct.") | ||
panic!("Can not derive EncodeLabelValue for struct.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🙏 |
||
} | ||
syn::Data::Enum(syn::DataEnum { variants, .. }) => { | ||
let match_arms: TokenStream2 = variants | ||
.into_iter() | ||
.map(|v| { | ||
let ident = v.ident; | ||
|
||
let attribute = v | ||
.attrs | ||
.iter() | ||
.find(|a| a.path.is_ident("prometheus")) | ||
.map(|a| a.parse_args::<syn::Ident>().unwrap().to_string()); | ||
let case = match attribute.as_deref() { | ||
Some("lower") => ValueCase::Lower, | ||
Some("upper") => ValueCase::Upper, | ||
Some(other) => { | ||
panic!("Provided attribute '{other}', but only 'lower' and 'upper' are supported") | ||
} | ||
None => config.value_case.clone(), | ||
}; | ||
|
||
let value = case.apply(&ident); | ||
|
||
quote! { | ||
#name::#ident => encoder.write_str(stringify!(#ident))?, | ||
#name::#ident => encoder.write_str(stringify!(#value))?, | ||
} | ||
}) | ||
.collect(); | ||
|
@@ -114,7 +166,7 @@ pub fn derive_encode_label_value(input: TokenStream) -> TokenStream { | |
} | ||
} | ||
} | ||
syn::Data::Union(_) => panic!("Can not derive Encode for union."), | ||
syn::Data::Union(_) => panic!("Can not derive EncodeLabelValue for union."), | ||
}; | ||
|
||
let gen = quote! { | ||
|
@@ -132,6 +184,77 @@ pub fn derive_encode_label_value(input: TokenStream) -> TokenStream { | |
gen.into() | ||
} | ||
|
||
#[derive(Clone)] | ||
enum ValueCase { | ||
Lower, | ||
Upper, | ||
NoChange, | ||
} | ||
|
||
impl ValueCase { | ||
fn apply(&self, ident: &Ident) -> Ident { | ||
match self { | ||
ValueCase::Lower => Ident::new(&ident.to_string().to_lowercase(), ident.span()), | ||
ValueCase::Upper => Ident::new(&ident.to_string().to_uppercase(), ident.span()), | ||
ValueCase::NoChange => ident.clone(), | ||
} | ||
} | ||
} | ||
|
||
struct LabelConfig { | ||
value_case: ValueCase, | ||
} | ||
|
||
impl Default for LabelConfig { | ||
fn default() -> Self { | ||
Self { | ||
value_case: ValueCase::NoChange, | ||
} | ||
} | ||
} | ||
|
||
impl Parse for LabelConfig { | ||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> { | ||
let mut config = LabelConfig::default(); | ||
|
||
while input.peek(Ident) { | ||
let ident: Ident = input.parse()?; | ||
|
||
match ident.to_string().as_str() { | ||
"value_case" => { | ||
let _: Token![=] = input.parse()?; | ||
let case: LitStr = input.parse()?; | ||
|
||
match case.value().as_str() { | ||
"lower" => config.value_case = ValueCase::Lower, | ||
"upper" => config.value_case = ValueCase::Upper, | ||
invalid => { | ||
return Err(syn::Error::new( | ||
case.span(), | ||
format!( | ||
"value case may only be \"lower\" or \"upper\", not \"{invalid}\"" | ||
), | ||
)) | ||
Comment on lines
+229
to
+237
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To de-duplicate this code with the one above in line 146, how about implementing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It will take me some time, but I think I can do this |
||
} | ||
} | ||
} | ||
invalid => { | ||
return Err(syn::Error::new( | ||
ident.span(), | ||
format!("invalid prometheus attribute \"{invalid}\""), | ||
)) | ||
} | ||
} | ||
|
||
if input.peek(Token![,]) { | ||
let _: Token![,] = input.parse()?; | ||
} | ||
} | ||
|
||
Ok(config) | ||
} | ||
} | ||
|
||
// Copied from https://github.com/djc/askama (MIT and APACHE licensed) and | ||
// modified. | ||
static KEYWORD_IDENTIFIERS: [(&str, &str); 48] = [ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have a concrete use-case where one would upper or lower case a specific variant only?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do not
it might be better to remove this and wait for a motivated individual to plumb serde in to label generation which could provide all manner of derive customization
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you want me to remove per-variant case changing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes please remove. Thank you.