Add route for querying signing_info for all validators (#3952)
Also remove duplicate pagination: - move function to extract query params into types/rest - adjust pagination values locally until available in tendermint for validators Code cleanup: - helper function in test - fix pagination description in swagger.yaml - uint instead of int when possible Closes: #3226 Closes: #3991
This commit is contained in:
parent
e5897d8d91
commit
5bb6090e38
|
@ -0,0 +1 @@
|
|||
#3949 added /slashing/signing_infos to get signing_info for all validators
|
|
@ -785,6 +785,8 @@ func TestUnjail(t *testing.T) {
|
|||
require.Equal(t, true, signingInfo.IndexOffset > 0)
|
||||
require.Equal(t, time.Unix(0, 0).UTC(), signingInfo.JailedUntil)
|
||||
require.Equal(t, true, signingInfo.MissedBlocksCounter == 0)
|
||||
signingInfoList := getSigningInfoList(t, port)
|
||||
require.NotZero(t, len(signingInfoList))
|
||||
}
|
||||
|
||||
func TestProposalsQuery(t *testing.T) {
|
||||
|
|
|
@ -221,11 +221,11 @@ paths:
|
|||
required: true
|
||||
- in: query
|
||||
name: page
|
||||
description: Pagination page
|
||||
description: Page number
|
||||
type: integer
|
||||
- in: query
|
||||
name: size
|
||||
description: Pagination size
|
||||
name: limit
|
||||
description: Maximum number of items per page
|
||||
type: integer
|
||||
responses:
|
||||
200:
|
||||
|
@ -885,22 +885,45 @@ paths:
|
|||
200:
|
||||
description: OK
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
start_height:
|
||||
type: string
|
||||
index_offset:
|
||||
type: string
|
||||
jailed_until:
|
||||
type: string
|
||||
missed_blocks_counter:
|
||||
type: string
|
||||
$ref: "#/definitions/SigningInfo"
|
||||
204:
|
||||
description: No sign info of this validator
|
||||
400:
|
||||
description: Invalid validator public key
|
||||
500:
|
||||
description: Internal Server Error
|
||||
/slashing/signing_infos:
|
||||
get:
|
||||
summary: Get sign info of given all validators
|
||||
description: Get sign info of all validators
|
||||
produces:
|
||||
- application/json
|
||||
tags:
|
||||
- ICS23
|
||||
parameters:
|
||||
- in: query
|
||||
name: page
|
||||
description: Page number
|
||||
type: integer
|
||||
required: true
|
||||
- in: query
|
||||
name: limit
|
||||
description: Maximum number of items per page
|
||||
type: integer
|
||||
required: true
|
||||
responses:
|
||||
200:
|
||||
description: OK
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/SigningInfo"
|
||||
204:
|
||||
description: No validators with sign info
|
||||
400:
|
||||
description: Invalid validator public key for one of the validators
|
||||
500:
|
||||
description: Internal Server Error
|
||||
/slashing/validators/{validatorAddr}/unjail:
|
||||
post:
|
||||
summary: Unjail a jailed validator
|
||||
|
@ -2181,3 +2204,14 @@ definitions:
|
|||
type: array
|
||||
items:
|
||||
$ref: "#/definitions/Coin"
|
||||
SigningInfo:
|
||||
type: object
|
||||
properties:
|
||||
start_height:
|
||||
type: string
|
||||
index_offset:
|
||||
type: string
|
||||
jailed_until:
|
||||
type: string
|
||||
missed_blocks_counter:
|
||||
type: string
|
||||
|
|
|
@ -1392,6 +1392,21 @@ func getSigningInfo(t *testing.T, port string, validatorPubKey string) slashing.
|
|||
return signingInfo
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
// ICS 23 - SlashingList
|
||||
// ----------------------------------------------------------------------
|
||||
// GET /slashing/signing_infos Get sign info of all validators with pagination
|
||||
func getSigningInfoList(t *testing.T, port string) []slashing.ValidatorSigningInfo {
|
||||
res, body := Request(t, port, "GET", "/slashing/signing_infos?page=1&limit=1", nil)
|
||||
require.Equal(t, http.StatusOK, res.StatusCode, body)
|
||||
|
||||
var signingInfo []slashing.ValidatorSigningInfo
|
||||
err := cdc.UnmarshalJSON([]byte(body), &signingInfo)
|
||||
require.Nil(t, err)
|
||||
|
||||
return signingInfo
|
||||
}
|
||||
|
||||
// TODO: Test this functionality, it is not currently in any of the tests
|
||||
// POST /slashing/validators/{validatorAddr}/unjail Unjail a jailed validator
|
||||
func doUnjail(
|
||||
|
|
|
@ -45,7 +45,7 @@ func ValidatorCommand(cdc *codec.Codec) *cobra.Command {
|
|||
|
||||
cliCtx := context.NewCLIContext().WithCodec(cdc)
|
||||
|
||||
result, err := getValidators(cliCtx, height)
|
||||
result, err := GetValidators(cliCtx, height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -113,7 +113,7 @@ func bech32ValidatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error
|
|||
}, nil
|
||||
}
|
||||
|
||||
func getValidators(cliCtx context.CLIContext, height *int64) (ResultValidatorsOutput, error) {
|
||||
func GetValidators(cliCtx context.CLIContext, height *int64) (ResultValidatorsOutput, error) {
|
||||
// get the node
|
||||
node, err := cliCtx.GetNode()
|
||||
if err != nil {
|
||||
|
@ -170,7 +170,7 @@ func ValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerFunc {
|
|||
return
|
||||
}
|
||||
|
||||
output, err := getValidators(cliCtx, &height)
|
||||
output, err := GetValidators(cliCtx, &height)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
|
@ -188,7 +188,7 @@ func LatestValidatorSetRequestHandlerFn(cliCtx context.CLIContext) http.HandlerF
|
|||
return
|
||||
}
|
||||
|
||||
output, err := getValidators(cliCtx, &height)
|
||||
output, err := GetValidators(cliCtx, &height)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
|
|
|
@ -4,15 +4,13 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client"
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
rest "github.com/cosmos/cosmos-sdk/types/rest"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
|
@ -22,12 +20,10 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
flagTags = "tags"
|
||||
flagAny = "any"
|
||||
flagPage = "page"
|
||||
flagLimit = "limit"
|
||||
defaultPage = 1
|
||||
defaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
||||
flagTags = "tags"
|
||||
flagAny = "any"
|
||||
flagPage = "page"
|
||||
flagLimit = "limit"
|
||||
)
|
||||
|
||||
// default client command to search through tagged transactions
|
||||
|
@ -96,8 +92,8 @@ $ gaiacli query txs --tags '<tag1>:<value1>&<tag2>:<value2>' --page 1 --limit 30
|
|||
cmd.Flags().Bool(client.FlagTrustNode, false, "Trust connected full node (don't verify proofs for responses)")
|
||||
viper.BindPFlag(client.FlagTrustNode, cmd.Flags().Lookup(client.FlagTrustNode))
|
||||
cmd.Flags().String(flagTags, "", "tag:value list of tags that must match")
|
||||
cmd.Flags().Int32(flagPage, defaultPage, "Query a specific page of paginated results")
|
||||
cmd.Flags().Int32(flagLimit, defaultLimit, "Query number of transactions results per page returned")
|
||||
cmd.Flags().Int32(flagPage, rest.DefaultPage, "Query a specific page of paginated results")
|
||||
cmd.Flags().Int32(flagLimit, rest.DefaultLimit, "Query number of transactions results per page returned")
|
||||
cmd.MarkFlagRequired(flagTags)
|
||||
return cmd
|
||||
}
|
||||
|
@ -184,7 +180,7 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.
|
|||
return
|
||||
}
|
||||
|
||||
tags, page, limit, err = parseHTTPArgs(r)
|
||||
tags, page, limit, err = rest.ParseHTTPArgs(r)
|
||||
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
|
@ -200,51 +196,3 @@ func SearchTxRequestHandlerFn(cliCtx context.CLIContext, cdc *codec.Codec) http.
|
|||
rest.PostProcessResponse(w, cdc, txs, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
func parseHTTPArgs(r *http.Request) (tags []string, page, limit int, err error) {
|
||||
tags = make([]string, 0, len(r.Form))
|
||||
for key, values := range r.Form {
|
||||
if key == "page" || key == "limit" {
|
||||
continue
|
||||
}
|
||||
var value string
|
||||
value, err = url.QueryUnescape(values[0])
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
}
|
||||
|
||||
var tag string
|
||||
if key == types.TxHeightKey {
|
||||
tag = fmt.Sprintf("%s=%s", key, value)
|
||||
} else {
|
||||
tag = fmt.Sprintf("%s='%s'", key, value)
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
pageStr := r.FormValue("page")
|
||||
if pageStr == "" {
|
||||
page = defaultPage
|
||||
} else {
|
||||
page, err = strconv.Atoi(pageStr)
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
} else if page <= 0 {
|
||||
return tags, page, limit, errors.New("page must greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
limitStr := r.FormValue("limit")
|
||||
if limitStr == "" {
|
||||
limit = defaultLimit
|
||||
} else {
|
||||
limit, err = strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
} else if limit <= 0 {
|
||||
return tags, page, limit, errors.New("limit must greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
return tags, page, limit, nil
|
||||
}
|
||||
|
|
|
@ -3,9 +3,12 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/tendermint/tendermint/types"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
@ -13,6 +16,11 @@ import (
|
|||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultPage = 1
|
||||
DefaultLimit = 30 // should be consistent with tendermint/tendermint/rpc/core/pipe.go:19
|
||||
)
|
||||
|
||||
// GasEstimateResponse defines a response definition for tx gas estimation.
|
||||
type GasEstimateResponse struct {
|
||||
GasEstimate uint64 `json:"gas_estimate"`
|
||||
|
@ -211,3 +219,53 @@ func PostProcessResponse(w http.ResponseWriter, cdc *codec.Codec, response inter
|
|||
w.Header().Set("Content-Type", "application/json")
|
||||
_, _ = w.Write(output)
|
||||
}
|
||||
|
||||
// ParseHTTPArgs parses the request's URL and returns a slice containing all arguments pairs.
|
||||
// It separates page and limit used for pagination
|
||||
func ParseHTTPArgs(r *http.Request) (tags []string, page, limit int, err error) {
|
||||
tags = make([]string, 0, len(r.Form))
|
||||
for key, values := range r.Form {
|
||||
if key == "page" || key == "limit" {
|
||||
continue
|
||||
}
|
||||
var value string
|
||||
value, err = url.QueryUnescape(values[0])
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
}
|
||||
|
||||
var tag string
|
||||
if key == types.TxHeightKey {
|
||||
tag = fmt.Sprintf("%s=%s", key, value)
|
||||
} else {
|
||||
tag = fmt.Sprintf("%s='%s'", key, value)
|
||||
}
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
|
||||
pageStr := r.FormValue("page")
|
||||
if pageStr == "" {
|
||||
page = DefaultPage
|
||||
} else {
|
||||
page, err = strconv.Atoi(pageStr)
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
} else if page <= 0 {
|
||||
return tags, page, limit, errors.New("page must greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
limitStr := r.FormValue("limit")
|
||||
if limitStr == "" {
|
||||
limit = DefaultLimit
|
||||
} else {
|
||||
limit, err = strconv.Atoi(limitStr)
|
||||
if err != nil {
|
||||
return tags, page, limit, err
|
||||
} else if limit <= 0 {
|
||||
return tags, page, limit, errors.New("limit must greater than 0")
|
||||
}
|
||||
}
|
||||
|
||||
return tags, page, limit, nil
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
@ -55,3 +56,55 @@ func TestBaseReqValidateBasic(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHTTPArgs(t *testing.T) {
|
||||
req0 := mustNewRequest(t, "", "/", nil)
|
||||
req1 := mustNewRequest(t, "", "/?limit=5", nil)
|
||||
req2 := mustNewRequest(t, "", "/?page=5", nil)
|
||||
req3 := mustNewRequest(t, "", "/?page=5&limit=5", nil)
|
||||
|
||||
reqE1 := mustNewRequest(t, "", "/?page=-1", nil)
|
||||
reqE2 := mustNewRequest(t, "", "/?limit=-1", nil)
|
||||
req4 := mustNewRequest(t, "", "/?foo=faa", nil)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
req *http.Request
|
||||
w http.ResponseWriter
|
||||
tags []string
|
||||
page int
|
||||
limit int
|
||||
err bool
|
||||
}{
|
||||
{"no params", req0, httptest.NewRecorder(), []string{}, DefaultPage, DefaultLimit, false},
|
||||
{"Limit", req1, httptest.NewRecorder(), []string{}, DefaultPage, 5, false},
|
||||
{"Page", req2, httptest.NewRecorder(), []string{}, 5, DefaultLimit, false},
|
||||
{"Page and limit", req3, httptest.NewRecorder(), []string{}, 5, 5, false},
|
||||
|
||||
{"error page 0", reqE1, httptest.NewRecorder(), []string{}, DefaultPage, DefaultLimit, true},
|
||||
{"error limit 0", reqE2, httptest.NewRecorder(), []string{}, DefaultPage, DefaultLimit, true},
|
||||
|
||||
{"tags", req4, httptest.NewRecorder(), []string{"foo='faa'"}, DefaultPage, DefaultLimit, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tags, page, limit, err := ParseHTTPArgs(tt.req)
|
||||
if tt.err {
|
||||
require.NotNil(t, err)
|
||||
} else {
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, tt.tags, tags)
|
||||
require.Equal(t, tt.page, page)
|
||||
require.Equal(t, tt.limit, limit)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func mustNewRequest(t *testing.T, method, url string, body io.Reader) *http.Request {
|
||||
req, err := http.NewRequest(method, url, body)
|
||||
require.NoError(t, err)
|
||||
err = req.ParseForm()
|
||||
require.NoError(t, err)
|
||||
return req
|
||||
}
|
||||
|
|
|
@ -2,15 +2,14 @@ package rest
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/mux"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/client/context"
|
||||
"github.com/cosmos/cosmos-sdk/client/rpc"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/types/rest"
|
||||
"github.com/cosmos/cosmos-sdk/x/slashing"
|
||||
"github.com/gorilla/mux"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Codec) {
|
||||
|
@ -19,6 +18,11 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co
|
|||
signingInfoHandlerFn(cliCtx, slashing.StoreKey, cdc),
|
||||
).Methods("GET")
|
||||
|
||||
r.HandleFunc(
|
||||
"/slashing/signing_infos",
|
||||
signingInfoHandlerListFn(cliCtx, slashing.StoreKey, cdc),
|
||||
).Methods("GET").Queries("page", "{page}", "limit", "{limit}")
|
||||
|
||||
r.HandleFunc(
|
||||
"/slashing/parameters",
|
||||
queryParamsHandlerFn(cdc, cliCtx),
|
||||
|
@ -26,39 +30,79 @@ func registerQueryRoutes(cliCtx context.CLIContext, r *mux.Router, cdc *codec.Co
|
|||
}
|
||||
|
||||
// http request handler to query signing info
|
||||
// nolint: unparam
|
||||
func signingInfoHandlerFn(cliCtx context.CLIContext, storeName string, cdc *codec.Codec) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
pk, err := sdk.GetConsPubKeyBech32(vars["validatorPubKey"])
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(pk.Address()))
|
||||
signingInfo, code, err := getSigningInfo(cliCtx, storeName, cdc, pk.Address())
|
||||
|
||||
res, err := cliCtx.QueryStore(key, storeName)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
rest.WriteErrorResponse(w, code, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
if code == http.StatusNoContent {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
var signingInfo slashing.ValidatorSigningInfo
|
||||
rest.PostProcessResponse(w, cdc, signingInfo, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
err = cdc.UnmarshalBinaryLengthPrefixed(res, &signingInfo)
|
||||
// http request handler to query signing info
|
||||
func signingInfoHandlerListFn(cliCtx context.CLIContext, storeName string, cdc *codec.Codec) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
var signingInfoList []slashing.ValidatorSigningInfo
|
||||
|
||||
_, page, limit, err := rest.ParseHTTPArgs(r)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
height, err := rpc.GetChainHeight(cliCtx)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, cdc, signingInfo, cliCtx.Indent)
|
||||
validators, err := rpc.GetValidators(cliCtx, &height)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
if len(validators.Validators) == 0 {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: this should happen when querying Validators from RPC,
|
||||
// as soon as it's available this is not needed anymore
|
||||
// parameter page is (page-1) because ParseHTTPArgs starts with page 1, where our array start with 0
|
||||
start, end := adjustPagination(uint(len(validators.Validators)), uint(page)-1, uint(limit))
|
||||
for _, validator := range validators.Validators[start:end] {
|
||||
address := validator.Address
|
||||
signingInfo, code, err := getSigningInfo(cliCtx, storeName, cdc, address)
|
||||
if err != nil {
|
||||
rest.WriteErrorResponse(w, code, err.Error())
|
||||
return
|
||||
}
|
||||
signingInfoList = append(signingInfoList, signingInfo)
|
||||
}
|
||||
|
||||
if len(signingInfoList) == 0 {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
rest.PostProcessResponse(w, cdc, signingInfoList, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -75,3 +119,48 @@ func queryParamsHandlerFn(cdc *codec.Codec, cliCtx context.CLIContext) http.Hand
|
|||
rest.PostProcessResponse(w, cdc, res, cliCtx.Indent)
|
||||
}
|
||||
}
|
||||
|
||||
func getSigningInfo(cliCtx context.CLIContext, storeName string, cdc *codec.Codec, address []byte) (signingInfo slashing.ValidatorSigningInfo, code int, err error) {
|
||||
key := slashing.GetValidatorSigningInfoKey(sdk.ConsAddress(address))
|
||||
|
||||
res, err := cliCtx.QueryStore(key, storeName)
|
||||
if err != nil {
|
||||
code = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
||||
if len(res) == 0 {
|
||||
code = http.StatusNoContent
|
||||
return
|
||||
}
|
||||
|
||||
err = cdc.UnmarshalBinaryLengthPrefixed(res, &signingInfo)
|
||||
if err != nil {
|
||||
code = http.StatusInternalServerError
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// Adjust pagination with page starting from 0
|
||||
func adjustPagination(size, page, limit uint) (start uint, end uint) {
|
||||
// If someone asks for pages bigger than our dataset, just return everything
|
||||
if limit > size {
|
||||
return 0, size
|
||||
}
|
||||
|
||||
// Do pagination when healthy, fallback to 0
|
||||
start = 0
|
||||
if page*limit < size {
|
||||
start = page * limit
|
||||
}
|
||||
|
||||
// Do pagination only when healthy, fallback to len(dataset)
|
||||
end = size
|
||||
if start+limit <= size {
|
||||
end = start + limit
|
||||
}
|
||||
|
||||
return start, end
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
package rest
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdjustPagination(t *testing.T) {
|
||||
type args struct {
|
||||
s string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
size uint
|
||||
page uint
|
||||
limit uint
|
||||
start uint
|
||||
end uint
|
||||
}{
|
||||
{"Ok", 3, 0, 1, 0, 1},
|
||||
{"Limit too big", 3, 1, 5, 0, 3},
|
||||
{"Page over limit", 3, 2, 3, 0, 3},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
start, end := adjustPagination(tt.size, tt.page, tt.limit)
|
||||
require.Equal(t, tt.start, start)
|
||||
require.Equal(t, tt.end, end)
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue