diff --git a/CHANGELOG.md b/CHANGELOG.md index 3b9d4f94..a0e32055 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [0.23.1] - unreleased +## [0.24.0] - unreleased + +### Added + +- `EncodeLabelSet` is now implemented for tuples `(A: EncodeLabelSet, B: EncodeLabelSet)`. + +### Changed + +- `EncodeLabelSet::encode()` now accepts a mutable reference to its encoder parameter. + +## [0.23.1] ### Changed diff --git a/Cargo.toml b/Cargo.toml index d04c8999..1dd43945 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus-client" -version = "0.23.1" +version = "0.24.0" authors = ["Max Inden "] edition = "2021" description = "Open Metrics client library allowing users to natively instrument applications." diff --git a/derive-encode/Cargo.toml b/derive-encode/Cargo.toml index f365b077..145c0159 100644 --- a/derive-encode/Cargo.toml +++ b/derive-encode/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "prometheus-client-derive-encode" -version = "0.4.2" +version = "0.5.0" authors = ["Max Inden "] edition = "2021" description = "Auxiliary crate to derive Encode trait from prometheus-client." diff --git a/derive-encode/src/lib.rs b/derive-encode/src/lib.rs index 1858cf8d..5b3cbe32 100644 --- a/derive-encode/src/lib.rs +++ b/derive-encode/src/lib.rs @@ -72,7 +72,7 @@ pub fn derive_encode_label_set(input: TokenStream) -> TokenStream { let gen = quote! { impl prometheus_client::encoding::EncodeLabelSet for #name { - fn encode(&self, mut encoder: prometheus_client::encoding::LabelSetEncoder) -> std::result::Result<(), std::fmt::Error> { + 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; diff --git a/src/encoding.rs b/src/encoding.rs index 9e2acac0..ae8420f6 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -203,7 +203,7 @@ impl MetricEncoder<'_> { /// An encodable label set. pub trait EncodeLabelSet { /// Encode oneself into the given encoder. - fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error>; + fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error>; } /// Encoder for a label set. @@ -238,19 +238,20 @@ impl LabelSetEncoder<'_> { } impl EncodeLabelSet for [T; N] { - fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { self.as_ref().encode(encoder) } } impl EncodeLabelSet for &[T] { - fn encode(&self, mut encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { if self.is_empty() { return Ok(()); } for label in self.iter() { - label.encode(encoder.encode_label())? + let encoder = encoder.encode_label(); + label.encode(encoder)? } Ok(()) @@ -258,17 +259,32 @@ impl EncodeLabelSet for &[T] { } impl EncodeLabelSet for Vec { - fn encode(&self, encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { self.as_slice().encode(encoder) } } +impl EncodeLabelSet for (A, B) +where + A: EncodeLabelSet, + B: EncodeLabelSet, +{ + fn encode(&self, encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { + let (a, b) = self; + + a.encode(encoder)?; + b.encode(encoder)?; + + Ok(()) + } +} + /// Uninhabited type to represent the lack of a label set for a metric #[derive(Debug)] pub enum NoLabelSet {} impl EncodeLabelSet for NoLabelSet { - fn encode(&self, _encoder: LabelSetEncoder) -> Result<(), std::fmt::Error> { + fn encode(&self, _encoder: &mut LabelSetEncoder) -> Result<(), std::fmt::Error> { Ok(()) } } diff --git a/src/encoding/text.rs b/src/encoding/text.rs index f10cf1d3..945124d7 100644 --- a/src/encoding/text.rs +++ b/src/encoding/text.rs @@ -296,8 +296,9 @@ pub(crate) struct MetricEncoder<'a> { impl std::fmt::Debug for MetricEncoder<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let mut labels = String::new(); + let mut encoder = LabelSetEncoder::new(&mut labels).into(); if let Some(l) = self.family_labels { - l.encode(LabelSetEncoder::new(&mut labels).into())?; + l.encode(&mut encoder)?; } f.debug_struct("Encoder") @@ -451,7 +452,7 @@ impl MetricEncoder<'_> { self.writer.write_str(" # {")?; exemplar .label_set - .encode(LabelSetEncoder::new(self.writer).into())?; + .encode(&mut LabelSetEncoder::new(self.writer).into())?; self.writer.write_str("} ")?; exemplar.value.encode( ExemplarValueEncoder { @@ -502,14 +503,14 @@ impl MetricEncoder<'_> { self.writer.write_str("{")?; self.const_labels - .encode(LabelSetEncoder::new(self.writer).into())?; + .encode(&mut LabelSetEncoder::new(self.writer).into())?; if let Some(additional_labels) = additional_labels { if !self.const_labels.is_empty() { self.writer.write_str(",")?; } - additional_labels.encode(LabelSetEncoder::new(self.writer).into())?; + additional_labels.encode(&mut LabelSetEncoder::new(self.writer).into())?; } /// Writer impl which prepends a comma on the first call to write output to the wrapped writer @@ -539,9 +540,9 @@ impl MetricEncoder<'_> { writer: self.writer, should_prepend: true, }; - labels.encode(LabelSetEncoder::new(&mut writer).into())?; + labels.encode(&mut LabelSetEncoder::new(&mut writer).into())?; } else { - labels.encode(LabelSetEncoder::new(self.writer).into())?; + labels.encode(&mut LabelSetEncoder::new(self.writer).into())?; }; } @@ -936,7 +937,7 @@ mod tests { struct EmptyLabels {} impl EncodeLabelSet for EmptyLabels { - fn encode(&self, _encoder: crate::encoding::LabelSetEncoder) -> Result<(), Error> { + fn encode(&self, _encoder: &mut crate::encoding::LabelSetEncoder) -> Result<(), Error> { Ok(()) } } @@ -1114,6 +1115,68 @@ mod tests { parse_with_python_client(encoded); } + #[test] + fn label_sets_can_be_composed() { + #[derive(Clone, Debug, Eq, Hash, PartialEq)] + struct Color(&'static str); + impl EncodeLabelSet for Color { + fn encode( + &self, + encoder: &mut crate::encoding::LabelSetEncoder, + ) -> Result<(), std::fmt::Error> { + use crate::encoding::EncodeLabel; + let Self(color) = *self; + let labels = ("color", color); + let encoder = encoder.encode_label(); + labels.encode(encoder) + } + } + + #[derive(Clone, Debug, Eq, Hash, PartialEq)] + struct Size(&'static str); + impl EncodeLabelSet for Size { + fn encode( + &self, + encoder: &mut crate::encoding::LabelSetEncoder, + ) -> Result<(), std::fmt::Error> { + use crate::encoding::EncodeLabel; + let Self(size) = *self; + let labels = ("size", size); + let encoder = encoder.encode_label(); + labels.encode(encoder) + } + } + + type Labels = (Color, Size); + + let mut registry = Registry::default(); + let family = Family::::default(); + registry.register("items", "Example metric", family.clone()); + + { + let labels = (Color("red"), Size("large")); + let counter = family.get_or_create(&labels); + counter.inc(); + } + { + let labels = (Color("blue"), Size("small")); + let counter = family.get_or_create(&labels); + counter.inc_by(2); + } + + let mut encoded = String::new(); + encode(&mut encoded, ®istry).unwrap(); + + let expected = "# HELP items Example metric.\n\ + # TYPE items counter\n\ + items_total{color=\"blue\",size=\"small\"} 2\n\ + items_total{color=\"red\",size=\"large\"} 1\n\ + # EOF\n"; + assert_eq!(expected, encoded); + + parse_with_python_client(encoded); + } + #[test] fn encode_registry_eof() { let mut orders_registry = Registry::default();