feat: api - GET /api/v2/versions/{versionCode}/servers/{serverKey}/tribes - add more sort options

This commit is contained in:
Dawid Wysokiński 2024-02-20 07:34:44 +01:00
parent e5d8ba5390
commit bf3a7b11e5
Signed by: Kichiyaki
GPG Key ID: B5445E357FB8B892
5 changed files with 300 additions and 1 deletions

View File

@ -142,6 +142,7 @@ paths:
- $ref: "#/components/parameters/CursorQueryParam"
- $ref: "#/components/parameters/LimitQueryParam"
- $ref: "#/components/parameters/TribeDeletedQueryParam"
- $ref: "#/components/parameters/TribeSortQueryParam"
responses:
200:
$ref: "#/components/responses/ListTribesResponse"
@ -949,6 +950,28 @@ components:
schema:
type: boolean
required: false
TribeSortQueryParam:
name: sort
in: query
description: Order matters!
schema:
type: array
items:
type: string
enum:
- odScoreAtt:ASC
- odScoreAtt:DESC
- odScoreDef:ASC
- odScoreDef:DESC
- odScoreTotal:ASC
- odScoreTotal:DESC
- points:ASC
- points:DESC
- dominance:ASC
- dominance:DESC
- deletedAt:ASC
- deletedAt:DESC
maxItems: 2
VersionCodePathParam:
in: path
name: versionCode

View File

