Repository: prometheus/common (go)

Issue:
Content negotiation is broken for expfmt and for promhttp
According to [this comment](https://github.com/open-telemetry/opentelemetry-collector-contrib/issues/18913#issuecomment-1444579266) on OTel's Prometheus exporter `expfmt` and `promhttp` are returning the wrong format if `openmetrics-text 1.0.0` is requested. It seems `openmetrics-text 1.0.0` is not supported only `openmetrics-text 0.0.1` is.


If a user requests `openmetrics-text 1.0.0`:
```
Accept: application/openmetrics-text; version=1.0.0; charset=utf-8
```
The expected result should be in the OpenMetrics format, also the content-type should be:
```
Content-Type: application/openmetrics-text; version=1.0.0; charset=utf-8
```
But the result is in Prometheus plaintext format, the content-type says the same:
```
Content-Type: text/plain; version=0.0.4; charset=utf-8
```
If a user requests `openmetrics-text 0.0.1`:
```
Accept: application/openmetrics-text; version=0.0.1; charset=utf-8
```
The result is in the expected OpenMetrics format:
```
Content-Type: application/openmetrics-text; version=0.0.1; charset=utf-8
```

As far as I know, `0.0.1` should not be used, only `1.0.0`.
According to the [OpenMetrics specs](https://github.com/OpenObservability/OpenMetrics/blob/main/specification/OpenMetrics.md#overall-structure):
>The content type MUST be: application/openmetrics-text; version=1.0.0; charset=utf-8

Also the Prometheus server uses 1.0.0 for scraping: https://github.com/prometheus/prometheus/issues/9430

Pull request:
Negotiate OM v1.0.0
Also return OM v1.0.0, and not v0.0.1

Fixes #456 

Patch:
diff --git a/expfmt/encode.go b/expfmt/encode.go
index 64dc0eb..e2d03ea 100644
--- a/expfmt/encode.go
+++ b/expfmt/encode.go
@@ -99,8 +99,11 @@ func NegotiateIncludingOpenMetrics(h http.Header) Format {
 		if ac.Type == "text" && ac.SubType == "plain" && (ver == TextVersion || ver == "") {
 			return FmtText
 		}
-		if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion || ver == "") {
-			return FmtOpenMetrics
+		if ac.Type+"/"+ac.SubType == OpenMetricsType && (ver == OpenMetricsVersion_0_0_1 || ver == OpenMetricsVersion_1_0_0 || ver == "") {
+			if ver == OpenMetricsVersion_1_0_0 {
+				return FmtOpenMetrics_1_0_0
+			}
+			return FmtOpenMetrics_0_0_1
 		}
 	}
 	return FmtText
@@ -146,7 +149,7 @@ func NewEncoder(w io.Writer, format Format) Encoder {
 			},
 			close: func() error { return nil },
 		}
-	case FmtOpenMetrics:
+	case FmtOpenMetrics_0_0_1, FmtOpenMetrics_1_0_0:
 		return encoderCloser{
 			encode: func(v *dto.MetricFamily) error {
 				_, err := MetricFamilyToOpenMetrics(w, v)
diff --git a/expfmt/expfmt.go b/expfmt/expfmt.go
index 0f176fa..c4cb20f 100644
--- a/expfmt/expfmt.go
+++ b/expfmt/expfmt.go
@@ -19,20 +19,22 @@ type Format string
 
 // Constants to assemble the Content-Type values for the different wire protocols.
 const (
-	TextVersion        = "0.0.4"
-	ProtoType          = `application/vnd.google.protobuf`
-	ProtoProtocol      = `io.prometheus.client.MetricFamily`
-	ProtoFmt           = ProtoType + "; proto=" + ProtoProtocol + ";"
-	OpenMetricsType    = `application/openmetrics-text`
-	OpenMetricsVersion = "0.0.1"
+	TextVersion              = "0.0.4"
+	ProtoType                = `application/vnd.google.protobuf`
+	ProtoProtocol            = `io.prometheus.client.MetricFamily`
+	ProtoFmt                 = ProtoType + "; proto=" + ProtoProtocol + ";"
+	OpenMetricsType          = `application/openmetrics-text`
+	OpenMetricsVersion_0_0_1 = "0.0.1"
+	OpenMetricsVersion_1_0_0 = "1.0.0"
 
 	// The Content-Type values for the different wire protocols.
-	FmtUnknown      Format = `<unknown>`
-	FmtText         Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
-	FmtProtoDelim   Format = ProtoFmt + ` encoding=delimited`
-	FmtProtoText    Format = ProtoFmt + ` encoding=text`
-	FmtProtoCompact Format = ProtoFmt + ` encoding=compact-text`
-	FmtOpenMetrics  Format = OpenMetricsType + `; version=` + OpenMetricsVersion + `; charset=utf-8`
+	FmtUnknown           Format = `<unknown>`
+	FmtText              Format = `text/plain; version=` + TextVersion + `; charset=utf-8`
+	FmtProtoDelim        Format = ProtoFmt + ` encoding=delimited`
+	FmtProtoText         Format = ProtoFmt + ` encoding=text`
+	FmtProtoCompact      Format = ProtoFmt + ` encoding=compact-text`
+	FmtOpenMetrics_1_0_0 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_1_0_0 + `; charset=utf-8`
+	FmtOpenMetrics_0_0_1 Format = OpenMetricsType + `; version=` + OpenMetricsVersion_0_0_1 + `; charset=utf-8`
 )
 
 const (


Test patch:
diff --git a/expfmt/encode_test.go b/expfmt/encode_test.go
index 63d0bd4..c79a7d7 100644
--- a/expfmt/encode_test.go
+++ b/expfmt/encode_test.go
@@ -63,6 +63,45 @@ func TestNegotiate(t *testing.T) {
 	}
 }
 
+func TestNegotiateOpenMetrics(t *testing.T) {
+	tests := []struct {
+		name              string
+		acceptHeaderValue string
+		expectedFmt       string
+	}{
+		{
+			name:              "OM format, no version",
+			acceptHeaderValue: "application/openmetrics-text",
+			expectedFmt:       string(FmtOpenMetrics_0_0_1),
+		},
+		{
+			name:              "OM format, 0.0.1 version",
+			acceptHeaderValue: "application/openmetrics-text;version=0.0.1",
+			expectedFmt:       string(FmtOpenMetrics_0_0_1),
+		},
+		{
+			name:              "OM format, 1.0.0 version",
+			acceptHeaderValue: "application/openmetrics-text;version=1.0.0",
+			expectedFmt:       string(FmtOpenMetrics_1_0_0),
+		},
+		{
+			name:              "OM format, invalid version",
+			acceptHeaderValue: "application/openmetrics-text;version=0.0.4",
+			expectedFmt:       string(FmtText),
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			h := http.Header{}
+			h.Add(hdrAccept, test.acceptHeaderValue)
+			actualFmt := string(NegotiateIncludingOpenMetrics(h))
+			if actualFmt != test.expectedFmt {
+				t.Errorf("expected Negotiate to return format %s, but got %s instead", test.expectedFmt, actualFmt)
+			}
+		})
+	}
+}
 func TestEncode(t *testing.T) {
 	var buff bytes.Buffer
 	delimEncoder := NewEncoder(&buff, FmtProtoDelim)