3737//! assert_eq!(expected_msg, buffer);
3838//! ```
3939
40- use crate :: encoding:: { EncodeExemplarValue , EncodeLabelSet } ;
40+ use crate :: encoding:: { EncodeExemplarValue , EncodeLabelSet , NoLabelSet } ;
4141use crate :: metrics:: exemplar:: Exemplar ;
4242use crate :: metrics:: MetricType ;
4343use crate :: registry:: { Prefix , Registry , Unit } ;
@@ -324,7 +324,7 @@ impl<'a> MetricEncoder<'a> {
324324
325325 self . write_suffix ( "total" ) ?;
326326
327- self . encode_labels :: < ( ) > ( None ) ?;
327+ self . encode_labels :: < NoLabelSet > ( None ) ?;
328328
329329 v. encode (
330330 & mut CounterValueEncoder {
@@ -348,7 +348,7 @@ impl<'a> MetricEncoder<'a> {
348348 ) -> Result < ( ) , std:: fmt:: Error > {
349349 self . write_prefix_name_unit ( ) ?;
350350
351- self . encode_labels :: < ( ) > ( None ) ?;
351+ self . encode_labels :: < NoLabelSet > ( None ) ?;
352352
353353 v. encode (
354354 & mut GaugeValueEncoder {
@@ -404,14 +404,14 @@ impl<'a> MetricEncoder<'a> {
404404 ) -> Result < ( ) , std:: fmt:: Error > {
405405 self . write_prefix_name_unit ( ) ?;
406406 self . write_suffix ( "sum" ) ?;
407- self . encode_labels :: < ( ) > ( None ) ?;
407+ self . encode_labels :: < NoLabelSet > ( None ) ?;
408408 self . writer . write_str ( " " ) ?;
409409 self . writer . write_str ( dtoa:: Buffer :: new ( ) . format ( sum) ) ?;
410410 self . newline ( ) ?;
411411
412412 self . write_prefix_name_unit ( ) ?;
413413 self . write_suffix ( "count" ) ?;
414- self . encode_labels :: < ( ) > ( None ) ?;
414+ self . encode_labels :: < NoLabelSet > ( None ) ?;
415415 self . writer . write_str ( " " ) ?;
416416 self . writer . write_str ( itoa:: Buffer :: new ( ) . format ( count) ) ?;
417417 self . newline ( ) ?;
@@ -512,12 +512,37 @@ impl<'a> MetricEncoder<'a> {
512512 additional_labels. encode ( LabelSetEncoder :: new ( self . writer ) . into ( ) ) ?;
513513 }
514514
515- if let Some ( labels) = & self . family_labels {
516- if !self . const_labels . is_empty ( ) || additional_labels. is_some ( ) {
517- self . writer . write_str ( "," ) ?;
515+ /// Writer impl which prepends a comma on the first call to write output to the wrapped writer
516+ struct CommaPrependingWriter < ' a > {
517+ writer : & ' a mut dyn Write ,
518+ should_prepend : bool ,
519+ }
520+
521+ impl Write for CommaPrependingWriter < ' _ > {
522+ fn write_str ( & mut self , s : & str ) -> std:: fmt:: Result {
523+ if self . should_prepend {
524+ self . writer . write_char ( ',' ) ?;
525+ self . should_prepend = false ;
526+ }
527+ self . writer . write_str ( s)
518528 }
529+ }
519530
520- labels. encode ( LabelSetEncoder :: new ( self . writer ) . into ( ) ) ?;
531+ if let Some ( labels) = self . family_labels {
532+ // if const labels or additional labels have been written, a comma must be prepended before writing the family labels.
533+ // However, it could be the case that the family labels are `Some` and yet empty, so the comma should _only_
534+ // be prepended if one of the `Write` methods are actually called when attempting to write the family labels.
535+ // Therefore, wrap the writer on `Self` with a CommaPrependingWriter if other labels have been written and
536+ // there may be a need to prepend an extra comma before writing additional labels.
537+ if !self . const_labels . is_empty ( ) || additional_labels. is_some ( ) {
538+ let mut writer = CommaPrependingWriter {
539+ writer : self . writer ,
540+ should_prepend : true ,
541+ } ;
542+ labels. encode ( LabelSetEncoder :: new ( & mut writer) . into ( ) ) ?;
543+ } else {
544+ labels. encode ( LabelSetEncoder :: new ( self . writer ) . into ( ) ) ?;
545+ } ;
521546 }
522547
523548 self . writer . write_str ( "}" ) ?;
@@ -709,6 +734,7 @@ mod tests {
709734 use crate :: metrics:: { counter:: Counter , exemplar:: CounterWithExemplar } ;
710735 use pyo3:: { prelude:: * , types:: PyModule } ;
711736 use std:: borrow:: Cow ;
737+ use std:: fmt:: Error ;
712738 use std:: sync:: atomic:: { AtomicI32 , AtomicU32 } ;
713739
714740 #[ test]
@@ -899,6 +925,31 @@ mod tests {
899925 parse_with_python_client ( encoded) ;
900926 }
901927
928+ #[ test]
929+ fn encode_histogram_family_with_empty_struct_family_labels ( ) {
930+ let mut registry = Registry :: default ( ) ;
931+ let family =
932+ Family :: new_with_constructor ( || Histogram :: new ( exponential_buckets ( 1.0 , 2.0 , 10 ) ) ) ;
933+ registry. register ( "my_histogram" , "My histogram" , family. clone ( ) ) ;
934+
935+ #[ derive( Eq , PartialEq , Hash , Debug , Clone ) ]
936+ struct EmptyLabels { }
937+
938+ impl EncodeLabelSet for EmptyLabels {
939+ fn encode ( & self , _encoder : crate :: encoding:: LabelSetEncoder ) -> Result < ( ) , Error > {
940+ Ok ( ( ) )
941+ }
942+ }
943+
944+ family. get_or_create ( & EmptyLabels { } ) . observe ( 1.0 ) ;
945+
946+ let mut encoded = String :: new ( ) ;
947+
948+ encode ( & mut encoded, & registry) . unwrap ( ) ;
949+
950+ parse_with_python_client ( encoded) ;
951+ }
952+
902953 #[ test]
903954 fn encode_histogram_with_exemplars ( ) {
904955 let mut registry = Registry :: default ( ) ;
0 commit comments