client_rust icon indicating copy to clipboard operation
client_rust copied to clipboard

[IDEA]: Add helper for implementing `EncodeLabelSet` manually

Open nastynaz opened this issue 2 months ago • 3 comments

Problem

Manually implementing EncodeLabelSet for a struct currently involves a lot of boilerplate:

pub struct MyLabels {
    pub method: &'static str,  // or String, Cow<'static, str>, etc.
    pub status: u16
   // many other fields
}

impl EncodeLabelSet for MyLabels {
    fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), fmt::Error> {
        // label: method = self.method
        {
            let mut label = encoder.encode_label();
            let mut key = label.encode_label_key()?;
            // field name -> key string (handle raw-idents manually if you used them)
            EncodeLabelKey::encode("method", &mut key)?;

            let mut value = key.encode_label_value()?;
            EncodeLabelValue::encode(&self.method, &mut value)?;
            value.finish()?;
        }
        // label: status = self.status
        {
            let mut label = encoder.encode_label();
            let mut key = label.encode_label_key()?;
            EncodeLabelKey::encode("status", &mut key)?;
            let mut value = key.encode_label_value()?;
            EncodeLabelValue::encode(&self.status, &mut value)?;
            value.finish()?;
        }

        // repeated for all other fields

        Ok(())
    }
}

This can become a chore if you have to implement it manually (e.g. because you want conditional encoding).

Solution

A new LabelSetWriter struct that allows you to easily specify (key, value) pairs to encode.

Usage

 pub struct MyLabels {
     pub status: u16,
     pub method: &'static str,
     pub optional: Option<&'static str>,
     // suppose we want to flatten these (like `#[prometheus(flatten)]`)
     pub common: CommonLabels
 }

 impl EncodeLabelSet for MyLabels {
     fn encode(&self, enc: &mut LabelSetEncoder) -> Result<(), fmt::Error> {
         let mut writer = LabelSetWriter::new(enc);
             .kv("status", &self.status)?
             .kv("method", &self.method)?;

         // allows for conditional encoding
         if let Some(optional) = &self.optional {
             writer.kv("optional", optional)?;
         }
        // allows flattening
         writer.flatten(&self.common)?;

         Ok(())
     }
 }

Discussion

I've written the code for LabelSetWriter and it's helpful for me in reducing boilerplate. Would anyone else find this useful? I'm open to making a PR if so.

nastynaz avatar Oct 23 '25 00:10 nastynaz

Interesting. Could one use this in https://github.com/prometheus/client_rust/tree/master/derive-encode as well?

mxinden avatar Oct 23 '25 08:10 mxinden

Yes. That's where I got the inspiration from. It could replace it.

nastynaz avatar Oct 23 '25 23:10 nastynaz

Replace the whole macro? Or use it in the macro. If the latter, want to open a draft pull request as a showcase?

mxinden avatar Oct 24 '25 13:10 mxinden