[bugfix] further paging mishaps (#2884)

* FURTHER paging shenanigans 🥲

* remove cursor logic from ToLinkURL()

* fix up paging tests

---------

Co-authored-by: tobi <tobi.smethurst@protonmail.com>
This commit is contained in:
kim 2024-04-30 15:22:23 +01:00 committed by GitHub
parent ec7c983e46
commit a8254a40e7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 163 additions and 138 deletions

View file

@ -161,7 +161,7 @@ func (suite *RepliesGetTestSuite) TestGetRepliesNext() {
"type": "OrderedCollectionPage", "type": "OrderedCollectionPage",
"id": targetStatus.URI + "/replies?limit=20&only_other_accounts=false", "id": targetStatus.URI + "/replies?limit=20&only_other_accounts=false",
"partOf": targetStatus.URI + "/replies?only_other_accounts=false", "partOf": targetStatus.URI + "/replies?only_other_accounts=false",
"next": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false", "next": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&max_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false",
"prev": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false", "prev": "http://localhost:8080/users/the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY/replies?limit=20&min_id=01FF25D5Q0DH7CHD57CTRS6WK0&only_other_accounts=false",
"orderedItems": []string{"http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0"}, "orderedItems": []string{"http://localhost:8080/users/admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0"},
"totalItems": 1, "totalItems": 1,

View file

@ -21,8 +21,7 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io"
"math/rand"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"net/url" "net/url"
@ -42,9 +41,6 @@ import (
"github.com/tomnomnom/linkheader" "github.com/tomnomnom/linkheader"
) )
// random reader according to current-time source seed.
var randRd = rand.New(rand.NewSource(time.Now().Unix()))
type FollowTestSuite struct { type FollowTestSuite struct {
AccountStandardTestSuite AccountStandardTestSuite
} }
@ -76,33 +72,33 @@ func (suite *FollowTestSuite) TestFollowSelf() {
defer result.Body.Close() defer result.Body.Close()
// check the response // check the response
b, err := ioutil.ReadAll(result.Body) b, err := io.ReadAll(result.Body)
_ = b _ = b
assert.NoError(suite.T(), err) assert.NoError(suite.T(), err)
} }
func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit2() { func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit2() {
suite.testGetFollowersPage(2, "backward") suite.testGetFollowersPage(2, "newestToOldest")
} }
func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit4() { func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit4() {
suite.testGetFollowersPage(4, "backward") suite.testGetFollowersPage(4, "newestToOldest")
} }
func (suite *FollowTestSuite) TestGetFollowersPageBackwardLimit6() { func (suite *FollowTestSuite) TestGetFollowersPageNewestToOldestLimit6() {
suite.testGetFollowersPage(6, "backward") suite.testGetFollowersPage(6, "newestToOldest")
} }
func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit2() { func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit2() {
suite.testGetFollowersPage(2, "forward") suite.testGetFollowersPage(2, "oldestToNewest")
} }
func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit4() { func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit4() {
suite.testGetFollowersPage(4, "forward") suite.testGetFollowersPage(4, "oldestToNewest")
} }
func (suite *FollowTestSuite) TestGetFollowersPageForwardLimit6() { func (suite *FollowTestSuite) TestGetFollowersPageOldestToNewestLimit6() {
suite.testGetFollowersPage(6, "forward") suite.testGetFollowersPage(6, "oldestToNewest")
} }
func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) { func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string) {
@ -117,8 +113,11 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
var i int var i int
for _, targetAccount := range suite.testAccounts { // Have each account in the testrig follow the account
if targetAccount.ID == requestingAccount.ID { // that is requesting their followers from the API.
for _, account := range suite.testAccounts {
targetAccount := requestingAccount
if account.ID == targetAccount.ID {
// we cannot be our own target... // we cannot be our own target...
continue continue
} }
@ -132,9 +131,9 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
ID: id, ID: id,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
URI: fmt.Sprintf("%s/follow/%s", targetAccount.URI, id), URI: fmt.Sprintf("%s/follow/%s", account.URI, id),
AccountID: targetAccount.ID, AccountID: account.ID,
TargetAccountID: requestingAccount.ID, TargetAccountID: targetAccount.ID,
}) })
suite.NoError(err) suite.NoError(err)
@ -152,15 +151,17 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
var query string var query string
switch direction { switch direction {
case "backward": case "newestToOldest":
// Set the starting query to page backward from newest. // Set the starting query to page from
// newest (ie., first entry in slice).
acc := expectAccounts[0].(*model.Account) acc := expectAccounts[0].(*model.Account)
newest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID) newest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID)
expectAccounts = expectAccounts[1:] expectAccounts = expectAccounts[1:]
query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID)
case "forward": case "oldestToNewest":
// Set the starting query to page forward from the oldest. // Set the starting query to page from
// oldest (ie., last entry in slice).
acc := expectAccounts[len(expectAccounts)-1].(*model.Account) acc := expectAccounts[len(expectAccounts)-1].(*model.Account)
oldest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID) oldest, _ := suite.db.GetFollow(ctx, acc.ID, requestingAccount.ID)
expectAccounts = expectAccounts[:len(expectAccounts)-1] expectAccounts = expectAccounts[:len(expectAccounts)-1]
@ -208,9 +209,9 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
) )
switch direction { switch direction {
case "backward": case "newestToOldest":
// When paging backwards (DESC) we: // When paging newest to oldest (ie., first page to last page):
// - iter from end of received accounts // - iter from start of received accounts
// - iterate backward through received accounts // - iterate backward through received accounts
// - stop when we reach last index of received accounts // - stop when we reach last index of received accounts
// - compare each received with the first index of expected accounts // - compare each received with the first index of expected accounts
@ -221,8 +222,8 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
expect = func(i []interface{}) interface{} { return i[0] } expect = func(i []interface{}) interface{} { return i[0] }
trunc = func(i []interface{}) []interface{} { return i[1:] } trunc = func(i []interface{}) []interface{} { return i[1:] }
case "forward": case "oldestToNewest":
// When paging forwards (ASC) we: // When paging oldest to newest (ie., last page to first page):
// - iter from end of received accounts // - iter from end of received accounts
// - iterate backward through received accounts // - iterate backward through received accounts
// - stop when we reach first index of received accounts // - stop when we reach first index of received accounts
@ -230,7 +231,7 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
// - after each compare, drop the last index of expected accounts // - after each compare, drop the last index of expected accounts
start = func(i []*model.Account) int { return len(i) - 1 } start = func(i []*model.Account) int { return len(i) - 1 }
iter = func(i int) int { return i - 1 } iter = func(i int) int { return i - 1 }
check = func(idx int, i []*model.Account) bool { return idx >= 0 } check = func(idx int, _ []*model.Account) bool { return idx >= 0 }
expect = func(i []interface{}) interface{} { return i[len(i)-1] } expect = func(i []interface{}) interface{} { return i[len(i)-1] }
trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] }
} }
@ -256,7 +257,14 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
// Parse response link header values. // Parse response link header values.
values := result.Header.Values("Link") values := result.Header.Values("Link")
links := linkheader.ParseMultiple(values) links := linkheader.ParseMultiple(values)
filteredLinks := links.FilterByRel("next")
var filteredLinks linkheader.Links
if direction == "newestToOldest" {
filteredLinks = links.FilterByRel("next")
} else {
filteredLinks = links.FilterByRel("prev")
}
suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p)
// A ref link header was set. // A ref link header was set.
@ -271,28 +279,28 @@ func (suite *FollowTestSuite) testGetFollowersPage(limit int, direction string)
} }
} }
func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit2() { func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit2() {
suite.testGetFollowingPage(2, "backward") suite.testGetFollowingPage(2, "newestToOldest")
} }
func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit4() { func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit4() {
suite.testGetFollowingPage(4, "backward") suite.testGetFollowingPage(4, "newestToOldest")
} }
func (suite *FollowTestSuite) TestGetFollowingPageBackwardLimit6() { func (suite *FollowTestSuite) TestGetFollowingPageNewestToOldestLimit6() {
suite.testGetFollowingPage(6, "backward") suite.testGetFollowingPage(6, "newestToOldest")
} }
func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit2() { func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit2() {
suite.testGetFollowingPage(2, "forward") suite.testGetFollowingPage(2, "oldestToNewest")
} }
func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit4() { func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit4() {
suite.testGetFollowingPage(4, "forward") suite.testGetFollowingPage(4, "oldestToNewest")
} }
func (suite *FollowTestSuite) TestGetFollowingPageForwardLimit6() { func (suite *FollowTestSuite) TestGetFollowingPageOldestToNewestLimit6() {
suite.testGetFollowingPage(6, "forward") suite.testGetFollowingPage(6, "oldestToNewest")
} }
func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) { func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string) {
@ -307,8 +315,11 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
var i int var i int
// Have the account that is requesting their following
// list from the API follow each account in the testrig.
for _, targetAccount := range suite.testAccounts { for _, targetAccount := range suite.testAccounts {
if targetAccount.ID == requestingAccount.ID { account := requestingAccount
if targetAccount.ID == account.ID {
// we cannot be our own target... // we cannot be our own target...
continue continue
} }
@ -322,8 +333,8 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
ID: id, ID: id,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
URI: fmt.Sprintf("%s/follow/%s", requestingAccount.URI, id), URI: fmt.Sprintf("%s/follow/%s", account.URI, id),
AccountID: requestingAccount.ID, AccountID: account.ID,
TargetAccountID: targetAccount.ID, TargetAccountID: targetAccount.ID,
}) })
suite.NoError(err) suite.NoError(err)
@ -342,15 +353,17 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
var query string var query string
switch direction { switch direction {
case "backward": case "newestToOldest":
// Set the starting query to page backward from newest. // Set the starting query to page from
// newest (ie., first entry in slice).
acc := expectAccounts[0].(*model.Account) acc := expectAccounts[0].(*model.Account)
newest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID) newest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID)
expectAccounts = expectAccounts[1:] expectAccounts = expectAccounts[1:]
query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID)
case "forward": case "oldestToNewest":
// Set the starting query to page forward from the oldest. // Set the starting query to page from
// oldest (ie., last entry in slice).
acc := expectAccounts[len(expectAccounts)-1].(*model.Account) acc := expectAccounts[len(expectAccounts)-1].(*model.Account)
oldest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID) oldest, _ := suite.db.GetFollow(ctx, requestingAccount.ID, acc.ID)
expectAccounts = expectAccounts[:len(expectAccounts)-1] expectAccounts = expectAccounts[:len(expectAccounts)-1]
@ -397,9 +410,9 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
) )
switch direction { switch direction {
case "backward": case "newestToOldest":
// When paging backwards (DESC) we: // When paging newest to oldest (ie., first page to last page):
// - iter from end of received accounts // - iter from start of received accounts
// - iterate backward through received accounts // - iterate backward through received accounts
// - stop when we reach last index of received accounts // - stop when we reach last index of received accounts
// - compare each received with the first index of expected accounts // - compare each received with the first index of expected accounts
@ -410,8 +423,8 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
expect = func(i []interface{}) interface{} { return i[0] } expect = func(i []interface{}) interface{} { return i[0] }
trunc = func(i []interface{}) []interface{} { return i[1:] } trunc = func(i []interface{}) []interface{} { return i[1:] }
case "forward": case "oldestToNewest":
// When paging forwards (ASC) we: // When paging oldest to newest (ie., last page to first page):
// - iter from end of received accounts // - iter from end of received accounts
// - iterate backward through received accounts // - iterate backward through received accounts
// - stop when we reach first index of received accounts // - stop when we reach first index of received accounts
@ -419,7 +432,7 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
// - after each compare, drop the last index of expected accounts // - after each compare, drop the last index of expected accounts
start = func(i []*model.Account) int { return len(i) - 1 } start = func(i []*model.Account) int { return len(i) - 1 }
iter = func(i int) int { return i - 1 } iter = func(i int) int { return i - 1 }
check = func(idx int, i []*model.Account) bool { return idx >= 0 } check = func(idx int, _ []*model.Account) bool { return idx >= 0 }
expect = func(i []interface{}) interface{} { return i[len(i)-1] } expect = func(i []interface{}) interface{} { return i[len(i)-1] }
trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] }
} }
@ -445,7 +458,14 @@ func (suite *FollowTestSuite) testGetFollowingPage(limit int, direction string)
// Parse response link header values. // Parse response link header values.
values := result.Header.Values("Link") values := result.Header.Values("Link")
links := linkheader.ParseMultiple(values) links := linkheader.ParseMultiple(values)
filteredLinks := links.FilterByRel("next")
var filteredLinks linkheader.Links
if direction == "newestToOldest" {
filteredLinks = links.FilterByRel("next")
} else {
filteredLinks = links.FilterByRel("prev")
}
suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p)
// A ref link header was set. // A ref link header was set.

View file

@ -107,28 +107,28 @@ func (suite *GetTestSuite) TestGet() {
]`, dst.String()) ]`, dst.String())
} }
func (suite *GetTestSuite) TestGetPageBackwardLimit2() { func (suite *GetTestSuite) TestGetPageNewestToOldestLimit2() {
suite.testGetPage(2, "backward") suite.testGetPage(2, "newestToOldest")
} }
func (suite *GetTestSuite) TestGetPageBackwardLimit4() { func (suite *GetTestSuite) TestGetPageNewestToOldestLimit4() {
suite.testGetPage(4, "backward") suite.testGetPage(4, "newestToOldest")
} }
func (suite *GetTestSuite) TestGetPageBackwardLimit6() { func (suite *GetTestSuite) TestGetPageNewestToOldestLimit6() {
suite.testGetPage(6, "backward") suite.testGetPage(6, "newestToOldest")
} }
func (suite *GetTestSuite) TestGetPageForwardLimit2() { func (suite *GetTestSuite) TestGetPageOldestToNewestLimit2() {
suite.testGetPage(2, "forward") suite.testGetPage(2, "oldestToNewest")
} }
func (suite *GetTestSuite) TestGetPageForwardLimit4() { func (suite *GetTestSuite) TestGetPageOldestToNewestLimit4() {
suite.testGetPage(4, "forward") suite.testGetPage(4, "oldestToNewest")
} }
func (suite *GetTestSuite) TestGetPageForwardLimit6() { func (suite *GetTestSuite) TestGetPageOldestToNewestLimit6() {
suite.testGetPage(6, "forward") suite.testGetPage(6, "oldestToNewest")
} }
func (suite *GetTestSuite) testGetPage(limit int, direction string) { func (suite *GetTestSuite) testGetPage(limit int, direction string) {
@ -143,8 +143,11 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
var i int var i int
for _, targetAccount := range suite.testAccounts { // Have each account in the testrig follow req the
if targetAccount.ID == requestingAccount.ID { // account requesting their followers from the API.
for _, account := range suite.testAccounts {
targetAccount := requestingAccount
if account.ID == requestingAccount.ID {
// we cannot be our own target... // we cannot be our own target...
continue continue
} }
@ -158,9 +161,9 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
ID: id, ID: id,
CreatedAt: now, CreatedAt: now,
UpdatedAt: now, UpdatedAt: now,
URI: fmt.Sprintf("%s/follow/%s", targetAccount.URI, id), URI: fmt.Sprintf("%s/follow/%s", account.URI, id),
AccountID: targetAccount.ID, AccountID: account.ID,
TargetAccountID: requestingAccount.ID, TargetAccountID: targetAccount.ID,
}) })
suite.NoError(err) suite.NoError(err)
@ -178,15 +181,17 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
var query string var query string
switch direction { switch direction {
case "backward": case "newestToOldest":
// Set the starting query to page backward from newest. // Set the starting query to page from
// newest (ie., first entry in slice).
acc := expectAccounts[0].(*model.Account) acc := expectAccounts[0].(*model.Account)
newest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID) newest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID)
expectAccounts = expectAccounts[1:] expectAccounts = expectAccounts[1:]
query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID) query = fmt.Sprintf("limit=%d&max_id=%s", limit, newest.ID)
case "forward": case "oldestToNewest":
// Set the starting query to page forward from the oldest. // Set the starting query to page from
// oldest (ie., last entry in slice).
acc := expectAccounts[len(expectAccounts)-1].(*model.Account) acc := expectAccounts[len(expectAccounts)-1].(*model.Account)
oldest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID) oldest, _ := suite.db.GetFollowRequest(ctx, acc.ID, requestingAccount.ID)
expectAccounts = expectAccounts[:len(expectAccounts)-1] expectAccounts = expectAccounts[:len(expectAccounts)-1]
@ -232,9 +237,9 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
) )
switch direction { switch direction {
case "backward": case "newestToOldest":
// When paging backwards (DESC) we: // When paging newest to oldest (ie., first page to last page):
// - iter from end of received accounts // - iter from start of received accounts
// - iterate backward through received accounts // - iterate backward through received accounts
// - stop when we reach last index of received accounts // - stop when we reach last index of received accounts
// - compare each received with the first index of expected accounts // - compare each received with the first index of expected accounts
@ -245,8 +250,8 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
expect = func(i []interface{}) interface{} { return i[0] } expect = func(i []interface{}) interface{} { return i[0] }
trunc = func(i []interface{}) []interface{} { return i[1:] } trunc = func(i []interface{}) []interface{} { return i[1:] }
case "forward": case "oldestToNewest":
// When paging forwards (ASC) we: // When paging oldest to newest (ie., last page to first page):
// - iter from end of received accounts // - iter from end of received accounts
// - iterate backward through received accounts // - iterate backward through received accounts
// - stop when we reach first index of received accounts // - stop when we reach first index of received accounts
@ -254,7 +259,7 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
// - after each compare, drop the last index of expected accounts // - after each compare, drop the last index of expected accounts
start = func(i []*model.Account) int { return len(i) - 1 } start = func(i []*model.Account) int { return len(i) - 1 }
iter = func(i int) int { return i - 1 } iter = func(i int) int { return i - 1 }
check = func(idx int, i []*model.Account) bool { return idx >= 0 } check = func(idx int, _ []*model.Account) bool { return idx >= 0 }
expect = func(i []interface{}) interface{} { return i[len(i)-1] } expect = func(i []interface{}) interface{} { return i[len(i)-1] }
trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] } trunc = func(i []interface{}) []interface{} { return i[:len(i)-1] }
} }
@ -280,7 +285,14 @@ func (suite *GetTestSuite) testGetPage(limit int, direction string) {
// Parse response link header values. // Parse response link header values.
values := result.Header.Values("Link") values := result.Header.Values("Link")
links := linkheader.ParseMultiple(values) links := linkheader.ParseMultiple(values)
filteredLinks := links.FilterByRel("next")
var filteredLinks linkheader.Links
if direction == "newestToOldest" {
filteredLinks = links.FilterByRel("next")
} else {
filteredLinks = links.FilterByRel("prev")
}
suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p) suite.NotEmpty(filteredLinks, "no next link provided with more remaining accounts on page=%d", p)
// A ref link header was set. // A ref link header was set.

