Skip to content

Commit 563548c

Browse files
committed
expfmt: add NegotiateFormatsIncludingOpenMetrics
NegotiateIncludingOpenMetrics only returns the top Accept match, so servers that lack that format cannot fall back to the next recognised option without reimplementing negotiation. Return the full ordered list and keep the existing single-format helper as a thin wrapper. Signed-off-by: dpacgdm <dpac.gdm@gmail.com>
1 parent d28d382 commit 563548c

2 files changed

Lines changed: 56 additions & 7 deletions

File tree

expfmt/encode.go

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,21 @@ func Negotiate(h http.Header) Format {
9494
// temporary and will disappear once FmtOpenMetrics is fully supported and as
9595
// such may be negotiated by the normal Negotiate function.
9696
func NegotiateIncludingOpenMetrics(h http.Header) Format {
97+
formats := NegotiateFormatsIncludingOpenMetrics(h)
98+
return formats[0]
99+
}
100+
101+
// NegotiateFormatsIncludingOpenMetrics works like NegotiateIncludingOpenMetrics
102+
// but returns all recognised formats from the Accept header in descending
103+
// preference order instead of stopping at the first match. If no recognised
104+
// formats are found, the slice contains FmtText (with the negotiated escaping
105+
// scheme) as the default fallback.
106+
//
107+
// Callers that only support a subset of formats can iterate the result and pick
108+
// the first format they can serve.
109+
func NegotiateFormatsIncludingOpenMetrics(h http.Header) []Format {
97110
escapingScheme := Format(fmt.Sprintf("; escaping=%s", Format(model.NameEscapingScheme.String())))
111+
var formats []Format
98112
for _, ac := range goautoneg.ParseAccept(h.Get(hdrAccept)) {
99113
if escapeParam := ac.Params[model.EscapingKey]; escapeParam != "" {
100114
switch Format(escapeParam) {
@@ -108,26 +122,31 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {
108122
if ac.Type+"/"+ac.SubType == ProtoType && ac.Params["proto"] == ProtoProtocol {
109123
switch ac.Params["encoding"] {
110124
case "delimited":
111-
return FmtProtoDelim + escapingScheme
125+
formats = append(formats, FmtProtoDelim+escapingScheme)
112126
case "text":
113-
return FmtProtoText + escapingScheme
127+
formats = append(formats, FmtProtoText+escapingScheme)
114128
case "compact-text":
115-
return FmtProtoCompact + escapingScheme
129+
formats = append(formats, FmtProtoCompact+escapingScheme)
116130
}
131+
continue
117132
}
118133
if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
119-
return FmtText + escapingScheme
134+
formats = append(formats, FmtText+escapingScheme)
135+
continue
120136
}
121137
if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
122138
switch ver {
123139
case OpenMetricsVersion_1_0_0:
124-
return FmtOpenMetrics_1_0_0 + escapingScheme
140+
formats = append(formats, FmtOpenMetrics_1_0_0+escapingScheme)
125141
default:
126-
return FmtOpenMetrics_0_0_1 + escapingScheme
142+
formats = append(formats, FmtOpenMetrics_0_0_1+escapingScheme)
127143
}
128144
}
129145
}
130-
return FmtText + escapingScheme
146+
if len(formats) == 0 {
147+
return []Format{FmtText + escapingScheme}
148+
}
149+
return formats
131150
}
132151

133152
// NewEncoder returns a new encoder based on content type negotiation. All

expfmt/encode_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,36 @@ func TestNegotiateIncludingOpenMetrics(t *testing.T) {
200200
}
201201
}
202202

203+
func TestNegotiateFormatsIncludingOpenMetrics(t *testing.T) {
204+
oldDefault := model.NameEscapingScheme
205+
model.NameEscapingScheme = model.ValueEncodingEscaping
206+
defer func() {
207+
model.NameEscapingScheme = oldDefault
208+
}()
209+
210+
h := http.Header{}
211+
h.Add(hdrAccept, "application/vnd.google.protobuf;proto=io.prometheus.client.MetricFamily;encoding=delimited;q=0.6,application/openmetrics-text;version=1.0.0;escaping=allow-utf-8;q=0.5,text/plain;version=0.0.4;q=0.2")
212+
213+
got := NegotiateFormatsIncludingOpenMetrics(h)
214+
want := []Format{
215+
FmtProtoDelim + "; escaping=values",
216+
FmtOpenMetrics_1_0_0 + "; escaping=allow-utf-8",
217+
FmtText + "; escaping=allow-utf-8",
218+
}
219+
if len(got) != len(want) {
220+
t.Fatalf("expected %d formats, got %d: %#v", len(want), len(got), got)
221+
}
222+
for i := range want {
223+
if got[i] != want[i] {
224+
t.Errorf("format[%d]: want %q, got %q", i, want[i], got[i])
225+
}
226+
}
227+
// First entry must match NegotiateIncludingOpenMetrics.
228+
if NegotiateIncludingOpenMetrics(h) != got[0] {
229+
t.Errorf("NegotiateIncludingOpenMetrics should return first negotiated format")
230+
}
231+
}
232+
203233
func TestEncode(t *testing.T) {
204234
metric1 := &dto.MetricFamily{
205235
Name: proto.String("foo_metric"),

0 commit comments

Comments
 (0)