diff --git a/pkg/filter/pagination.go b/pkg/filter/pagination.go index b039649e0..84aa5d182 100644 --- a/pkg/filter/pagination.go +++ b/pkg/filter/pagination.go @@ -35,6 +35,10 @@ type ( desc []bool Reverse bool } + + pagingCursorValue struct { + v interface{} + } ) func NewPaging(limit uint, cursor string) (p Paging, err error) { @@ -68,6 +72,7 @@ func (p *PagingCursor) Values() []interface{} { return p.values } +// Stirng to implement Stringer and to get human-readable representation of the cursor func (p *PagingCursor) String() string { var o = "<" @@ -84,6 +89,7 @@ func (p *PagingCursor) String() string { return o + ">" } +// MarshalJSON serializes cursor struct as JSON and encodes it as base64 + adds quotes to be treated as JSON string func (p *PagingCursor) MarshalJSON() ([]byte, error) { buf, err := json.Marshal(struct { K []string @@ -117,7 +123,7 @@ func (p *PagingCursor) UnmarshalJSON(in []byte) error { var ( aux struct { K []string - V []interface{} + V []pagingCursorValue D []bool R bool } @@ -128,10 +134,15 @@ func (p *PagingCursor) UnmarshalJSON(in []byte) error { } p.keys = aux.K - p.values = aux.V p.desc = aux.D p.Reverse = aux.R + // json.Unmarshal treats uint64 in values ([]interface{}) as float64 and we don't like that. + p.values = make([]interface{}, len(aux.V)) + for i, v := range aux.V { + p.values[i] = v.v + } + return nil } @@ -166,3 +177,23 @@ func parseCursor(in string) (p *PagingCursor, err error) { return p, nil } + +// Making sure uint64 other int* values are properly unmarshaled +func (v *pagingCursorValue) UnmarshalJSON(in []byte) (err error) { + var ( + u uint64 + i int64 + ) + + if err = json.Unmarshal(in, &u); err == nil { + v.v = u + return + } + + if err = json.Unmarshal(in, &i); err == nil { + v.v = i + return + } + + return json.Unmarshal(in, &v.v) +} diff --git a/pkg/filter/pagination_test.go b/pkg/filter/pagination_test.go new file mode 100644 index 000000000..523e5d788 --- /dev/null +++ b/pkg/filter/pagination_test.go @@ -0,0 +1,64 @@ +package filter + +import ( + "fmt" + "github.com/stretchr/testify/require" + "testing" +) + +func Test_cursorEncDec(t *testing.T) { + var ( + req = require.New(t) + + id = uint64(201244712307261628) + + enc string + cur = &PagingCursor{} + dec = &PagingCursor{} + ) + + { + cur.Set("uint64", id, true) + cur.Set("string", "foo", true) + req.Len(cur.values, 2) + req.Equal(id, cur.values[0]) + req.Equal(fmt.Sprintf("", id), cur.String()) + } + + { + enc = cur.Encode() + req.NotEmpty(enc) + } + { + req.NoError(dec.Decode(enc[1 : len(enc)-1])) + req.Len(dec.values, 2) + req.Equal(id, dec.values[0]) + req.Equal("foo", dec.values[1]) + req.Equal(fmt.Sprintf("", id), cur.String()) + } +} + +func Test_cursorValueUnmarshal(t *testing.T) { + var ( + req = require.New(t) + pcv = &pagingCursorValue{} + ) + + req.NoError(pcv.UnmarshalJSON([]byte("201244712307261628"))) + req.Equal(pcv.v, uint64(201244712307261628)) + + req.NoError(pcv.UnmarshalJSON([]byte("42"))) + req.Equal(pcv.v, uint64(42)) + + req.NoError(pcv.UnmarshalJSON([]byte("-42"))) + req.Equal(pcv.v, int64(-42)) + + req.NoError(pcv.UnmarshalJSON([]byte("true"))) + req.Equal(pcv.v, true) + + req.NoError(pcv.UnmarshalJSON([]byte("42.42"))) + req.Equal(pcv.v, 42.42) + + req.NoError(pcv.UnmarshalJSON([]byte(`"foo"`))) + req.Equal(pcv.v, "foo") +}