feat: tribe - sort - add more options (#63)
Reviewed-on: twhelp/corev3#63
This commit is contained in:
parent
52c3ccb01b
commit
e5d8ba5390
|
@ -264,20 +264,7 @@ func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQue
|
|||
}
|
||||
case sortLen > 1:
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
switch sort[0] {
|
||||
case domain.ServerSortKeyASC:
|
||||
q = q.Where("server.key > ?", cursorKey)
|
||||
case domain.ServerSortKeyDESC:
|
||||
q = q.Where("server.key < ?", cursorKey)
|
||||
case domain.ServerSortOpenASC:
|
||||
q = q.Where("server.open > ?", cursorOpen)
|
||||
case domain.ServerSortOpenDESC:
|
||||
q = q.Where("server.open < ?", cursorOpen)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
|
||||
for i := 1; i < sortLen; i++ {
|
||||
for i := 0; i < sortLen; i++ {
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
current := sort[i]
|
||||
|
||||
|
@ -296,15 +283,23 @@ func (a listServersParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQue
|
|||
}
|
||||
}
|
||||
|
||||
greaterSymbol := bun.Safe(">")
|
||||
lessSymbol := bun.Safe("<")
|
||||
|
||||
if i == sortLen-1 {
|
||||
greaterSymbol = ">="
|
||||
lessSymbol = "<="
|
||||
}
|
||||
|
||||
switch current {
|
||||
case domain.ServerSortKeyASC:
|
||||
q = q.Where("server.key >= ?", cursorKey)
|
||||
q = q.Where("server.key ? ?", greaterSymbol, cursorKey)
|
||||
case domain.ServerSortKeyDESC:
|
||||
q = q.Where("server.key <= ?", cursorKey)
|
||||
q = q.Where("server.key ? ?", lessSymbol, cursorKey)
|
||||
case domain.ServerSortOpenASC:
|
||||
q = q.Where("server.open >= ?", cursorOpen)
|
||||
q = q.Where("server.open ? ?", greaterSymbol, cursorOpen)
|
||||
case domain.ServerSortOpenDESC:
|
||||
q = q.Where("server.open <= ?", cursorOpen)
|
||||
q = q.Where("server.open ? ?", lessSymbol, cursorOpen)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
|
|
|
@ -183,6 +183,30 @@ func (a listTribesParamsApplier) apply(q *bun.SelectQuery) *bun.SelectQuery {
|
|||
q = q.Order("tribe.server_key ASC")
|
||||
case domain.TribeSortServerKeyDESC:
|
||||
q = q.Order("tribe.server_key DESC")
|
||||
case domain.TribeSortODScoreAttASC:
|
||||
q = q.Order("tribe.score_att ASC")
|
||||
case domain.TribeSortODScoreAttDESC:
|
||||
q = q.Order("tribe.score_att DESC")
|
||||
case domain.TribeSortODScoreDefASC:
|
||||
q = q.Order("tribe.score_def ASC")
|
||||
case domain.TribeSortODScoreDefDESC:
|
||||
q = q.Order("tribe.score_def DESC")
|
||||
case domain.TribeSortODScoreTotalASC:
|
||||
q = q.Order("tribe.score_total ASC")
|
||||
case domain.TribeSortODScoreTotalDESC:
|
||||
q = q.Order("tribe.score_total DESC")
|
||||
case domain.TribeSortPointsASC:
|
||||
q = q.Order("tribe.points ASC")
|
||||
case domain.TribeSortPointsDESC:
|
||||
q = q.Order("tribe.points DESC")
|
||||
case domain.TribeSortDominanceASC:
|
||||
q = q.Order("tribe.dominance ASC")
|
||||
case domain.TribeSortDominanceDESC:
|
||||
q = q.Order("tribe.dominance DESC")
|
||||
case domain.TribeSortDeletedAtASC:
|
||||
q = q.OrderExpr("COALESCE(tribe.deleted_at, ?) ASC", time.Time{})
|
||||
case domain.TribeSortDeletedAtDESC:
|
||||
q = q.OrderExpr("COALESCE(tribe.deleted_at, ?) DESC", time.Time{})
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
|
@ -198,8 +222,15 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
|
|||
}
|
||||
|
||||
q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
cursorID := a.params.Cursor().ID()
|
||||
cursorServerKey := a.params.Cursor().ServerKey()
|
||||
cursor := a.params.Cursor()
|
||||
cursorID := cursor.ID()
|
||||
cursorServerKey := cursor.ServerKey()
|
||||
cursorODScoreAtt := cursor.ODScoreAtt()
|
||||
cursorODScoreDef := cursor.ODScoreDef()
|
||||
cursorODScoreTotal := cursor.ODScoreTotal()
|
||||
cursorPoints := cursor.Points()
|
||||
cursorDominance := cursor.Dominance()
|
||||
cursorDeletedAt := cursor.DeletedAt()
|
||||
|
||||
sort := a.params.Sort()
|
||||
sortLen := len(sort)
|
||||
|
@ -213,27 +244,27 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
|
|||
q = q.Where("tribe.id >= ?", cursorID)
|
||||
case domain.TribeSortIDDESC:
|
||||
q = q.Where("tribe.id <= ?", cursorID)
|
||||
case domain.TribeSortServerKeyASC, domain.TribeSortServerKeyDESC:
|
||||
case domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortServerKeyDESC,
|
||||
domain.TribeSortODScoreAttASC,
|
||||
domain.TribeSortODScoreAttDESC,
|
||||
domain.TribeSortODScoreDefASC,
|
||||
domain.TribeSortODScoreDefDESC,
|
||||
domain.TribeSortODScoreTotalASC,
|
||||
domain.TribeSortODScoreTotalDESC,
|
||||
domain.TribeSortPointsASC,
|
||||
domain.TribeSortPointsDESC,
|
||||
domain.TribeSortDominanceASC,
|
||||
domain.TribeSortDominanceDESC,
|
||||
domain.TribeSortDeletedAtASC,
|
||||
domain.TribeSortDeletedAtDESC:
|
||||
return q.Err(errSortNoUniqueField)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
case sortLen > 1:
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
switch sort[0] {
|
||||
case domain.TribeSortIDASC:
|
||||
q = q.Where("tribe.id > ?", cursorID)
|
||||
case domain.TribeSortIDDESC:
|
||||
q = q.Where("tribe.id < ?", cursorID)
|
||||
case domain.TribeSortServerKeyASC:
|
||||
q = q.Where("tribe.server_key > ?", cursorServerKey)
|
||||
case domain.TribeSortServerKeyDESC:
|
||||
q = q.Where("tribe.server_key < ?", cursorServerKey)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
|
||||
for i := 1; i < sortLen; i++ {
|
||||
for i := 0; i < sortLen; i++ {
|
||||
q.WhereGroup(" OR ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
current := sort[i]
|
||||
|
||||
|
@ -247,20 +278,70 @@ func (a listTribesParamsApplier) applyCursor(q *bun.SelectQuery) *bun.SelectQuer
|
|||
case domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortServerKeyDESC:
|
||||
q = q.Where("tribe.server_key = ?", cursorServerKey)
|
||||
case domain.TribeSortODScoreAttASC,
|
||||
domain.TribeSortODScoreAttDESC:
|
||||
q = q.Where("tribe.score_att = ?", cursorODScoreAtt)
|
||||
case domain.TribeSortODScoreDefASC,
|
||||
domain.TribeSortODScoreDefDESC:
|
||||
q = q.Where("tribe.score_def = ?", cursorODScoreDef)
|
||||
case domain.TribeSortODScoreTotalASC,
|
||||
domain.TribeSortODScoreTotalDESC:
|
||||
q = q.Where("tribe.score_total = ?", cursorODScoreTotal)
|
||||
case domain.TribeSortPointsASC,
|
||||
domain.TribeSortPointsDESC:
|
||||
q = q.Where("tribe.points = ?", cursorPoints)
|
||||
case domain.TribeSortDominanceASC,
|
||||
domain.TribeSortDominanceDESC:
|
||||
q = q.Where("tribe.dominance = ?", cursorDominance)
|
||||
case domain.TribeSortDeletedAtASC,
|
||||
domain.TribeSortDeletedAtDESC:
|
||||
q = q.Where("COALESCE(tribe.deleted_at, ?) = ?", time.Time{}, cursorDeletedAt)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
}
|
||||
|
||||
greaterSymbol := bun.Safe(">")
|
||||
lessSymbol := bun.Safe("<")
|
||||
|
||||
if i == sortLen-1 {
|
||||
greaterSymbol = ">="
|
||||
lessSymbol = "<="
|
||||
}
|
||||
|
||||
switch current {
|
||||
case domain.TribeSortIDASC:
|
||||
q = q.Where("tribe.id >= ?", cursorID)
|
||||
q = q.Where("tribe.id ? ?", greaterSymbol, cursorID)
|
||||
case domain.TribeSortIDDESC:
|
||||
q = q.Where("tribe.id <= ?", cursorID)
|
||||
q = q.Where("tribe.id ? ?", lessSymbol, cursorID)
|
||||
case domain.TribeSortServerKeyASC:
|
||||
q = q.Where("tribe.server_key >= ?", cursorServerKey)
|
||||
q = q.Where("tribe.server_key ? ?", greaterSymbol, cursorServerKey)
|
||||
case domain.TribeSortServerKeyDESC:
|
||||
q = q.Where("tribe.server_key <= ?", cursorServerKey)
|
||||
q = q.Where("tribe.server_key ? ?", lessSymbol, cursorServerKey)
|
||||
case domain.TribeSortODScoreAttASC:
|
||||
q = q.Where("tribe.score_att ? ?", greaterSymbol, cursorODScoreAtt)
|
||||
case domain.TribeSortODScoreAttDESC:
|
||||
q = q.Where("tribe.score_att ? ?", lessSymbol, cursorODScoreAtt)
|
||||
case domain.TribeSortODScoreDefASC:
|
||||
q = q.Where("tribe.score_def ? ?", greaterSymbol, cursorODScoreDef)
|
||||
case domain.TribeSortODScoreDefDESC:
|
||||
q = q.Where("tribe.score_def ? ?", lessSymbol, cursorODScoreDef)
|
||||
case domain.TribeSortODScoreTotalASC:
|
||||
q = q.Where("tribe.score_total ? ?", greaterSymbol, cursorODScoreTotal)
|
||||
case domain.TribeSortODScoreTotalDESC:
|
||||
q = q.Where("tribe.score_total ? ?", lessSymbol, cursorODScoreTotal)
|
||||
case domain.TribeSortPointsASC:
|
||||
q = q.Where("tribe.points ? ?", greaterSymbol, cursorPoints)
|
||||
case domain.TribeSortPointsDESC:
|
||||
q = q.Where("tribe.points ? ?", lessSymbol, cursorPoints)
|
||||
case domain.TribeSortDominanceASC:
|
||||
q = q.Where("tribe.dominance ? ?", greaterSymbol, cursorDominance)
|
||||
case domain.TribeSortDominanceDESC:
|
||||
q = q.Where("tribe.dominance ? ?", lessSymbol, cursorDominance)
|
||||
case domain.TribeSortDeletedAtASC:
|
||||
q = q.Where("COALESCE(tribe.deleted_at, ?) ? ?", time.Time{}, greaterSymbol, cursorDeletedAt)
|
||||
case domain.TribeSortDeletedAtDESC:
|
||||
q = q.Where("COALESCE(tribe.deleted_at, ?) ? ?", time.Time{}, lessSymbol, cursorDeletedAt)
|
||||
default:
|
||||
return q.Err(errUnsupportedSortValue)
|
||||
}
|
||||
|
|
|
@ -224,6 +224,209 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
|
|||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[scoreAtt DESC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortODScoreAttDESC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.OD().ScoreAtt(), b.OD().ScoreAtt())*-1,
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[scoreDef DESC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortODScoreDefDESC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.OD().ScoreDef(), b.OD().ScoreDef())*-1,
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[scoreTotal DESC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortODScoreTotalDESC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.OD().ScoreTotal(), b.OD().ScoreTotal())*-1,
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[points DESC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortPointsDESC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.Points(), b.Points())*-1,
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[dominance ASC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortDominanceASC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.Dominance(), b.Dominance()),
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[deletedAt ASC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortDeletedAtASC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
a.DeletedAt().Compare(b.DeletedAt()),
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: sort=[deletedAt DESC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortDeletedAtDESC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, _ domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
a.DeletedAt().Compare(b.DeletedAt())*-1,
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: ids serverKeys",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
|
@ -430,6 +633,98 @@ func testTribeRepository(t *testing.T, newRepos func(t *testing.T) repositories)
|
|||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: cursor sort=[points DESC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortPointsDESC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
|
||||
res, err := repos.tribe.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Tribes()), 2)
|
||||
|
||||
require.NoError(t, params.SetCursor(domaintest.NewTribeCursor(t, func(cfg *domaintest.TribeCursorConfig) {
|
||||
cfg.ID = res.Tribes()[1].ID()
|
||||
cfg.ServerKey = res.Tribes()[1].ServerKey()
|
||||
cfg.Points = res.Tribes()[1].Points()
|
||||
})))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.Points(), b.Points())*-1,
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
assert.GreaterOrEqual(t, tribes[0].ID(), params.Cursor().ID())
|
||||
assert.GreaterOrEqual(t, tribes[0].ServerKey(), params.Cursor().ServerKey())
|
||||
for _, tr := range res.Tribes() {
|
||||
assert.LessOrEqual(t, tr.Points(), params.Cursor().Points())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: cursor sort=[deletedAt ASC, serverKey ASC, id ASC]",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
t.Helper()
|
||||
|
||||
params := domain.NewListTribesParams()
|
||||
require.NoError(t, params.SetSort([]domain.TribeSort{
|
||||
domain.TribeSortDeletedAtASC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortIDASC,
|
||||
}))
|
||||
|
||||
res, err := repos.tribe.List(ctx, params)
|
||||
require.NoError(t, err)
|
||||
require.Greater(t, len(res.Tribes()), 2)
|
||||
|
||||
require.NoError(t, params.SetCursor(domaintest.NewTribeCursor(t, func(cfg *domaintest.TribeCursorConfig) {
|
||||
cfg.ID = res.Tribes()[1].ID()
|
||||
cfg.ServerKey = res.Tribes()[1].ServerKey()
|
||||
cfg.DeletedAt = res.Tribes()[1].DeletedAt()
|
||||
})))
|
||||
|
||||
return params
|
||||
},
|
||||
assertResult: func(t *testing.T, params domain.ListTribesParams, res domain.ListTribesResult) {
|
||||
t.Helper()
|
||||
tribes := res.Tribes()
|
||||
assert.NotEmpty(t, len(tribes))
|
||||
assert.True(t, slices.IsSortedFunc(tribes, func(a, b domain.Tribe) int {
|
||||
return cmp.Or(
|
||||
a.DeletedAt().Compare(b.DeletedAt()),
|
||||
cmp.Compare(a.ServerKey(), b.ServerKey()),
|
||||
cmp.Compare(a.ID(), b.ID()),
|
||||
)
|
||||
}))
|
||||
assert.GreaterOrEqual(t, tribes[0].ID(), params.Cursor().ID())
|
||||
assert.GreaterOrEqual(t, tribes[0].ServerKey(), params.Cursor().ServerKey())
|
||||
for _, tr := range res.Tribes() {
|
||||
assert.GreaterOrEqual(t, tr.DeletedAt(), params.Cursor().DeletedAt())
|
||||
}
|
||||
},
|
||||
assertError: func(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.NoError(t, err)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "OK: limit=2",
|
||||
params: func(t *testing.T) domain.ListTribesParams {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package domaintest
|
||||
|
||||
import (
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"gitea.dwysokinski.me/twhelp/corev3/internal/domain"
|
||||
|
@ -13,23 +14,44 @@ func RandTribeTag() string {
|
|||
}
|
||||
|
||||
type TribeCursorConfig struct {
|
||||
ID int
|
||||
ServerKey string
|
||||
ID int
|
||||
ServerKey string
|
||||
ODScoreAtt int
|
||||
ODScoreDef int
|
||||
ODScoreTotal int
|
||||
Points int
|
||||
Dominance float64
|
||||
DeletedAt time.Time
|
||||
}
|
||||
|
||||
func NewTribeCursor(tb TestingTB, opts ...func(cfg *TribeCursorConfig)) domain.TribeCursor {
|
||||
tb.Helper()
|
||||
|
||||
cfg := &TribeCursorConfig{
|
||||
ID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
ID: RandID(),
|
||||
ServerKey: RandServerKey(),
|
||||
ODScoreAtt: gofakeit.IntRange(0, math.MaxInt),
|
||||
ODScoreDef: gofakeit.IntRange(0, math.MaxInt),
|
||||
ODScoreTotal: gofakeit.IntRange(0, math.MaxInt),
|
||||
Points: gofakeit.IntRange(0, math.MaxInt),
|
||||
Dominance: gofakeit.Float64Range(0.1, 99.9),
|
||||
DeletedAt: time.Time{},
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(cfg)
|
||||
}
|
||||
|
||||
tc, err := domain.NewTribeCursor(cfg.ID, cfg.ServerKey)
|
||||
tc, err := domain.NewTribeCursor(
|
||||
cfg.ID,
|
||||
cfg.ServerKey,
|
||||
cfg.ODScoreAtt,
|
||||
cfg.ODScoreDef,
|
||||
cfg.ODScoreTotal,
|
||||
cfg.Points,
|
||||
cfg.Dominance,
|
||||
cfg.DeletedAt,
|
||||
)
|
||||
require.NoError(tb, err)
|
||||
|
||||
return tc
|
||||
|
|
|
@ -5,7 +5,6 @@ import (
|
|||
"math"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -494,12 +493,17 @@ func decodeServerCursor(encoded string) (ServerCursor, error) {
|
|||
return ServerCursor{}, err
|
||||
}
|
||||
|
||||
open, err := strconv.ParseBool(m["open"])
|
||||
key, err := m.string("key")
|
||||
if err != nil {
|
||||
return ServerCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
vc, err := NewServerCursor(m["key"], open)
|
||||
open, err := m.bool("open")
|
||||
if err != nil {
|
||||
return ServerCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
vc, err := NewServerCursor(key, open)
|
||||
if err != nil {
|
||||
return ServerCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
@ -524,9 +528,9 @@ func (sc ServerCursor) Encode() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
return encodeCursor([][2]string{
|
||||
return encodeCursor([]keyValuePair{
|
||||
{"key", sc.key},
|
||||
{"open", strconv.FormatBool(sc.open)},
|
||||
{"open", sc.open},
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,6 @@ import (
|
|||
"math"
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
@ -367,16 +366,43 @@ const (
|
|||
TribeSortIDDESC
|
||||
TribeSortServerKeyASC
|
||||
TribeSortServerKeyDESC
|
||||
TribeSortODScoreAttASC
|
||||
TribeSortODScoreAttDESC
|
||||
TribeSortODScoreDefASC
|
||||
TribeSortODScoreDefDESC
|
||||
TribeSortODScoreTotalASC
|
||||
TribeSortODScoreTotalDESC
|
||||
TribeSortPointsASC
|
||||
TribeSortPointsDESC
|
||||
TribeSortDominanceASC
|
||||
TribeSortDominanceDESC
|
||||
TribeSortDeletedAtASC
|
||||
TribeSortDeletedAtDESC
|
||||
)
|
||||
|
||||
type TribeCursor struct {
|
||||
id int
|
||||
serverKey string
|
||||
id int
|
||||
serverKey string
|
||||
odScoreAtt int
|
||||
odScoreDef int
|
||||
odScoreTotal int
|
||||
points int
|
||||
dominance float64
|
||||
deletedAt time.Time
|
||||
}
|
||||
|
||||
const tribeCursorModelName = "TribeCursor"
|
||||
|
||||
func NewTribeCursor(id int, serverKey string) (TribeCursor, error) {
|
||||
func NewTribeCursor(
|
||||
id int,
|
||||
serverKey string,
|
||||
odScoreAtt int,
|
||||
odScoreDef int,
|
||||
odScoreTotal int,
|
||||
points int,
|
||||
dominance float64,
|
||||
deletedAt time.Time,
|
||||
) (TribeCursor, error) {
|
||||
if err := validateIntInRange(id, 1, math.MaxInt); err != nil {
|
||||
return TribeCursor{}, ValidationError{
|
||||
Model: tribeCursorModelName,
|
||||
|
@ -393,24 +419,107 @@ func NewTribeCursor(id int, serverKey string) (TribeCursor, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if err := validateIntInRange(odScoreAtt, 0, math.MaxInt); err != nil {
|
||||
return TribeCursor{}, ValidationError{
|
||||
Model: tribeCursorModelName,
|
||||
Field: "odScoreAtt",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateIntInRange(odScoreDef, 0, math.MaxInt); err != nil {
|
||||
return TribeCursor{}, ValidationError{
|
||||
Model: tribeCursorModelName,
|
||||
Field: "odScoreDef",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateIntInRange(odScoreTotal, 0, math.MaxInt); err != nil {
|
||||
return TribeCursor{}, ValidationError{
|
||||
Model: tribeCursorModelName,
|
||||
Field: "odScoreTotal",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
if err := validateIntInRange(points, 0, math.MaxInt); err != nil {
|
||||
return TribeCursor{}, ValidationError{
|
||||
Model: tribeCursorModelName,
|
||||
Field: "points",
|
||||
Err: err,
|
||||
}
|
||||
}
|
||||
|
||||
return TribeCursor{
|
||||
id: id,
|
||||
serverKey: serverKey,
|
||||
id: id,
|
||||
serverKey: serverKey,
|
||||
odScoreAtt: odScoreAtt,
|
||||
odScoreDef: odScoreDef,
|
||||
odScoreTotal: odScoreTotal,
|
||||
points: points,
|
||||
dominance: dominance,
|
||||
deletedAt: deletedAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//nolint:gocyclo
|
||||
func decodeTribeCursor(encoded string) (TribeCursor, error) {
|
||||
m, err := decodeCursor(encoded)
|
||||
if err != nil {
|
||||
return TribeCursor{}, err
|
||||
}
|
||||
|
||||
id, err := strconv.Atoi(m["id"])
|
||||
id, err := m.int("id")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
tc, err := NewTribeCursor(id, m["serverKey"])
|
||||
serverKey, err := m.string("serverKey")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
odScoreAtt, err := m.int("odScoreAtt")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
odScoreDef, err := m.int("odScoreDef")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
odScoreTotal, err := m.int("odScoreTotal")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
points, err := m.int("points")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
dominance, err := m.float64("dominance")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
deletedAt, err := m.time("deletedAt")
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
tc, err := NewTribeCursor(
|
||||
id,
|
||||
serverKey,
|
||||
odScoreAtt,
|
||||
odScoreDef,
|
||||
odScoreTotal,
|
||||
points,
|
||||
dominance,
|
||||
deletedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return TribeCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
@ -426,6 +535,30 @@ func (tc TribeCursor) ServerKey() string {
|
|||
return tc.serverKey
|
||||
}
|
||||
|
||||
func (tc TribeCursor) ODScoreAtt() int {
|
||||
return tc.odScoreAtt
|
||||
}
|
||||
|
||||
func (tc TribeCursor) ODScoreDef() int {
|
||||
return tc.odScoreDef
|
||||
}
|
||||
|
||||
func (tc TribeCursor) ODScoreTotal() int {
|
||||
return tc.odScoreTotal
|
||||
}
|
||||
|
||||
func (tc TribeCursor) Points() int {
|
||||
return tc.points
|
||||
}
|
||||
|
||||
func (tc TribeCursor) Dominance() float64 {
|
||||
return tc.dominance
|
||||
}
|
||||
|
||||
func (tc TribeCursor) DeletedAt() time.Time {
|
||||
return tc.deletedAt
|
||||
}
|
||||
|
||||
func (tc TribeCursor) IsZero() bool {
|
||||
return tc == TribeCursor{}
|
||||
}
|
||||
|
@ -435,9 +568,15 @@ func (tc TribeCursor) Encode() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
return encodeCursor([][2]string{
|
||||
{"id", strconv.Itoa(tc.id)},
|
||||
return encodeCursor([]keyValuePair{
|
||||
{"id", tc.id},
|
||||
{"serverKey", tc.serverKey},
|
||||
{"odScoreAtt", tc.odScoreAtt},
|
||||
{"odScoreDef", tc.odScoreDef},
|
||||
{"odScoreTotal", tc.odScoreTotal},
|
||||
{"points", tc.points},
|
||||
{"dominance", tc.dominance},
|
||||
{"deletedAt", tc.deletedAt},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -510,7 +649,7 @@ func (params *ListTribesParams) Sort() []TribeSort {
|
|||
|
||||
const (
|
||||
tribeSortMinLength = 1
|
||||
tribeSortMaxLength = 2
|
||||
tribeSortMaxLength = 3
|
||||
)
|
||||
|
||||
func (params *ListTribesParams) SetSort(sort []TribeSort) error {
|
||||
|
@ -584,7 +723,17 @@ func NewListTribesResult(tribes Tribes, next Tribe) (ListTribesResult, error) {
|
|||
}
|
||||
|
||||
if len(tribes) > 0 {
|
||||
res.self, err = NewTribeCursor(tribes[0].ID(), tribes[0].ServerKey())
|
||||
od := tribes[0].OD()
|
||||
res.self, err = NewTribeCursor(
|
||||
tribes[0].ID(),
|
||||
tribes[0].ServerKey(),
|
||||
od.ScoreAtt(),
|
||||
od.ScoreDef(),
|
||||
od.ScoreTotal(),
|
||||
tribes[0].Points(),
|
||||
tribes[0].Dominance(),
|
||||
tribes[0].DeletedAt(),
|
||||
)
|
||||
if err != nil {
|
||||
return ListTribesResult{}, ValidationError{
|
||||
Model: listTribesResultModelName,
|
||||
|
@ -595,7 +744,17 @@ func NewListTribesResult(tribes Tribes, next Tribe) (ListTribesResult, error) {
|
|||
}
|
||||
|
||||
if !next.IsZero() {
|
||||
res.next, err = NewTribeCursor(next.ID(), next.ServerKey())
|
||||
od := next.OD()
|
||||
res.next, err = NewTribeCursor(
|
||||
next.ID(),
|
||||
next.ServerKey(),
|
||||
od.ScoreAtt(),
|
||||
od.ScoreDef(),
|
||||
od.ScoreTotal(),
|
||||
next.Points(),
|
||||
next.Dominance(),
|
||||
next.DeletedAt(),
|
||||
)
|
||||
if err != nil {
|
||||
return ListTribesResult{}, ValidationError{
|
||||
Model: listTribesResultModelName,
|
||||
|
|
|
@ -186,8 +186,14 @@ func TestNewTribeCursor(t *testing.T) {
|
|||
validTribeCursor := domaintest.NewTribeCursor(t)
|
||||
|
||||
type args struct {
|
||||
id int
|
||||
serverKey string
|
||||
id int
|
||||
serverKey string
|
||||
odScoreAtt int
|
||||
odScoreDef int
|
||||
odScoreTotal int
|
||||
points int
|
||||
dominance float64
|
||||
deletedAt time.Time
|
||||
}
|
||||
|
||||
type test struct {
|
||||
|
@ -200,16 +206,28 @@ func TestNewTribeCursor(t *testing.T) {
|
|||
{
|
||||
name: "OK",
|
||||
args: args{
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||
points: validTribeCursor.Points(),
|
||||
dominance: validTribeCursor.Dominance(),
|
||||
deletedAt: validTribeCursor.DeletedAt(),
|
||||
},
|
||||
expectedErr: nil,
|
||||
},
|
||||
{
|
||||
name: "ERR: id < 1",
|
||||
args: args{
|
||||
id: 0,
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
id: 0,
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||
points: validTribeCursor.Points(),
|
||||
dominance: validTribeCursor.Dominance(),
|
||||
deletedAt: validTribeCursor.DeletedAt(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "TribeCursor",
|
||||
|
@ -220,14 +238,104 @@ func TestNewTribeCursor(t *testing.T) {
|
|||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: odScoreAtt < 0",
|
||||
args: args{
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
odScoreAtt: -1,
|
||||
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||
points: validTribeCursor.Points(),
|
||||
dominance: validTribeCursor.Dominance(),
|
||||
deletedAt: validTribeCursor.DeletedAt(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "TribeCursor",
|
||||
Field: "odScoreAtt",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: odScoreDef < 0",
|
||||
args: args{
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||
odScoreDef: -1,
|
||||
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||
points: validTribeCursor.Points(),
|
||||
dominance: validTribeCursor.Dominance(),
|
||||
deletedAt: validTribeCursor.DeletedAt(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "TribeCursor",
|
||||
Field: "odScoreDef",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: odScoreTotal < 0",
|
||||
args: args{
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||
odScoreTotal: -1,
|
||||
points: validTribeCursor.Points(),
|
||||
dominance: validTribeCursor.Dominance(),
|
||||
deletedAt: validTribeCursor.DeletedAt(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "TribeCursor",
|
||||
Field: "odScoreTotal",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: points < 0",
|
||||
args: args{
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: validTribeCursor.ServerKey(),
|
||||
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||
points: -1,
|
||||
dominance: validTribeCursor.Dominance(),
|
||||
deletedAt: validTribeCursor.DeletedAt(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "TribeCursor",
|
||||
Field: "points",
|
||||
Err: domain.MinGreaterEqualError{
|
||||
Min: 0,
|
||||
Current: -1,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, serverKeyTest := range newServerKeyValidationTests() {
|
||||
tests = append(tests, test{
|
||||
name: serverKeyTest.name,
|
||||
args: args{
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: serverKeyTest.key,
|
||||
id: validTribeCursor.ID(),
|
||||
serverKey: serverKeyTest.key,
|
||||
odScoreAtt: validTribeCursor.ODScoreAtt(),
|
||||
odScoreDef: validTribeCursor.ODScoreDef(),
|
||||
odScoreTotal: validTribeCursor.ODScoreTotal(),
|
||||
points: validTribeCursor.Points(),
|
||||
dominance: validTribeCursor.Dominance(),
|
||||
deletedAt: validTribeCursor.DeletedAt(),
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
Model: "TribeCursor",
|
||||
|
@ -243,7 +351,16 @@ func TestNewTribeCursor(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tc, err := domain.NewTribeCursor(tt.args.id, tt.args.serverKey)
|
||||
tc, err := domain.NewTribeCursor(
|
||||
tt.args.id,
|
||||
tt.args.serverKey,
|
||||
tt.args.odScoreAtt,
|
||||
tt.args.odScoreDef,
|
||||
tt.args.odScoreTotal,
|
||||
tt.args.points,
|
||||
tt.args.dominance,
|
||||
tt.args.deletedAt,
|
||||
)
|
||||
require.ErrorIs(t, err, tt.expectedErr)
|
||||
if tt.expectedErr != nil {
|
||||
return
|
||||
|
@ -333,6 +450,7 @@ func TestListTribesParams_SetSort(t *testing.T) {
|
|||
name: "OK",
|
||||
args: args{
|
||||
sort: []domain.TribeSort{
|
||||
domain.TribeSortPointsASC,
|
||||
domain.TribeSortIDASC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
},
|
||||
|
@ -348,18 +466,19 @@ func TestListTribesParams_SetSort(t *testing.T) {
|
|||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 2,
|
||||
Max: 3,
|
||||
Current: 0,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "ERR: len(sort) > 2",
|
||||
name: "ERR: len(sort) > 3",
|
||||
args: args{
|
||||
sort: []domain.TribeSort{
|
||||
domain.TribeSortDominanceASC,
|
||||
domain.TribeSortPointsASC,
|
||||
domain.TribeSortIDASC,
|
||||
domain.TribeSortServerKeyASC,
|
||||
domain.TribeSortServerKeyDESC,
|
||||
},
|
||||
},
|
||||
expectedErr: domain.ValidationError{
|
||||
|
@ -367,8 +486,8 @@ func TestListTribesParams_SetSort(t *testing.T) {
|
|||
Field: "sort",
|
||||
Err: domain.LenOutOfRangeError{
|
||||
Min: 1,
|
||||
Max: 2,
|
||||
Current: 3,
|
||||
Max: 3,
|
||||
Current: 4,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
|
|
@ -3,8 +3,11 @@ package domain
|
|||
import (
|
||||
"bytes"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func parseURL(rawURL string) (*url.URL, error) {
|
||||
|
@ -23,38 +26,76 @@ const (
|
|||
cursorKeyValueSeparator = "="
|
||||
)
|
||||
|
||||
func encodeCursor(s [][2]string) string {
|
||||
if len(s) == 0 {
|
||||
type keyValuePair struct {
|
||||
key string
|
||||
value any
|
||||
}
|
||||
|
||||
func (kvp keyValuePair) valueString() string {
|
||||
switch v := kvp.value.(type) {
|
||||
case string:
|
||||
return v
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case bool:
|
||||
return strconv.FormatBool(v)
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'g', -1, 64)
|
||||
case time.Time:
|
||||
return v.Format(time.RFC3339)
|
||||
case fmt.Stringer:
|
||||
return v.String()
|
||||
default:
|
||||
return fmt.Sprintf("%v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func encodeCursor(kvps []keyValuePair) string {
|
||||
if len(kvps) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
n := len(cursorSeparator) * (len(s) - 1)
|
||||
for _, el := range s {
|
||||
n += len(el[0]) + len(el[1]) + len(cursorKeyValueSeparator)
|
||||
values := make([]string, len(kvps))
|
||||
n := len(cursorSeparator) * (len(kvps) - 1)
|
||||
|
||||
for i, kvp := range kvps {
|
||||
value := kvp.valueString()
|
||||
n += len(kvp.key) + len(value) + len(cursorKeyValueSeparator)
|
||||
values[i] = value
|
||||
}
|
||||
|
||||
var b bytes.Buffer
|
||||
b.Grow(n)
|
||||
|
||||
for _, el := range s {
|
||||
for i, kvp := range kvps {
|
||||
if b.Len() > 0 {
|
||||
_, _ = b.WriteString(cursorSeparator)
|
||||
}
|
||||
|
||||
_, _ = b.WriteString(el[0])
|
||||
_, _ = b.WriteString(kvp.key)
|
||||
_, _ = b.WriteString(cursorKeyValueSeparator)
|
||||
_, _ = b.WriteString(el[1])
|
||||
_, _ = b.WriteString(values[i])
|
||||
}
|
||||
|
||||
return base64.StdEncoding.EncodeToString(b.Bytes())
|
||||
}
|
||||
|
||||
type cursorKeyNotFoundError struct {
|
||||
key string
|
||||
}
|
||||
|
||||
func (e cursorKeyNotFoundError) Error() string {
|
||||
return "key " + e.key + " not found"
|
||||
}
|
||||
|
||||
type decodedCursor map[string]string
|
||||
|
||||
const (
|
||||
encodedCursorMinLength = 1
|
||||
encodedCursorMaxLength = 1000
|
||||
)
|
||||
|
||||
func decodeCursor(s string) (map[string]string, error) {
|
||||
func decodeCursor(s string) (decodedCursor, error) {
|
||||
if err := validateStringLen(s, encodedCursorMinLength, encodedCursorMaxLength); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -65,12 +106,12 @@ func decodeCursor(s string) (map[string]string, error) {
|
|||
}
|
||||
decoded := string(decodedBytes)
|
||||
|
||||
kvs := strings.Split(decoded, cursorSeparator)
|
||||
kvps := strings.Split(decoded, cursorSeparator)
|
||||
|
||||
m := make(map[string]string, len(kvs))
|
||||
m := make(decodedCursor, len(kvps))
|
||||
|
||||
for _, kv := range kvs {
|
||||
k, v, found := strings.Cut(kv, cursorKeyValueSeparator)
|
||||
for _, kvp := range kvps {
|
||||
k, v, found := strings.Cut(kvp, cursorKeyValueSeparator)
|
||||
if !found {
|
||||
return nil, ErrInvalidCursor
|
||||
}
|
||||
|
@ -79,3 +120,68 @@ func decodeCursor(s string) (map[string]string, error) {
|
|||
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func (c decodedCursor) string(k string) (string, error) {
|
||||
val, ok := c[k]
|
||||
if !ok {
|
||||
return "", cursorKeyNotFoundError{k}
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c decodedCursor) int(k string) (int, error) {
|
||||
valStr, ok := c[k]
|
||||
if !ok {
|
||||
return 0, cursorKeyNotFoundError{k}
|
||||
}
|
||||
|
||||
val, err := strconv.Atoi(valStr)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c decodedCursor) float64(k string) (float64, error) {
|
||||
valStr, ok := c[k]
|
||||
if !ok {
|
||||
return 0, cursorKeyNotFoundError{k}
|
||||
}
|
||||
|
||||
val, err := strconv.ParseFloat(valStr, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c decodedCursor) bool(k string) (bool, error) {
|
||||
valStr, ok := c[k]
|
||||
if !ok {
|
||||
return false, cursorKeyNotFoundError{k}
|
||||
}
|
||||
|
||||
val, err := strconv.ParseBool(valStr)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
||||
func (c decodedCursor) time(k string) (time.Time, error) {
|
||||
valStr, ok := c[k]
|
||||
if !ok {
|
||||
return time.Time{}, cursorKeyNotFoundError{k}
|
||||
}
|
||||
|
||||
val, err := time.Parse(time.RFC3339, valStr)
|
||||
if err != nil {
|
||||
return time.Time{}, fmt.Errorf("couldn't parse value for key %s: %w", k, err)
|
||||
}
|
||||
|
||||
return val, nil
|
||||
}
|
||||
|
|
|
@ -131,7 +131,12 @@ func decodeVersionCursor(encoded string) (VersionCursor, error) {
|
|||
return VersionCursor{}, err
|
||||
}
|
||||
|
||||
vc, err := NewVersionCursor(m["code"])
|
||||
code, err := m.string("code")
|
||||
if err != nil {
|
||||
return VersionCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
||||
vc, err := NewVersionCursor(code)
|
||||
if err != nil {
|
||||
return VersionCursor{}, ErrInvalidCursor
|
||||
}
|
||||
|
@ -152,7 +157,7 @@ func (vc VersionCursor) Encode() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
return encodeCursor([][2]string{
|
||||
return encodeCursor([]keyValuePair{
|
||||
{"code", vc.code},
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue