Skip to content

Conversation

@alexandear
Copy link
Contributor

@alexandear alexandear commented Jan 18, 2026

Support Go iterator ranging via Scan and Scan2 functions. MustIter and ScanAndCollect are convenience wrappers. This is an alternative to #3916.

Iteration is implemented only for two methods that I was able to verify manually:

  • IssuesService.ListComments - offset-based
  • SecurityAdvisoriesService.ListRepositorySecurityAdvisoriesForOrg - cursor (after)-based

One drawback of this implementation is the requirement to add the optional parameter reqOpts ...RequestOption to every List* endpoint, which is not a backward-compatible change.

Adapted from gitlab-org/api/client-go.

@codecov
Copy link

codecov bot commented Jan 18, 2026

Codecov Report

❌ Patch coverage is 57.60870% with 39 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.24%. Comparing base (a21a651) to head (5b330c7).
⚠️ Report is 2 commits behind head on master.

Files with missing lines Patch % Lines
example/iterpagination/main.go 0.00% 25 Missing ⚠️
github/github.go 33.33% 6 Missing and 2 partials ⚠️
github/pagination.go 88.23% 3 Missing and 3 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master    #3925      +/-   ##
==========================================
- Coverage   92.45%   92.24%   -0.22%     
==========================================
  Files         203      205       +2     
  Lines       14980    15067      +87     
==========================================
+ Hits        13850    13898      +48     
- Misses        927      961      +34     
- Partials      203      208       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@alexandear alexandear force-pushed the feat/iter-pagination branch 2 times, most recently from 7205459 to aa7deb2 Compare January 27, 2026 15:20
@alexandear alexandear force-pushed the feat/iter-pagination branch from aa7deb2 to 5b330c7 Compare January 27, 2026 15:25
@alexandear
Copy link
Contributor Author

@gmlewis @merchantmoh-debug this is ready for review as a concept, as I was told here.

@alexandear alexandear marked this pull request as ready for review January 27, 2026 15:31
@gmlewis gmlewis changed the title feat: Iterator support via Scan and Scan2 feat: Add iterator support via Scan and Scan2 Jan 27, 2026
@gmlewis gmlewis added the NeedsReview PR is awaiting a review before merging. label Jan 28, 2026
Copy link
Collaborator

@gmlewis gmlewis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for working out this PR, @alexandear! I appreciate it.

Here are my initial thoughts:

  • this approach is more invasive than I had hoped such that all iterable endpoints will need to be upgraded to support the iterators
  • the actual example usage is less intuitive than I had hoped it might be
  • looking at the ease-of-use from an end-user's point of view, it seems like the go:generator approach will provide a cleaner and easier-to-understand API
  • from a maintenance burden perspective, the two approaches appear comparable and yet I'm guessing that more "Issues" might be filed against this approach helping people to understand how to set up the iteration for their particular use case or just simply because they couldn't wire up their own anonymous function because they couldn't figure out how to copy/paste the return values for the function they are trying to call

Therefore, I'm leaning toward the go:generate approach at this point... but I would like to hear from others as well if they have any opinions.

cc: @stevehipwell - @zyfy29 - @Not-Dhananjay-Mishra

@gmlewis gmlewis added the DO NOT MERGE Do not merge this PR. label Jan 28, 2026
@gmlewis
Copy link
Collaborator

gmlewis commented Jan 28, 2026

I suppose that a hybrid approach might be to use Scan and Scan2 under the hood but use go:generate to make a really easy-to-use API for users of this repo?

Would there be any benefits to this approach over the other go:generate-only approach?

@Not-Dhananjay-Mishra
Copy link
Contributor

@alexandear @gmlewis I see Scan2 approach support resp.After cursor-based pagination but I couldn't find that in go:generate approach.
am i correct?

@merchantmoh-debug
Copy link
Contributor

@alexandear @gmlewis I see Scan2 approach support resp.After cursor-based pagination but I couldn't find that in go:generate approach. am i correct?

@Not-Dhananjay-Mishra You are spot on regarding the current gap. The existing generator template primarily targeted int-based offset pagination (NextPage), but we can trivially extend it to support the cursor variations.

@merchantmoh-debug
Copy link
Contributor

I suppose that a hybrid approach might be to use Scan and Scan2 under the hood but use go:generate to make a really easy-to-use API for users of this repo?

Would there be any benefits to this approach over the other go:generate-only approach?

@gmlewis I strongly believe the pure go:generate approach is superior to both the Scan runtime and a "Hybrid" approach.

On usage of "Hybrid" (Generating wrappers around Scan): If we are already running go:generate, we have the opportunity to produce "native" Go loops that call the API directly (Client -> Check Error -> Yield -> Advance). Wrapping Scan would just add a layer of runtime indirection (and potentially reflection) without any DX benefit. Generated code should be simple, readable, and "boring"—it should look exactly like the code a user would write by hand.

On Cursor Support: The Response struct actually supports three disjoint cursor models (NextPageToken, Cursor, After). A generator can statically handle this nuance by encoding the precedence logic in the template:

go
// Template logic to handle all pagination types automatically
if resp.NextPageToken != "" { opts.Page = resp.NextPageToken }
else if resp.Cursor != "" { opts.Cursor = resp.Cursor }
else if resp.After != "" { opts.After = resp.After }
I can update gen-iterators.go to support all three modes, giving us full coverage without the runtime complexity of Scan.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

DO NOT MERGE Do not merge this PR. NeedsReview PR is awaiting a review before merging.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants