diff options
author | 2023-11-20 12:22:28 +0000 | |
---|---|---|
committer | 2023-11-20 12:22:28 +0000 | |
commit | 16275853eb8a43e0b113d476b896de53585c1281 (patch) | |
tree | b2e0e6b4fc7cd4f1cc781e5c305ec24df38e6718 /internal/paging | |
parent | [chore]: Bump github.com/tdewolff/minify/v2 from 2.20.6 to 2.20.7 (#2370) (diff) | |
download | gotosocial-16275853eb8a43e0b113d476b896de53585c1281.tar.xz |
[bugfix] self-referencing collection pages for status replies (#2364)
Diffstat (limited to 'internal/paging')
-rw-r--r-- | internal/paging/boundary.go | 17 | ||||
-rw-r--r-- | internal/paging/page.go | 83 | ||||
-rw-r--r-- | internal/paging/page_test.go | 56 | ||||
-rw-r--r-- | internal/paging/util.go | 43 |
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 -} |