From 4e3a2b2f98a7c1afce09203d3bff47e1491c73a6 Mon Sep 17 00:00:00 2001 From: Cristian George ANDREI Date: Wed, 10 Sep 2025 16:01:32 +0300 Subject: [PATCH 1/3] Add support for 'skip_encoding_if' for structs that derives EncodeLabelSet Signed-off-by: Cristian George ANDREI --- derive-encode/src/lib.rs | 88 +++++++++++++++++++++++++++------------- 1 file changed, 60 insertions(+), 28 deletions(-) diff --git a/derive-encode/src/lib.rs b/derive-encode/src/lib.rs index a3c0aac..916d93d 100644 --- a/derive-encode/src/lib.rs +++ b/derive-encode/src/lib.rs @@ -22,38 +22,67 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { syn::Fields::Named(syn::FieldsNamed { named, .. }) => named .into_iter() .map(|f| { - let attribute = f - .attrs + let ident = f.ident.unwrap(); + let ident_string = KEYWORD_IDENTIFIERS .iter() - .find(|a| a.path().is_ident("prometheus")) - .map(|a| a.parse_args::().unwrap().to_string()); - let flatten = match attribute.as_deref() { - Some("flatten") => true, - Some(other) => { - panic!("Provided attribute '{other}', but only 'flatten' is supported") + .find(|pair| ident == pair.1) + .map(|pair| pair.0.to_string()) + .unwrap_or_else(|| ident.to_string()); + + let mut flatten = false; + let mut skip_encoding_if_fn: Option = None; + + for attr in f.attrs.iter().filter(|a| a.path().is_ident("prometheus")) { + let result = attr.parse_nested_meta(|meta| { + if meta.path.is_ident("flatten") { + flatten = true; + return Ok(()); + } + + if meta.path.is_ident("skip_encoding_if") { + let lit: syn::LitStr = meta.value()?.parse()?; + match lit.parse::() { + Ok(path) => { + skip_encoding_if_fn = Some(path); + Ok(()) + } + Err(err) => Err(err), + }?; + return Ok(()); + } + + Err(meta.error("unsupported #[prometheus(..)] attribute")) + }); + + if let Err(err) = result { + return err.to_compile_error(); } - None => false, - }; - let ident = f.ident.unwrap(); + } + if flatten { quote! { - EncodeLabelSet::encode(&self.#ident, encoder)?; + prometheus_client::encoding::EncodeLabelSet::encode(&self.#ident, encoder)?; + } + } else if let Some(skip_fn) = skip_encoding_if_fn { + quote! { + if !(#skip_fn(&self.#ident)) { + let mut label_encoder = encoder.encode_label(); + let mut label_key_encoder = label_encoder.encode_label_key()?; + prometheus_client::encoding::EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; + + let mut label_value_encoder = label_key_encoder.encode_label_value()?; + prometheus_client::encoding::EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; + label_value_encoder.finish()?; + } } } else { - let ident_string = KEYWORD_IDENTIFIERS - .iter() - .find(|pair| ident == pair.1) - .map(|pair| pair.0.to_string()) - .unwrap_or_else(|| ident.to_string()); - quote! { let mut label_encoder = encoder.encode_label(); let mut label_key_encoder = label_encoder.encode_label_key()?; - EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; + prometheus_client::encoding::EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; let mut label_value_encoder = label_key_encoder.encode_label_value()?; - EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; - + prometheus_client::encoding::EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; label_value_encoder.finish()?; } } @@ -64,22 +93,25 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { } syn::Fields::Unit => panic!("Can not derive Encode for struct with unit field."), }, - syn::Data::Enum(syn::DataEnum { .. }) => { + syn::Data::Enum(_) => { panic!("Can not derive Encode for enum.") } syn::Data::Union(_) => panic!("Can not derive Encode for union."), }; let gen = quote! { - impl ::prometheus_client::encoding::EncodeLabelSet for #name { - fn encode(&self, encoder: &mut ::prometheus_client::encoding::LabelSetEncoder) -> ::core::result::Result<(), ::core::fmt::Error> { - use ::prometheus_client::encoding::EncodeLabel; - use ::prometheus_client::encoding::EncodeLabelKey; - use ::prometheus_client::encoding::EncodeLabelValue; + impl prometheus_client::encoding::EncodeLabelSet for #name { + fn encode( + &self, + encoder: &mut prometheus_client::encoding::LabelSetEncoder, + ) -> std::result::Result<(), std::fmt::Error> { + use prometheus_client::encoding::EncodeLabel; + use prometheus_client::encoding::EncodeLabelKey; + use prometheus_client::encoding::EncodeLabelValue; #body - ::core::result::Result::Ok(()) + Ok(()) } } }; From 2d0e07575f76c6299a49c2f1d44da53ec9e7c764 Mon Sep 17 00:00:00 2001 From: Cristian George ANDREI Date: Wed, 10 Sep 2025 16:29:48 +0300 Subject: [PATCH 2/3] Add test for skip_encoding_if Signed-off-by: Cristian George ANDREI --- derive-encode/tests/lib.rs | 55 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/derive-encode/tests/lib.rs b/derive-encode/tests/lib.rs index 5d0910f..1095460 100644 --- a/derive-encode/tests/lib.rs +++ b/derive-encode/tests/lib.rs @@ -206,6 +206,61 @@ fn flatten() { assert_eq!(expected, buffer); } +#[test] +fn skip_encoding_if() { + fn skip_empty_string(s: &String) -> bool { + s.is_empty() + } + + fn skip_zero(n: &u64) -> bool { + *n == 0 + } + + #[derive(EncodeLabelSet, Hash, Clone, Eq, PartialEq, Debug)] + struct Labels { + method: String, + #[prometheus(skip_encoding_if = "skip_empty_string")] + path: String, + #[prometheus(skip_encoding_if = "skip_zero")] + status_code: u64, + user_id: u64, + } + + let mut registry = Registry::default(); + let family = Family::::default(); + registry.register("my_counter", "This is my counter", family.clone()); + + family + .get_or_create(&Labels { + method: "GET".to_string(), + path: "".to_string(), // This should be skipped + status_code: 0, // This should be skipped + user_id: 123, + }) + .inc(); + + family + .get_or_create(&Labels { + method: "POST".to_string(), + path: "/api/users".to_string(), // This should not be skipped + status_code: 200, // This should not be skipped + user_id: 456, + }) + .inc(); + + let mut buffer = String::new(); + encode(&mut buffer, ®istry).unwrap(); + + assert!(buffer.contains("# HELP my_counter This is my counter.")); + assert!(buffer.contains("# TYPE my_counter counter")); + assert!(buffer.contains("my_counter_total{method=\"GET\",user_id=\"123\"} 1")); + assert!(buffer.contains("my_counter_total{method=\"POST\",path=\"/api/users\",status_code=\"200\",user_id=\"456\"} 1")); + assert!(buffer.contains("# EOF")); + + assert!(!buffer.contains("path=\"\"")); + assert!(!buffer.contains("status_code=\"0\"")); +} + #[test] fn build() { let t = trybuild::TestCases::new(); From e3942f074cc4651c228805d9e979bd2ecccbc779 Mon Sep 17 00:00:00 2001 From: Cristian George ANDREI Date: Thu, 11 Sep 2025 10:33:50 +0300 Subject: [PATCH 3/3] Use absolute paths Signed-off-by: Cristian George ANDREI --- derive-encode/src/lib.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/derive-encode/src/lib.rs b/derive-encode/src/lib.rs index 916d93d..0927972 100644 --- a/derive-encode/src/lib.rs +++ b/derive-encode/src/lib.rs @@ -61,17 +61,17 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { if flatten { quote! { - prometheus_client::encoding::EncodeLabelSet::encode(&self.#ident, encoder)?; + EncodeLabelSet::encode(&self.#ident, encoder)?; } } else if let Some(skip_fn) = skip_encoding_if_fn { quote! { if !(#skip_fn(&self.#ident)) { let mut label_encoder = encoder.encode_label(); let mut label_key_encoder = label_encoder.encode_label_key()?; - prometheus_client::encoding::EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; + EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; let mut label_value_encoder = label_key_encoder.encode_label_value()?; - prometheus_client::encoding::EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; + EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; label_value_encoder.finish()?; } } @@ -79,10 +79,10 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { quote! { let mut label_encoder = encoder.encode_label(); let mut label_key_encoder = label_encoder.encode_label_key()?; - prometheus_client::encoding::EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; + EncodeLabelKey::encode(&#ident_string, &mut label_key_encoder)?; let mut label_value_encoder = label_key_encoder.encode_label_value()?; - prometheus_client::encoding::EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; + EncodeLabelValue::encode(&self.#ident, &mut label_value_encoder)?; label_value_encoder.finish()?; } } @@ -100,18 +100,18 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { }; let gen = quote! { - impl prometheus_client::encoding::EncodeLabelSet for #name { + impl ::prometheus_client::encoding::EncodeLabelSet for #name { fn encode( &self, encoder: &mut prometheus_client::encoding::LabelSetEncoder, ) -> std::result::Result<(), std::fmt::Error> { - use prometheus_client::encoding::EncodeLabel; - use prometheus_client::encoding::EncodeLabelKey; - use prometheus_client::encoding::EncodeLabelValue; + use ::prometheus_client::encoding::EncodeLabel; + use ::prometheus_client::encoding::EncodeLabelKey; + use ::prometheus_client::encoding::EncodeLabelValue; #body - Ok(()) + ::core::result::Result::Ok(()) } } };