View file

@ -54,6 +54,13 @@ func EitherMinID(minID, sinceID string) Boundary {
the cursor value, and max_id provides a the cursor value, and max_id provides a
limiting value to the results. limiting value to the results.
But to further complicate it...
The "next" and "prev" relative links provided
in the link header are ALWAYS DESCENDING. Which
means we will ALWAYS provide next=?max_id and
prev=?min_id. *shakes fist at mastodon api*
*/ */
switch { switch {
case minID != "": case minID != "":
@ -67,7 +74,12 @@ func EitherMinID(minID, sinceID string) Boundary {
// SinceID ... // SinceID ...
func SinceID(sinceID string) Boundary { func SinceID(sinceID string) Boundary {
return Boundary{ return Boundary{
Name: "since_id", // even when a since_id query is
// provided, the next / prev rel
// links are DESCENDING with
// next:max_id and prev:min_id.
// so ALWAYS use min_id as name.
Name: "min_id",
Value: sinceID, Value: sinceID,
Order: OrderDescending, Order: OrderDescending,
} }

View file

@ -202,8 +202,9 @@ func Page_PageFunc[WithID any](p *Page, in []WithID, get func(WithID) string) []
return in return in
} }
// Next creates a new instance for the next returnable page, using // Prev creates a new instance for the next returnable page, using
// given max value. This preserves original limit and max key name. // given max value. This will always assume DESCENDING for Mastodon
// API compatibility, but in case of change it can support both.
func (p *Page) Next(lo, hi string) *Page { func (p *Page) Next(lo, hi string) *Page {
if p == nil || lo == "" || hi == "" { if p == nil || lo == "" || hi == "" {
// no paging. // no paging.
@ -216,25 +217,22 @@ func (p *Page) Next(lo, hi string) *Page {
// Set original limit. // Set original limit.
p2.Limit = p.Limit p2.Limit = p.Limit
if p.order().Ascending() { // NOTE:
// When ascending, next page // We ALWAYS assume the order
// needs to start with min at // when creating next / prev
// the next highest value. // links is DESCENDING. It will
p2.Min = p.Min.new(hi) // always use prev: ?max_name
p2.Max = p.Max.new("") p2.Min = p.Min.new("")
} else { p2.Max = p.Max.new(lo)
// When descending, next page p2.Min.Order = OrderDescending
// needs to start with max at p2.Max.Order = OrderDescending
// the next lowest value.
p2.Min = p.Min.new("")
p2.Max = p.Max.new(lo)
}
return p2 return p2
} }
// Prev creates a new instance for the prev returnable page, using // Prev creates a new instance for the prev returnable page, using
// given min value. This preserves original limit and min key name. // given min value. This will always assume DESCENDING for Mastodon
// API compatibility, but in case of change it can support both.
func (p *Page) Prev(lo, hi string) *Page { func (p *Page) Prev(lo, hi string) *Page {
if p == nil || lo == "" || hi == "" { if p == nil || lo == "" || hi == "" {
// no paging. // no paging.
@ -247,19 +245,15 @@ func (p *Page) Prev(lo, hi string) *Page {
// Set original limit. // Set original limit.
p2.Limit = p.Limit p2.Limit = p.Limit
if p.order().Ascending() { // NOTE:
// When ascending, prev page // We ALWAYS assume the order
// needs to start with max at // when creating next / prev
// the next lowest value. // links is DESCENDING. It will
p2.Min = p.Min.new("") // always use prev: ?min_name
p2.Max = p.Max.new(lo) p2.Min = p.Min.new(hi)
} else { p2.Max = p.Max.new("")
// When descending, next page p2.Min.Order = OrderDescending
// needs to start with max at p2.Max.Order = OrderDescending
// the next lowest value.
p2.Min = p.Min.new(hi)
p2.Max = p.Max.new("")
}
return p2 return p2
} }
@ -289,27 +283,14 @@ func (p *Page) ToLinkURL(proto, host, path string, queryParams url.Values) *url.
queryParams = cloneQuery(queryParams) queryParams = cloneQuery(queryParams)
} }
var cursor string if p.Min.Value != "" {
// Set page-minimum cursor value.
// Depending on page ordering, the queryParams.Set(p.Min.Name, p.Min.Value)
// page will be cursored by either
// the min or max query parameter.
if p.order().Ascending() {
cursor = p.Min.Name
} else {
cursor = p.Max.Name
} }
if cursor != "" { if p.Max.Value != "" {
if p.Min.Value != "" { // Set page-maximum cursor value.
// Set page-minimum cursor value. queryParams.Set(p.Max.Name, p.Max.Value)
queryParams.Set(cursor, p.Min.Value)
}
if p.Max.Value != "" {
// Set page-maximum cursor value.
queryParams.Set(cursor, p.Max.Value)
}
} }
if p.Limit > 0 { if p.Limit > 0 {