@ -6,6 +6,7 @@ import (
"math"
"net/url"
"slices"
"strings"
"time"
)
@ -380,6 +381,40 @@ const (
TribeSortDeletedAtDESC
)
//nolint:gocyclo
func newTribeSortFromString(s string) (TribeSort, error) {
switch strings.ToLower(s) {
case "odscoreatt:asc":
return TribeSortODScoreAttASC, nil
case "odscoreatt:desc":
return TribeSortODScoreAttDESC, nil
case "odscoredef:asc":
return TribeSortODScoreDefASC, nil
case "odscoredef:desc":
return TribeSortODScoreDefDESC, nil
case "odscoretotal:asc":
return TribeSortODScoreTotalASC, nil
case "odscoretotal:desc":
return TribeSortODScoreTotalDESC, nil
case "points:asc":
return TribeSortPointsASC, nil
case "points:desc":
return TribeSortPointsDESC, nil
case "dominance:asc":
return TribeSortDominanceASC, nil
case "dominance:desc":
return TribeSortDominanceDESC, nil
case "deletedat:asc":
return TribeSortDeletedAtASC, nil
case "deletedat:desc":
return TribeSortDeletedAtDESC, nil
default:
return 0, UnsupportedSortStringError{
Sort: s,
}
}
}
type TribeCursor struct {
id int
serverKey string
@ -666,6 +701,31 @@ func (params *ListTribesParams) SetSort(sort []TribeSort) error {
return nil
}
func (params *ListTribesParams) PrependSortString(sort []string) error {
if err := validateSliceLen(sort, tribeSortMinLength, max(tribeSortMaxLength-len(params.sort), 0)); err != nil {
return ValidationError{
Model: listTribesParamsModelName,
Field: "sort",
Err: err,
}
}
for i := len(sort) - 1; i >= 0; i-- {
converted, err := newTribeSortFromString(sort[i])
if err != nil {
return SliceElementValidationError{
Model: listTribesParamsModelName,
Field: "sort",
Index: i,
Err: err,
}
}
params.sort = append([]TribeSort{converted}, params.sort...)
}
return nil
}
func (params *ListTribesParams) Cursor() TribeCursor {
return params.cursor
}

View File

@ -510,6 +510,185 @@ func TestListTribesParams_SetSort(t *testing.T) {
}
}
func TestListTribesParams_PrependSortString(t *testing.T) {
t.Parallel()
defaultNewParams := func(t *testing.T) domain.ListTribesParams {
t.Helper()
return domain.ListTribesParams{}
}
type args struct {
sort []string
}
tests := []struct {
name string
newParams func(t *testing.T) domain.ListTribesParams
args args
expectedSort []domain.TribeSort
expectedErr error
}{
{
name: "OK: [odScoreAtt:ASC, odScoreDef:ASC, odScoreTotal:ASC]",
args: args{
sort: []string{
"odScoreAtt:ASC",
"odScoreDef:ASC",
"odScoreTotal:ASC",
},
},
expectedSort: []domain.TribeSort{
domain.TribeSortODScoreAttASC,
domain.TribeSortODScoreDefASC,
domain.TribeSortODScoreTotalASC,
},
},
{
name: "OK: [odScoreAtt:DESC, odScoreDef:DESC, odScoreTotal:DESC]",
args: args{
sort: []string{
"odScoreAtt:DESC",
"odScoreDef:DESC",
"odScoreTotal:DESC",
},
},
expectedSort: []domain.TribeSort{
domain.TribeSortODScoreAttDESC,
domain.TribeSortODScoreDefDESC,
domain.TribeSortODScoreTotalDESC,
},
},
{
name: "OK: [points:ASC, dominance:ASC, deletedAt:ASC]",
args: args{
sort: []string{
"points:ASC",
"dominance:ASC",
"deletedAt:ASC",
},
},
expectedSort: []domain.TribeSort{
domain.TribeSortPointsASC,
domain.TribeSortDominanceASC,
domain.TribeSortDeletedAtASC,
},
},
{
name: "OK: [points:DESC, dominance:DESC, deletedAt:DESC]",
args: args{
sort: []string{
"points:DESC",
"dominance:DESC",
"deletedAt:DESC",
},
},
expectedSort: []domain.TribeSort{
domain.TribeSortPointsDESC,
domain.TribeSortDominanceDESC,
domain.TribeSortDeletedAtDESC,
},
},
{
name: "ERR: len(sort) < 1",
args: args{
sort: nil,
},
expectedErr: domain.ValidationError{
Model: "ListTribesParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 3,
Current: 0,
},
},
},
{
name: "ERR: len(sort) > 3",
args: args{
sort: []string{
"odScoreAtt:ASC",
"odScoreDef:ASC",
"odScoreTotal:ASC",
"points:ASC",
},
},
expectedErr: domain.ValidationError{
Model: "ListTribesParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 3,
Current: 4,
},
},
},
{
name: "ERR: custom params + len(sort) > 2",
newParams: func(t *testing.T) domain.ListTribesParams {
t.Helper()
params := domain.NewListTribesParams()
require.NoError(t, params.SetSort([]domain.TribeSort{domain.TribeSortIDASC}))
return params
},
args: args{
sort: []string{
"odScoreAtt:ASC",
"odScoreDef:ASC",
"odScoreTotal:ASC",
},
},
expectedErr: domain.ValidationError{
Model: "ListTribesParams",
Field: "sort",
Err: domain.LenOutOfRangeError{
Min: 1,
Max: 2,
Current: 3,
},
},
},
{
name: "ERR: unsupported sort string",
newParams: defaultNewParams,
args: args{
sort: []string{
"odScoreAtt:ASC",
"odScoreDef:ASC",
"odScoreTotal:",
},
},
expectedErr: domain.SliceElementValidationError{
Model: "ListTribesParams",
Field: "sort",
Index: 2,
Err: domain.UnsupportedSortStringError{
Sort: "odScoreTotal:",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
newParams := defaultNewParams
if tt.newParams != nil {
newParams = tt.newParams
}
params := newParams(t)
require.ErrorIs(t, params.PrependSortString(tt.args.sort), tt.expectedErr)
if tt.expectedErr != nil {
return
}
assert.Equal(t, tt.expectedSort, params.Sort())
})
}
}
func TestListTribesParams_SetEncodedCursor(t *testing.T) {
t.Parallel()

View File

@ -229,6 +229,30 @@ func (e InvalidURLError) Params() map[string]any {
}
}
type UnsupportedSortStringError struct {
Sort string
}
var _ ErrorWithParams = UnsupportedSortStringError{}
func (e UnsupportedSortStringError) Error() string {
return fmt.Sprintf("sort %s is unsupported", e.Sort)
}
func (e UnsupportedSortStringError) Type() ErrorType {
return ErrorTypeIncorrectInput
}
func (e UnsupportedSortStringError) Code() string {
return "unsupported-sort-string"
}
func (e UnsupportedSortStringError) Params() map[string]any {
return map[string]any{
"Sort": e.Sort,
}
}
var ErrRequired error = simpleError{
msg: "can't be blank",
typ: ErrorTypeIncorrectInput,

View File

@ -2,11 +2,13 @@ package port
import (
"net/http"
"strconv"
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
"gitea.dwysokinski.me/twhelp/corev3/internal/port/internal/apimodel"
)
//nolint:gocyclo
func (h *apiHTTPHandler) ListTribes(
w http.ResponseWriter,
r *http.Request,
@ -21,6 +23,13 @@ func (h *apiHTTPHandler) ListTribes(
return
}
if params.Sort != nil {
if err := domainParams.PrependSortString(*params.Sort); err != nil {
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
return
}
}
if err := domainParams.SetServerKeys([]string{serverKey}); err != nil {
apiErrorRenderer{errors: []error{err}, formatErrorPath: formatListTribesParamsErrorPath}.render(w, r)
return
@ -72,7 +81,11 @@ func formatListTribesParamsErrorPath(segments []errorPathSegment) []string {
case "deleted":
return []string{"$query", "deleted"}
case "sort":
return []string{"$query", "sort"}
path := []string{"$query", "sort"}
if segments[0].index >= 0 {
path = append(path, strconv.Itoa(segments[0].index))
}
return path
default:
return nil
}