@@ -98,6 +98,111 @@ func TestNegotiate(t *testing.T) {
9898 }
9999}
100100
101+ func TestNegotiateFormats (t * testing.T ) {
102+ acceptValuePrefix := "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily"
103+ tests := []struct {
104+ name string
105+ acceptHeaderValues []string
106+ expectedFmts []string
107+ }{
108+ {
109+ name : "empty accept header returns FmtText fallback" ,
110+ acceptHeaderValues : []string {},
111+ expectedFmts : []string {"text/plain; version=0.0.4; charset=utf-8; escaping=underscores" },
112+ },
113+ {
114+ name : "single protobuf delimited" ,
115+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=delimited" },
116+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores" },
117+ },
118+ {
119+ name : "single protobuf text" ,
120+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=text" },
121+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=text; escaping=underscores" },
122+ },
123+ {
124+ name : "single protobuf compact-text" ,
125+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=compact-text" },
126+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=compact-text; escaping=underscores" },
127+ },
128+ {
129+ name : "plain text" ,
130+ acceptHeaderValues : []string {"text/plain;version=0.0.4" },
131+ expectedFmts : []string {"text/plain; version=0.0.4; charset=utf-8; escaping=underscores" },
132+ },
133+ {
134+ name : "openmetrics is not recognised" ,
135+ acceptHeaderValues : []string {"application/openmetrics-text;version=1.0.0" },
136+ expectedFmts : []string {"text/plain; version=0.0.4; charset=utf-8; escaping=underscores" },
137+ },
138+ {
139+ name : "multiple formats returned in preference order" ,
140+ acceptHeaderValues : []string {
141+ acceptValuePrefix + ";encoding=delimited;q=0.6" ,
142+ "text/plain;version=0.0.4;q=0.2" ,
143+ },
144+ expectedFmts : []string {
145+ "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores" ,
146+ "text/plain; version=0.0.4; charset=utf-8; escaping=underscores" ,
147+ },
148+ },
149+ {
150+ name : "multiple formats with openmetrics skipped" ,
151+ acceptHeaderValues : []string {
152+ acceptValuePrefix + ";encoding=delimited;q=0.6" ,
153+ "application/openmetrics-text;version=1.0.0;q=0.5" ,
154+ "text/plain;version=0.0.4;q=0.2" ,
155+ },
156+ expectedFmts : []string {
157+ "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores" ,
158+ "text/plain; version=0.0.4; charset=utf-8; escaping=underscores" ,
159+ },
160+ },
161+ {
162+ name : "escaping param is respected" ,
163+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=delimited;escaping=allow-utf-8" },
164+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=allow-utf-8" },
165+ },
166+ {
167+ name : "unknown escaping param falls back to default" ,
168+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=delimited;escaping=bogus" },
169+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores" },
170+ },
171+ {
172+ name : "single header with multiple comma-separated formats" ,
173+ acceptHeaderValues : []string {
174+ acceptValuePrefix + ";encoding=delimited;q=0.6," +
175+ "application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.5," +
176+ "text/plain;version=0.0.4;q=0.2" ,
177+ },
178+ expectedFmts : []string {
179+ "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores" ,
180+ "text/plain; version=0.0.4; charset=utf-8; escaping=underscores" ,
181+ },
182+ },
183+ }
184+
185+ oldDefault := model .NameEscapingScheme
186+ model .NameEscapingScheme = model .UnderscoreEscaping
187+ defer func () {
188+ model .NameEscapingScheme = oldDefault
189+ }()
190+
191+ for _ , test := range tests {
192+ t .Run (test .name , func (t * testing.T ) {
193+ h := http.Header {}
194+ for _ , v := range test .acceptHeaderValues {
195+ h .Add (hdrAccept , v )
196+ }
197+ actualFmts := NegotiateFormats (h )
198+ require .Len (t , actualFmts , len (test .expectedFmts ), "number of returned formats" )
199+ for i , expected := range test .expectedFmts {
200+ assert .Equal (t , expected , string (actualFmts [i ]), "format at index %d" , i )
201+ }
202+ })
203+ }
204+ }
205+
101206func TestNegotiateIncludingOpenMetrics (t * testing.T ) {
102207 acceptValuePrefix := "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily"
103208 tests := []struct {
@@ -200,6 +305,115 @@ func TestNegotiateIncludingOpenMetrics(t *testing.T) {
200305 }
201306}
202307
308+ func TestNegotiateFormatsIncludingOpenMetrics (t * testing.T ) {
309+ acceptValuePrefix := "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily"
310+ tests := []struct {
311+ name string
312+ acceptHeaderValues []string
313+ expectedFmts []string
314+ }{
315+ {
316+ name : "empty accept header returns FmtText fallback" ,
317+ acceptHeaderValues : []string {},
318+ expectedFmts : []string {"text/plain; version=0.0.4; charset=utf-8; escaping=values" },
319+ },
320+ {
321+ name : "single protobuf delimited" ,
322+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=delimited" },
323+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values" },
324+ },
325+ {
326+ name : "single OM 1.0.0" ,
327+ acceptHeaderValues : []string {"application/openmetrics-text;version=1.0.0" },
328+ expectedFmts : []string {"application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values" },
329+ },
330+ {
331+ name : "single OM 0.0.1" ,
332+ acceptHeaderValues : []string {"application/openmetrics-text;version=0.0.1" },
333+ expectedFmts : []string {"application/openmetrics-text; version=0.0.1; charset=utf-8; escaping=values" },
334+ },
335+ {
336+ name : "single OM no version" ,
337+ acceptHeaderValues : []string {"application/openmetrics-text" },
338+ expectedFmts : []string {"application/openmetrics-text; version=0.0.1; charset=utf-8; escaping=values" },
339+ },
340+ {
341+ name : "OM invalid version is not recognised" ,
342+ acceptHeaderValues : []string {"application/openmetrics-text;version=0.0.4" },
343+ expectedFmts : []string {"text/plain; version=0.0.4; charset=utf-8; escaping=values" },
344+ },
345+ {
346+ name : "multiple formats returned in preference order" ,
347+ acceptHeaderValues : []string {
348+ acceptValuePrefix + ";encoding=delimited;q=0.6" ,
349+ "application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.5" ,
350+ "text/plain;version=0.0.4;q=0.2" ,
351+ },
352+ expectedFmts : []string {
353+ "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values" ,
354+ "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=allow-utf-8" ,
355+ "text/plain; version=0.0.4; charset=utf-8; escaping=values" ,
356+ },
357+ },
358+ {
359+ name : "server supporting only OM and text/plain can skip protobuf" ,
360+ acceptHeaderValues : []string {
361+ acceptValuePrefix + ";encoding=delimited;q=0.6" ,
362+ "application/openmetrics-text;version=1.0.0;q=0.5" ,
363+ "text/plain;version=0.0.4;q=0.2" ,
364+ },
365+ expectedFmts : []string {
366+ "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values" ,
367+ "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=values" ,
368+ "text/plain; version=0.0.4; charset=utf-8; escaping=values" ,
369+ },
370+ },
371+ {
372+ name : "escaping param is respected" ,
373+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=delimited;escaping=underscores" },
374+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=underscores" },
375+ },
376+ {
377+ name : "unknown escaping param falls back to default" ,
378+ acceptHeaderValues : []string {acceptValuePrefix + ";encoding=delimited;escaping=bogus" },
379+ expectedFmts : []string {"application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values" },
380+ },
381+ {
382+ name : "single header with multiple comma-separated formats" ,
383+ acceptHeaderValues : []string {
384+ acceptValuePrefix + ";encoding=delimited;q=0.6," +
385+ "application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.5," +
386+ "text/plain;version=0.0.4;q=0.2" ,
387+ },
388+ expectedFmts : []string {
389+ "application/vnd.google.protobuf; proto=io.prometheus.client.MetricFamily; encoding=delimited; escaping=values" ,
390+ "application/openmetrics-text; version=1.0.0; charset=utf-8; escaping=allow-utf-8" ,
391+ "text/plain; version=0.0.4; charset=utf-8; escaping=values" ,
392+ },
393+ },
394+ }
395+
396+ oldDefault := model .NameEscapingScheme
397+ model .NameEscapingScheme = model .ValueEncodingEscaping
398+ defer func () {
399+ model .NameEscapingScheme = oldDefault
400+ }()
401+
402+ for _ , test := range tests {
403+ t .Run (test .name , func (t * testing.T ) {
404+ h := http.Header {}
405+ for _ , v := range test .acceptHeaderValues {
406+ h .Add (hdrAccept , v )
407+ }
408+ actualFmts := NegotiateFormatsIncludingOpenMetrics (h )
409+ require .Len (t , actualFmts , len (test .expectedFmts ), "number of returned formats" )
410+ for i , expected := range test .expectedFmts {
411+ assert .Equal (t , expected , string (actualFmts [i ]), "format at index %d" , i )
412+ }
413+ })
414+ }
415+ }
416+
203417func TestEncode (t * testing.T ) {
204418 metric1 := & dto.MetricFamily {
205419 Name : proto .String ("foo_metric" ),
0 commit comments