fj/cmd/paginate.go

44 lines
1.2 KiB
Go
Raw Permalink Normal View History

feat(cmd): pagination unification + `fj api --paginate` Before this, only `release list` walked pages. `repo list`, `pr list` (the non-filter branch), and `issue list` all passed `PageSize: limit` directly to the gitea SDK — which silently caps PageSize at 50, so any request for more than 50 results was truncated to 50 with no warning. `--limit` was effectively a per-page hint, not a real limit. ## Changes - New `cmd/paginate.go` — generic `paginateGitea[T any]` that walks pages until the response is short or the limit is reached. Uses Go 1.20 generics so each list command keeps its existing typed slice without conversion overhead. - `repo list` — paginates ListUserRepos. - `pr list` — paginates ListRepoPullRequests in both branches: - With client-side filters (assignee, author, labels, search, draft, head, base): pull all pages then filter+limit. - Without filters: paginate up to limit. - `issue list` — paginates ListRepoIssues. Overshoots 2x because the API returns both issues AND PRs and we filter PRs out client-side; the overshoot keeps us bounded but reduces the chance of returning fewer results than `--limit`. ## `fj api --paginate` Mirrors `gh api --paginate`: - Follows RFC 5988 `Link: rel="next"` headers (Forgejo emits these on list endpoints). - Concatenates each page's JSON array into a single array via `concatPaginatedJSON`. If a page is not a JSON array, errors with a clear message — `--paginate` only makes sense for paginatable endpoints. - GET-only (errors on POST/PUT/DELETE). - Reuses the same auth and custom headers across pages; the body-size limit applies per-page. Refactored the request execution into a `doOnce` closure so the loop body isn't a copy of the single-request path. Verified live: $ fj api 'repos/public/claude-code-proxy/commits?limit=2' \ --paginate --jq '. | length' 44 (44 = total commits in the repo, walked via Link headers from a 2-per-page starting query.) Out of scope for this commit, deferred: - De-duplicating cmd/aliases.go ↔ cmd/actions.go subtrees (the type mismatch they caused is already fixed in the prior commit; the duplication itself is polish).
2026-05-02 15:46:22 -06:00
package cmd
// paginateGitea walks pages of a gitea SDK list method until the response
// is short (last page) or we hit limit. limit=0 means unlimited.
//
// Forgejo/Gitea caps PageSize at 50, so naive `PageSize: limit` for limit > 50
// silently truncated results across most `fj * list` commands. This helper
// centralizes the loop so every list command paginates consistently.
//
// fetch is called with (page, pageSize) and returns the items for that page.
// The 1-based `page` matches the gitea SDK convention.
func paginateGitea[T any](limit int, fetch func(page, pageSize int) ([]T, error)) ([]T, error) {
const maxPageSize = 50
pageSize := maxPageSize
if limit > 0 && limit < pageSize {
pageSize = limit
}
var all []T
for page := 1; ; page++ {
if limit > 0 && len(all) >= limit {
break
}
batch, err := fetch(page, pageSize)
if err != nil {
return all, err
}
if len(batch) == 0 {
break
}
all = append(all, batch...)
// A short page (less than the requested size) is the conventional
// "you've reached the end" signal — saves one extra round-trip.
if len(batch) < pageSize {
break
}
}
if limit > 0 && len(all) > limit {
all = all[:limit]
}
return all, nil
}