mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2025-10-29 04:42:25 -05:00
[bugfix] self-referencing collection pages for status replies (#2364)
This commit is contained in:
parent
efefdb1323
commit
16275853eb
24 changed files with 611 additions and 427 deletions
|
|
@ -131,3 +131,20 @@ func (b Boundary) Find(in []string) int {
|
|||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// Boundary_FindFunc is functionally equivalent to Boundary{}.Find() but for an arbitrary type with ID.
|
||||
// Note: this is not a Boundary{} method as Go generics are not supported in method receiver functions.
|
||||
func Boundary_FindFunc[T any](b Boundary, in []T, get func(T) string) int { //nolint:revive
|
||||
if get == nil {
|
||||
panic("nil function")
|
||||
}
|
||||
if b.Value == "" {
|
||||
return -1
|
||||
}
|
||||
for i := range in {
|
||||
if get(in[i]) == b.Value {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,9 +19,8 @@ package paging
|
|||
|
||||
import (
|
||||
"net/url"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
type Page struct {
|
||||
|
|
@ -117,7 +116,7 @@ func (p *Page) Page(in []string) []string {
|
|||
|
||||
// Output slice must
|
||||
// ALWAYS be descending.
|
||||
in = Reverse(in)
|
||||
slices.Reverse(in)
|
||||
}
|
||||
} else {
|
||||
// Default sort is descending,
|
||||
|
|
@ -143,6 +142,66 @@ func (p *Page) Page(in []string) []string {
|
|||
return in
|
||||
}
|
||||
|
||||
// Page_PageFunc is functionally equivalent to Page{}.Page(), but for an arbitrary type with ID.
|
||||
// Note: this is not a Page{} method as Go generics are not supported in method receiver functions.
|
||||
func Page_PageFunc[WithID any](p *Page, in []WithID, get func(WithID) string) []WithID { //nolint:revive
|
||||
if p == nil {
|
||||
// no paging.
|
||||
return in
|
||||
}
|
||||
|
||||
if p.order().Ascending() {
|
||||
// Sort type is ascending, input
|
||||
// data is assumed to be ascending.
|
||||
|
||||
if minIdx := Boundary_FindFunc(p.Min, in, get); minIdx != -1 {
|
||||
// Reslice skipping up to min.
|
||||
in = in[minIdx+1:]
|
||||
}
|
||||
|
||||
if maxIdx := Boundary_FindFunc(p.Max, in, get); maxIdx != -1 {
|
||||
// Reslice stripping past max.
|
||||
in = in[:maxIdx]
|
||||
}
|
||||
|
||||
if p.Limit > 0 && p.Limit < len(in) {
|
||||
// Reslice input to limit.
|
||||
in = in[:p.Limit]
|
||||
}
|
||||
|
||||
if len(in) > 1 {
|
||||
// Clone input before
|
||||
// any modifications.
|
||||
in = slices.Clone(in)
|
||||
|
||||
// Output slice must
|
||||
// ALWAYS be descending.
|
||||
slices.Reverse(in)
|
||||
}
|
||||
} else {
|
||||
// Default sort is descending,
|
||||
// catching all cases when NOT
|
||||
// ascending (even zero value).
|
||||
|
||||
if maxIdx := Boundary_FindFunc(p.Max, in, get); maxIdx != -1 {
|
||||
// Reslice skipping up to max.
|
||||
in = in[maxIdx+1:]
|
||||
}
|
||||
|
||||
if minIdx := Boundary_FindFunc(p.Min, in, get); minIdx != -1 {
|
||||
// Reslice stripping past min.
|
||||
in = in[:minIdx]
|
||||
}
|
||||
|
||||
if p.Limit > 0 && p.Limit < len(in) {
|
||||
// Reslice input to limit.
|
||||
in = in[:p.Limit]
|
||||
}
|
||||
}
|
||||
|
||||
return in
|
||||
}
|
||||
|
||||
// Next creates a new instance for the next returnable page, using
|
||||
// given max value. This preserves original limit and max key name.
|
||||
func (p *Page) Next(lo, hi string) *Page {
|
||||
|
|
@ -225,21 +284,24 @@ func (p *Page) ToLinkURL(proto, host, path string, queryParams url.Values) *url.
|
|||
if queryParams == nil {
|
||||
// Allocate new query parameters.
|
||||
queryParams = make(url.Values)
|
||||
} else {
|
||||
// Before edit clone existing params.
|
||||
queryParams = cloneQuery(queryParams)
|
||||
}
|
||||
|
||||
if p.Min.Value != "" {
|
||||
// A page-minimum query parameter is available.
|
||||
queryParams.Add(p.Min.Name, p.Min.Value)
|
||||
queryParams.Set(p.Min.Name, p.Min.Value)
|
||||
}
|
||||
|
||||
if p.Max.Value != "" {
|
||||
// A page-maximum query parameter is available.
|
||||
queryParams.Add(p.Max.Name, p.Max.Value)
|
||||
queryParams.Set(p.Max.Name, p.Max.Value)
|
||||
}
|
||||
|
||||
if p.Limit > 0 {
|
||||
// A page limit query parameter is available.
|
||||
queryParams.Add("limit", strconv.Itoa(p.Limit))
|
||||
queryParams.Set("limit", strconv.Itoa(p.Limit))
|
||||
}
|
||||
|
||||
// Build URL string.
|
||||
|
|
@ -250,3 +312,12 @@ func (p *Page) ToLinkURL(proto, host, path string, queryParams url.Values) *url.
|
|||
RawQuery: queryParams.Encode(),
|
||||
}
|
||||
}
|
||||
|
||||
// cloneQuery clones input map of url values.
|
||||
func cloneQuery(src url.Values) url.Values {
|
||||
dst := make(url.Values, len(src))
|
||||
for k, vs := range src {
|
||||
dst[k] = slices.Clone(vs)
|
||||
}
|
||||
return dst
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@ package paging_test
|
|||
|
||||
import (
|
||||
"math/rand"
|
||||
"slices"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/paging"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// random reader according to current-time source seed.
|
||||
|
|
@ -77,9 +77,7 @@ func TestPage(t *testing.T) {
|
|||
var cases = []Case{
|
||||
CreateCase("minID and maxID set", func(ids []string) ([]string, *paging.Page, []string) {
|
||||
// Ensure input slice sorted ascending for min_id
|
||||
slices.SortFunc(ids, func(a, b string) bool {
|
||||
return a > b // i.e. largest at lowest idx
|
||||
})
|
||||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random indices in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
|
|
@ -93,7 +91,7 @@ var cases = []Case{
|
|||
expect := slices.Clone(ids)
|
||||
expect = cutLower(expect, minID)
|
||||
expect = cutUpper(expect, maxID)
|
||||
expect = paging.Reverse(expect)
|
||||
slices.Reverse(expect)
|
||||
|
||||
// Return page and expected IDs.
|
||||
return ids, &paging.Page{
|
||||
|
|
@ -103,9 +101,7 @@ var cases = []Case{
|
|||
}),
|
||||
CreateCase("minID, maxID and limit set", func(ids []string) ([]string, *paging.Page, []string) {
|
||||
// Ensure input slice sorted ascending for min_id
|
||||
slices.SortFunc(ids, func(a, b string) bool {
|
||||
return a > b // i.e. largest at lowest idx
|
||||
})
|
||||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random parameters in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
|
|
@ -120,7 +116,7 @@ var cases = []Case{
|
|||
expect := slices.Clone(ids)
|
||||
expect = cutLower(expect, minID)
|
||||
expect = cutUpper(expect, maxID)
|
||||
expect = paging.Reverse(expect)
|
||||
slices.Reverse(expect)
|
||||
|
||||
// Now limit the slice.
|
||||
if limit < len(expect) {
|
||||
|
|
@ -136,9 +132,7 @@ var cases = []Case{
|
|||
}),
|
||||
CreateCase("minID, maxID and too-large limit set", func(ids []string) ([]string, *paging.Page, []string) {
|
||||
// Ensure input slice sorted ascending for min_id
|
||||
slices.SortFunc(ids, func(a, b string) bool {
|
||||
return a > b // i.e. largest at lowest idx
|
||||
})
|
||||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random parameters in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
|
|
@ -152,7 +146,7 @@ var cases = []Case{
|
|||
expect := slices.Clone(ids)
|
||||
expect = cutLower(expect, minID)
|
||||
expect = cutUpper(expect, maxID)
|
||||
expect = paging.Reverse(expect)
|
||||
slices.Reverse(expect)
|
||||
|
||||
// Return page and expected IDs.
|
||||
return ids, &paging.Page{
|
||||
|
|
@ -163,9 +157,7 @@ var cases = []Case{
|
|||
}),
|
||||
CreateCase("sinceID and maxID set", func(ids []string) ([]string, *paging.Page, []string) {
|
||||
// Ensure input slice sorted descending for since_id
|
||||
slices.SortFunc(ids, func(a, b string) bool {
|
||||
return a < b // i.e. smallest at lowest idx
|
||||
})
|
||||
slices.SortFunc(ids, descending)
|
||||
|
||||
// Select random indices in slice.
|
||||
sinceIdx := randRd.Intn(len(ids))
|
||||
|
|
@ -188,9 +180,7 @@ var cases = []Case{
|
|||
}),
|
||||
CreateCase("maxID set", func(ids []string) ([]string, *paging.Page, []string) {
|
||||
// Ensure input slice sorted descending for max_id
|
||||
slices.SortFunc(ids, func(a, b string) bool {
|
||||
return a < b // i.e. smallest at lowest idx
|
||||
})
|
||||
slices.SortFunc(ids, descending)
|
||||
|
||||
// Select random indices in slice.
|
||||
maxIdx := randRd.Intn(len(ids))
|
||||
|
|
@ -209,9 +199,7 @@ var cases = []Case{
|
|||
}),
|
||||
CreateCase("sinceID set", func(ids []string) ([]string, *paging.Page, []string) {
|
||||
// Ensure input slice sorted descending for since_id
|
||||
slices.SortFunc(ids, func(a, b string) bool {
|
||||
return a < b
|
||||
})
|
||||
slices.SortFunc(ids, descending)
|
||||
|
||||
// Select random indices in slice.
|
||||
sinceIdx := randRd.Intn(len(ids))
|
||||
|
|
@ -230,9 +218,7 @@ var cases = []Case{
|
|||
}),
|
||||
CreateCase("minID set", func(ids []string) ([]string, *paging.Page, []string) {
|
||||
// Ensure input slice sorted ascending for min_id
|
||||
slices.SortFunc(ids, func(a, b string) bool {
|
||||
return a > b // i.e. largest at lowest idx
|
||||
})
|
||||
slices.SortFunc(ids, ascending)
|
||||
|
||||
// Select random indices in slice.
|
||||
minIdx := randRd.Intn(len(ids))
|
||||
|
|
@ -243,7 +229,7 @@ var cases = []Case{
|
|||
// Create expected output.
|
||||
expect := slices.Clone(ids)
|
||||
expect = cutLower(expect, minID)
|
||||
expect = paging.Reverse(expect)
|
||||
slices.Reverse(expect)
|
||||
|
||||
// Return page and expected IDs.
|
||||
return ids, &paging.Page{
|
||||
|
|
@ -296,3 +282,21 @@ func generateSlice(len int) []string {
|
|||
}
|
||||
return in
|
||||
}
|
||||
|
||||
func ascending(a, b string) int {
|
||||
if a > b {
|
||||
return 1
|
||||
} else if a < b {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func descending(a, b string) int {
|
||||
if a < b {
|
||||
return 1
|
||||
} else if a > b {
|
||||
return -1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
// GoToSocial
|
||||
// Copyright (C) GoToSocial Authors admin@gotosocial.org
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package paging
|
||||
|
||||
// Reverse will reverse the given input slice.
|
||||
func Reverse(in []string) []string {
|
||||
var (
|
||||
// Start at front.
|
||||
i = 0
|
||||
|
||||
// Start at back.
|
||||
j = len(in) - 1
|
||||
)
|
||||
|
||||
for i < j {
|
||||
// Swap i,j index values in slice.
|
||||
in[i], in[j] = in[j], in[i]
|
||||
|
||||
// incr + decr,
|
||||
// looping until
|
||||
// they meet in
|
||||
// the middle.
|
||||
i++
|
||||
j--
|
||||
}
|
||||
|
||||
return in
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue