summaryrefslogtreecommitdiff
path: root/internal/paging
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2023-11-20 12:22:28 +0000
committerLibravatar GitHub <noreply@github.com>2023-11-20 12:22:28 +0000
commit16275853eb8a43e0b113d476b896de53585c1281 (patch)
treeb2e0e6b4fc7cd4f1cc781e5c305ec24df38e6718 /internal/paging
parent[chore]: Bump github.com/tdewolff/minify/v2 from 2.20.6 to 2.20.7 (#2370) (diff)
downloadgotosocial-16275853eb8a43e0b113d476b896de53585c1281.tar.xz
[bugfix] self-referencing collection pages for status replies (#2364)
Diffstat (limited to 'internal/paging')
-rw-r--r--internal/paging/boundary.go17
-rw-r--r--internal/paging/page.go83
-rw-r--r--internal/paging/page_test.go56
-rw-r--r--internal/paging/util.go43
4 files changed, 124 insertions, 75 deletions
diff --git a/internal/paging/boundary.go b/internal/paging/boundary.go
index 15af65e0c..83d265515 100644
--- a/internal/paging/boundary.go
+++ b/internal/paging/boundary.go
@@ -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
+}
diff --git a/internal/paging/page.go b/internal/paging/page.go
index a56f674dd..a1cf76c74 100644
--- a/internal/paging/page.go
+++ b/internal/paging/page.go
@@ -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
+}
diff --git a/internal/paging/page_test.go b/internal/paging/page_test.go
index 9eeb90882..3046dfcdd 100644
--- a/internal/paging/page_test.go
+++ b/internal/paging/page_test.go
@@ -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
+}
diff --git a/internal/paging/util.go b/internal/paging/util.go
deleted file mode 100644
index dd941dd88..000000000
--- a/internal/paging/util.go
+++ /dev/null
@@ -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
-}