v1.4.0
Adds a pages sub-package: a Go 1.23 range-over-func iterator for
Link-header pagination, plus a typed wrapper that decodes each page
into elements of any type. The iterator runs on a caller-supplied
*http.Client, so the existing RateLimit, Throttle, Retry, oauth2,
and ETag layers apply per page with no extra wiring. Also extends
ghtest with a LinkHeader fixture builder so tests can construct
RFC 8288 Link headers without rolling their own. No new runtime
dependencies in the root module.
Added
pages.Pages(ctx, client, method, url, headers): range-over-func
iterator over paginated REST responses. WalksLink: rel="next"
and yields each*http.Response. Caller drains and closes each
body. Errors stop iteration after one yield; a clean end of
pagination (norel="next") stops silently.pages.As[T any](ctx, client, method, url, headers): typed wrapper
that decodes each page into[]Twith the standard library JSON
decoder and yields elements one at a time. Iterator owns each
response body and closes it after decoding, including on caller
break and on context cancellation.Thas no constraint, so
*github.Repositoryand similar SDK types work without
implementingjson.Unmarshaler.pages.ErrInvalidLinkHeader: exported sentinel surfaced when a
response Link header is structurally malformed. A missing
rel="next"is treated as a clean end of pagination, not an error.ghtest.LinkHeader(baseURL, page, perPage, lastPage): builds an
RFC 8288 Link header value for fixture servers. Returns "" when
lastPage <= 1so handlers can branch on a single string. Pairs
withghtest.Write304IfMatchandghtest.WriteSecondaryLimit.
Documentation
doc.goadds a paragraph naming thepagessub-package alongside
the existingetag,ratelimit, andthrottlenotes.README.mdadds an "Iterating over paginated results" recipe under
the Recipes section, with a runnable snippet usingpages.Asplus
WithETagCache.TESTING.mdreplaces the manual Link-header pagination recipe with
one that usesghtest.LinkHeader, mirroring how the
secondary-rate-limit recipe referencesghtest.WriteSecondaryLimit.examples/README.mdadds thelist-all-repos/row to the table.
Examples
- New
examples/list-all-repos/: usespages.As[*github.Repository]
to walk/user/reposagainstapi.github.comend to end. Shows
the iterator composing withWithETagCacheandWithUserAgent.
Dependencies
No changes in the root module. The examples/ module is unchanged.
Tooling
.golangci.yaml:bodycloseadded to the test-file linter
exclusion list. The iter.Seq2 yield-then-close pattern in
pages_test.goproduces false positives the linter cannot trace
through; the suppression is scoped to_test.gopaths so production
code still gets full bodyclose coverage. The two//nolint:bodyclose
directives inpages/pages.goremain (caller-closes contract on
yielded responses; close-via-decodePage on the typed wrapper).