From 8f5ebbfa5c3203a65c625ffeb69375e033270fbc Mon Sep 17 00:00:00 2001 From: Dhananjay Mishra Date: Mon, 9 Feb 2026 02:35:09 +0000 Subject: [PATCH 1/2] ListAllTopics now support pagination --- github/github-iterators.go | 31 ++++++++++++++ github/github-iterators_test.go | 72 +++++++++++++++++++++++++++++++++ github/repos.go | 7 +++- github/repos_test.go | 13 ++++-- 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/github/github-iterators.go b/github/github-iterators.go index 691b5ee10ce..80ee2efe59b 100644 --- a/github/github-iterators.go +++ b/github/github-iterators.go @@ -2780,6 +2780,37 @@ func (s *RepositoriesService) ListIter(ctx context.Context, user string, opts *R } } +// ListAllTopicsIter returns an iterator that paginates through all results of ListAllTopics. +func (s *RepositoriesService) ListAllTopicsIter(ctx context.Context, owner string, repo string, opts *ListOptions) iter.Seq2[string, error] { + return func(yield func(string, error) bool) { + // Create a copy of opts to avoid mutating the caller's struct + if opts == nil { + opts = &ListOptions{} + } else { + opts = Ptr(*opts) + } + + for { + items, resp, err := s.ListAllTopics(ctx, owner, repo, opts) + if err != nil { + yield(*new(string), err) + return + } + + for _, item := range items { + if !yield(item, nil) { + return + } + } + + if resp.NextPage == 0 { + break + } + opts.Page = resp.NextPage + } + } +} + // ListAutolinksIter returns an iterator that paginates through all results of ListAutolinks. func (s *RepositoriesService) ListAutolinksIter(ctx context.Context, owner string, repo string, opts *ListOptions) iter.Seq2[*Autolink, error] { return func(yield func(*Autolink, error) bool) { diff --git a/github/github-iterators_test.go b/github/github-iterators_test.go index fa5197ccf22..c8280ca9f49 100644 --- a/github/github-iterators_test.go +++ b/github/github-iterators_test.go @@ -6423,6 +6423,78 @@ func TestRepositoriesService_ListIter(t *testing.T) { } } +func TestRepositoriesService_ListAllTopicsIter(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + var callNum int + mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { + callNum++ + switch callNum { + case 1: + w.Header().Set("Link", `; rel="next"`) + fmt.Fprint(w, `[{},{},{}]`) + case 2: + fmt.Fprint(w, `[{},{},{},{}]`) + case 3: + fmt.Fprint(w, `[{},{}]`) + case 4: + w.WriteHeader(http.StatusNotFound) + case 5: + fmt.Fprint(w, `[{},{}]`) + } + }) + + iter := client.Repositories.ListAllTopicsIter(t.Context(), "", "", nil) + var gotItems int + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 7; gotItems != want { + t.Errorf("client.Repositories.ListAllTopicsIter call 1 got %v items; want %v", gotItems, want) + } + + opts := &ListOptions{} + iter = client.Repositories.ListAllTopicsIter(t.Context(), "", "", opts) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + } + if want := 2; gotItems != want { + t.Errorf("client.Repositories.ListAllTopicsIter call 2 got %v items; want %v", gotItems, want) + } + + iter = client.Repositories.ListAllTopicsIter(t.Context(), "", "", nil) + gotItems = 0 + for _, err := range iter { + gotItems++ + if err == nil { + t.Error("expected error; got nil") + } + } + if gotItems != 1 { + t.Errorf("client.Repositories.ListAllTopicsIter call 3 got %v items; want 1 (an error)", gotItems) + } + + iter = client.Repositories.ListAllTopicsIter(t.Context(), "", "", nil) + gotItems = 0 + iter(func(item string, err error) bool { + gotItems++ + if err != nil { + t.Errorf("Unexpected error: %v", err) + } + return false + }) + if gotItems != 1 { + t.Errorf("client.Repositories.ListAllTopicsIter call 4 got %v items; want 1 (an error)", gotItems) + } +} + func TestRepositoriesService_ListAutolinksIter(t *testing.T) { t.Parallel() client, mux, _ := setup(t) diff --git a/github/repos.go b/github/repos.go index d2ded49943e..ac76f1f3f80 100644 --- a/github/repos.go +++ b/github/repos.go @@ -1928,8 +1928,13 @@ type repositoryTopics struct { // GitHub API docs: https://docs.github.com/rest/repos/repos#get-all-repository-topics // //meta:operation GET /repos/{owner}/{repo}/topics -func (s *RepositoriesService) ListAllTopics(ctx context.Context, owner, repo string) ([]string, *Response, error) { +func (s *RepositoriesService) ListAllTopics(ctx context.Context, owner, repo string, opts *ListOptions) ([]string, *Response, error) { u := fmt.Sprintf("repos/%v/%v/topics", owner, repo) + u, err := addOptions(u, opts) + if err != nil { + return nil, nil, err + } + req, err := s.client.NewRequest("GET", u, nil) if err != nil { return nil, nil, err diff --git a/github/repos_test.go b/github/repos_test.go index 33843242c58..c943c12947d 100644 --- a/github/repos_test.go +++ b/github/repos_test.go @@ -3476,11 +3476,16 @@ func TestRepositoriesService_ListAllTopics(t *testing.T) { mux.HandleFunc("/repos/o/r/topics", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") testHeader(t, r, "Accept", mediaTypeTopicsPreview) + testFormValues(t, r, values{ + "page": "1", + "per_page": "30", + }) fmt.Fprint(w, `{"names":["go", "go-github", "github"]}`) }) ctx := t.Context() - got, _, err := client.Repositories.ListAllTopics(ctx, "o", "r") + opts := &ListOptions{Page: 1, PerPage: 30} + got, _, err := client.Repositories.ListAllTopics(ctx, "o", "r", opts) if err != nil { t.Fatalf("Repositories.ListAllTopics returned error: %v", err) } @@ -3492,12 +3497,12 @@ func TestRepositoriesService_ListAllTopics(t *testing.T) { const methodName = "ListAllTopics" testBadOptions(t, methodName, func() (err error) { - _, _, err = client.Repositories.ListAllTopics(ctx, "\n", "\n") + _, _, err = client.Repositories.ListAllTopics(ctx, "\n", "\n", opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - got, resp, err := client.Repositories.ListAllTopics(ctx, "o", "r") + got, resp, err := client.Repositories.ListAllTopics(ctx, "o", "r", opts) if got != nil { t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) } @@ -3516,7 +3521,7 @@ func TestRepositoriesService_ListAllTopics_emptyTopics(t *testing.T) { }) ctx := t.Context() - got, _, err := client.Repositories.ListAllTopics(ctx, "o", "r") + got, _, err := client.Repositories.ListAllTopics(ctx, "o", "r", nil) if err != nil { t.Fatalf("Repositories.ListAllTopics returned error: %v", err) } From 422dfe26bb151d8f4e62fcefd4ebc21770d34cd0 Mon Sep 17 00:00:00 2001 From: Dhananjay Mishra Date: Mon, 9 Feb 2026 12:01:49 +0000 Subject: [PATCH 2/2] quick fix --- github/gen-iterators.go | 2 +- github/github-iterators_test.go | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/github/gen-iterators.go b/github/gen-iterators.go index 59b0da2838c..242863ac20e 100644 --- a/github/gen-iterators.go +++ b/github/gen-iterators.go @@ -126,7 +126,7 @@ type method struct { // This is needed for methods that internally unmarshal a wrapper struct // even though they return a slice. var customTestJSON = map[string]string{ - // Uncomment in #3978: "ListAllTopics": `{"names": []}`, + "ListAllTopics": `{"names": []}`, "ListUserInstallations": `{"installations": []}`, } diff --git a/github/github-iterators_test.go b/github/github-iterators_test.go index c8280ca9f49..0f7428a0cbf 100644 --- a/github/github-iterators_test.go +++ b/github/github-iterators_test.go @@ -6432,15 +6432,15 @@ func TestRepositoriesService_ListAllTopicsIter(t *testing.T) { switch callNum { case 1: w.Header().Set("Link", `; rel="next"`) - fmt.Fprint(w, `[{},{},{}]`) + fmt.Fprint(w, `{"names": ["","",""]}`) case 2: - fmt.Fprint(w, `[{},{},{},{}]`) + fmt.Fprint(w, `{"names": ["","","",""]}`) case 3: - fmt.Fprint(w, `[{},{}]`) + fmt.Fprint(w, `{"names": ["",""]}`) case 4: w.WriteHeader(http.StatusNotFound) case 5: - fmt.Fprint(w, `[{},{}]`) + fmt.Fprint(w, `{"names": ["",""]}`) } })