Merge pull request #13997 from bboreham/api-marshalling

bugfix: API: encode empty Vector/Matrix as [] not null
This commit is contained in:
Arthur Silva Sens 2024-04-29 15:26:01 -03:00 committed by GitHub
commit 3d42466894
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 77 additions and 38 deletions

View file

@ -15,7 +15,6 @@ package v1
import ( import (
"context" "context"
"encoding/json"
"errors" "errors"
"fmt" "fmt"
"io" "io"
@ -35,6 +34,7 @@ import (
"github.com/prometheus/prometheus/util/testutil" "github.com/prometheus/prometheus/util/testutil"
"github.com/go-kit/log" "github.com/go-kit/log"
jsoniter "github.com/json-iterator/go"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
config_util "github.com/prometheus/common/config" config_util "github.com/prometheus/common/config"
"github.com/prometheus/common/model" "github.com/prometheus/common/model"
@ -910,6 +910,7 @@ func TestStats(t *testing.T) {
require.IsType(t, &QueryData{}, i) require.IsType(t, &QueryData{}, i)
qd := i.(*QueryData) qd := i.(*QueryData)
require.NotNil(t, qd.Stats) require.NotNil(t, qd.Stats)
json := jsoniter.ConfigCompatibleWithStandardLibrary
j, err := json.Marshal(qd.Stats) j, err := json.Marshal(qd.Stats)
require.NoError(t, err) require.NoError(t, err)
require.JSONEq(t, `{"custom":"Custom Value"}`, string(j)) require.JSONEq(t, `{"custom":"Custom Value"}`, string(j))
@ -1171,6 +1172,25 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
}, },
}, },
}, },
// Test empty vector result
{
endpoint: api.query,
query: url.Values{
"query": []string{"bottomk(2, notExists)"},
},
responseAsJSON: `{"resultType":"vector","result":[]}`,
},
// Test empty matrix result
{
endpoint: api.queryRange,
query: url.Values{
"query": []string{"bottomk(2, notExists)"},
"start": []string{"0"},
"end": []string{"2"},
"step": []string{"1"},
},
responseAsJSON: `{"resultType":"matrix","result":[]}`,
},
// Missing query params in range queries. // Missing query params in range queries.
{ {
endpoint: api.queryRange, endpoint: api.queryRange,
@ -2891,10 +2911,13 @@ func testEndpoints(t *testing.T, api *API, tr *testTargetRetriever, es storage.E
if test.zeroFunc != nil { if test.zeroFunc != nil {
test.zeroFunc(res.data) test.zeroFunc(res.data)
} }
if test.response != nil {
assertAPIResponse(t, res.data, test.response) assertAPIResponse(t, res.data, test.response)
} }
}
if test.responseAsJSON != "" { if test.responseAsJSON != "" {
json := jsoniter.ConfigCompatibleWithStandardLibrary
s, err := json.Marshal(res.data) s, err := json.Marshal(res.data)
require.NoError(t, err) require.NoError(t, err)
require.JSONEq(t, test.responseAsJSON, string(s)) require.JSONEq(t, test.responseAsJSON, string(s))
@ -3292,18 +3315,7 @@ func TestRespondError(t *testing.T) {
require.Equal(t, want, have, "Return code %d expected in error response but got %d", want, have) require.Equal(t, want, have, "Return code %d expected in error response but got %d", want, have)
h := resp.Header.Get("Content-Type") h := resp.Header.Get("Content-Type")
require.Equal(t, "application/json", h, "Expected Content-Type %q but got %q", "application/json", h) require.Equal(t, "application/json", h, "Expected Content-Type %q but got %q", "application/json", h)
require.JSONEq(t, `{"status": "error", "data": "test", "errorType": "timeout", "error": "message"}`, string(body))
var res Response
err = json.Unmarshal(body, &res)
require.NoError(t, err, "Error unmarshaling JSON body")
exp := &Response{
Status: statusError,
Data: "test",
ErrorType: errorTimeout,
Error: "message",
}
require.Equal(t, exp, &res)
} }
func TestParseTimeParam(t *testing.T) { func TestParseTimeParam(t *testing.T) {

View file

@ -25,11 +25,13 @@ import (
) )
func init() { func init() {
jsoniter.RegisterTypeEncoderFunc("promql.Series", marshalSeriesJSON, marshalSeriesJSONIsEmpty) jsoniter.RegisterTypeEncoderFunc("promql.Vector", unsafeMarshalVectorJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("promql.Sample", marshalSampleJSON, marshalSampleJSONIsEmpty) jsoniter.RegisterTypeEncoderFunc("promql.Matrix", unsafeMarshalMatrixJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("promql.FPoint", marshalFPointJSON, marshalPointJSONIsEmpty) jsoniter.RegisterTypeEncoderFunc("promql.Series", unsafeMarshalSeriesJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("promql.HPoint", marshalHPointJSON, marshalPointJSONIsEmpty) jsoniter.RegisterTypeEncoderFunc("promql.Sample", unsafeMarshalSampleJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("exemplar.Exemplar", marshalExemplarJSON, marshalExemplarJSONEmpty) jsoniter.RegisterTypeEncoderFunc("promql.FPoint", unsafeMarshalFPointJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("promql.HPoint", unsafeMarshalHPointJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("exemplar.Exemplar", marshalExemplarJSON, neverEmpty)
jsoniter.RegisterTypeEncoderFunc("labels.Labels", unsafeMarshalLabelsJSON, labelsIsEmpty) jsoniter.RegisterTypeEncoderFunc("labels.Labels", unsafeMarshalLabelsJSON, labelsIsEmpty)
} }
@ -66,8 +68,12 @@ func (j JSONCodec) Encode(resp *Response) ([]byte, error) {
// < more histograms > // < more histograms >
// ], // ],
// }, // },
func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { func unsafeMarshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
s := *((*promql.Series)(ptr)) s := *((*promql.Series)(ptr))
marshalSeriesJSON(s, stream)
}
func marshalSeriesJSON(s promql.Series, stream *jsoniter.Stream) {
stream.WriteObjectStart() stream.WriteObjectStart()
stream.WriteObjectField(`metric`) stream.WriteObjectField(`metric`)
marshalLabelsJSON(s.Metric, stream) marshalLabelsJSON(s.Metric, stream)
@ -78,7 +84,7 @@ func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
stream.WriteObjectField(`values`) stream.WriteObjectField(`values`)
stream.WriteArrayStart() stream.WriteArrayStart()
} }
marshalFPointJSON(unsafe.Pointer(&p), stream) marshalFPointJSON(p, stream)
} }
if len(s.Floats) > 0 { if len(s.Floats) > 0 {
stream.WriteArrayEnd() stream.WriteArrayEnd()
@ -89,7 +95,7 @@ func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
stream.WriteObjectField(`histograms`) stream.WriteObjectField(`histograms`)
stream.WriteArrayStart() stream.WriteArrayStart()
} }
marshalHPointJSON(unsafe.Pointer(&p), stream) marshalHPointJSON(p, stream)
} }
if len(s.Histograms) > 0 { if len(s.Histograms) > 0 {
stream.WriteArrayEnd() stream.WriteArrayEnd()
@ -97,7 +103,8 @@ func marshalSeriesJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
stream.WriteObjectEnd() stream.WriteObjectEnd()
} }
func marshalSeriesJSONIsEmpty(unsafe.Pointer) bool { // In the Prometheus API we render an empty object as `[]` or similar.
func neverEmpty(unsafe.Pointer) bool {
return false return false
} }
@ -122,8 +129,12 @@ func marshalSeriesJSONIsEmpty(unsafe.Pointer) bool {
// }, // },
// "histogram": [ 1435781451.781, { < histogram, see jsonutil.MarshalHistogram > } ] // "histogram": [ 1435781451.781, { < histogram, see jsonutil.MarshalHistogram > } ]
// }, // },
func marshalSampleJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { func unsafeMarshalSampleJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
s := *((*promql.Sample)(ptr)) s := *((*promql.Sample)(ptr))
marshalSampleJSON(s, stream)
}
func marshalSampleJSON(s promql.Sample, stream *jsoniter.Stream) {
stream.WriteObjectStart() stream.WriteObjectStart()
stream.WriteObjectField(`metric`) stream.WriteObjectField(`metric`)
marshalLabelsJSON(s.Metric, stream) marshalLabelsJSON(s.Metric, stream)
@ -145,13 +156,13 @@ func marshalSampleJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
stream.WriteObjectEnd() stream.WriteObjectEnd()
} }
func marshalSampleJSONIsEmpty(unsafe.Pointer) bool { // marshalFPointJSON writes `[ts, "1.234"]`.
return false func unsafeMarshalFPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
p := *((*promql.FPoint)(ptr))
marshalFPointJSON(p, stream)
} }
// marshalFPointJSON writes `[ts, "1.234"]`. func marshalFPointJSON(p promql.FPoint, stream *jsoniter.Stream) {
func marshalFPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
p := *((*promql.FPoint)(ptr))
stream.WriteArrayStart() stream.WriteArrayStart()
jsonutil.MarshalTimestamp(p.T, stream) jsonutil.MarshalTimestamp(p.T, stream)
stream.WriteMore() stream.WriteMore()
@ -160,8 +171,12 @@ func marshalFPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
} }
// marshalHPointJSON writes `[ts, { < histogram, see jsonutil.MarshalHistogram > } ]`. // marshalHPointJSON writes `[ts, { < histogram, see jsonutil.MarshalHistogram > } ]`.
func marshalHPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { func unsafeMarshalHPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
p := *((*promql.HPoint)(ptr)) p := *((*promql.HPoint)(ptr))
marshalHPointJSON(p, stream)
}
func marshalHPointJSON(p promql.HPoint, stream *jsoniter.Stream) {
stream.WriteArrayStart() stream.WriteArrayStart()
jsonutil.MarshalTimestamp(p.T, stream) jsonutil.MarshalTimestamp(p.T, stream)
stream.WriteMore() stream.WriteMore()
@ -169,10 +184,6 @@ func marshalHPointJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
stream.WriteArrayEnd() stream.WriteArrayEnd()
} }
func marshalPointJSONIsEmpty(unsafe.Pointer) bool {
return false
}
// marshalExemplarJSON writes. // marshalExemplarJSON writes.
// //
// { // {
@ -201,10 +212,6 @@ func marshalExemplarJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
stream.WriteObjectEnd() stream.WriteObjectEnd()
} }
func marshalExemplarJSONEmpty(unsafe.Pointer) bool {
return false
}
func unsafeMarshalLabelsJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) { func unsafeMarshalLabelsJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
labelsPtr := (*labels.Labels)(ptr) labelsPtr := (*labels.Labels)(ptr)
marshalLabelsJSON(*labelsPtr, stream) marshalLabelsJSON(*labelsPtr, stream)
@ -229,3 +236,23 @@ func labelsIsEmpty(ptr unsafe.Pointer) bool {
labelsPtr := (*labels.Labels)(ptr) labelsPtr := (*labels.Labels)(ptr)
return labelsPtr.IsEmpty() return labelsPtr.IsEmpty()
} }
// Marshal a Vector as `[sample,sample,...]` - empty Vector is `[]`.
func unsafeMarshalVectorJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
v := *((*promql.Vector)(ptr))
stream.WriteArrayStart()
for _, s := range v {
marshalSampleJSON(s, stream)
}
stream.WriteArrayEnd()
}
// Marshal a Matrix as `[series,series,...]` - empty Matrix is `[]`.
func unsafeMarshalMatrixJSON(ptr unsafe.Pointer, stream *jsoniter.Stream) {
m := *((*promql.Matrix)(ptr))
stream.WriteArrayStart()
for _, s := range m {
marshalSeriesJSON(s, stream)
}
stream.WriteArrayEnd()
}