From f27a01d3c5ee8beffc1d1112ff5b0c18c1c55cab Mon Sep 17 00:00:00 2001 From: Virgil Date: Wed, 1 Apr 2026 09:28:20 +0000 Subject: [PATCH] feat(forge): add repo label iterator Co-Authored-By: Virgil --- docs/architecture.md | 2 +- forge/labels.go | 31 ++++++++++++++++++++++++++++ forge/labels_test.go | 49 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 1 deletion(-) diff --git a/docs/architecture.md b/docs/architecture.md index e57015e..0a72fce 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -89,7 +89,7 @@ The `gitea/` package mirrors this using `GITEA_URL`/`GITEA_TOKEN` and `gitea.*` | `client.go` | `New`, `NewFromConfig`, `GetCurrentUser`, `ForkRepo`, `CreatePullRequest` | | `repos.go` | `ListOrgRepos`, `ListOrgReposIter`, `ListUserRepos`, `ListUserReposIter`, `GetRepo`, `CreateOrgRepo`, `DeleteRepo`, `MigrateRepo` | | `issues.go` | `ListIssues`, `ListIssuesIter`, `GetIssue`, `CreateIssue`, `EditIssue`, `AssignIssue`, `ListPullRequests`, `ListPullRequestsIter`, `GetPullRequest`, `CreateIssueComment`, `ListIssueComments`, `ListIssueCommentsIter`, `CloseIssue` | -| `labels.go` | `ListOrgLabels`, `ListRepoLabels`, `CreateRepoLabel`, `GetLabelByName`, `EnsureLabel`, `AddIssueLabels`, `RemoveIssueLabel` | +| `labels.go` | `ListOrgLabels`, `ListRepoLabels`, `ListRepoLabelsIter`, `CreateRepoLabel`, `GetLabelByName`, `EnsureLabel`, `AddIssueLabels`, `RemoveIssueLabel` | | `prs.go` | `MergePullRequest`, `SetPRDraft`, `ListPRReviews`, `GetCombinedStatus`, `DismissReview` | | `webhooks.go` | `CreateRepoWebhook`, `ListRepoWebhooks` | | `orgs.go` | `ListMyOrgs`, `GetOrg`, `CreateOrg` | diff --git a/forge/labels.go b/forge/labels.go index 6af459f..3972279 100644 --- a/forge/labels.go +++ b/forge/labels.go @@ -3,6 +3,8 @@ package forge import ( + "iter" + strings "dappco.re/go/core/scm/internal/ax/stringsx" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" @@ -71,6 +73,35 @@ func (c *Client) ListRepoLabels(owner, repo string) ([]*forgejo.Label, error) { return all, nil } +// ListRepoLabelsIter returns an iterator over labels for a repository. +// Usage: ListRepoLabelsIter(...) +func (c *Client) ListRepoLabelsIter(owner, repo string) iter.Seq2[*forgejo.Label, error] { + return func(yield func(*forgejo.Label, error) bool) { + page := 1 + + for { + labels, resp, err := c.api.ListRepoLabels(owner, repo, forgejo.ListLabelsOptions{ + ListOptions: forgejo.ListOptions{Page: page, PageSize: 50}, + }) + if err != nil { + yield(nil, log.E("forge.ListRepoLabels", "failed to list repo labels", err)) + return + } + + for _, label := range labels { + if !yield(label, nil) { + return + } + } + + if resp == nil || page >= resp.LastPage { + break + } + page++ + } + } +} + // CreateRepoLabel creates a label on a repository. // Usage: CreateRepoLabel(...) func (c *Client) CreateRepoLabel(owner, repo string, opts forgejo.CreateLabelOption) (*forgejo.Label, error) { diff --git a/forge/labels_test.go b/forge/labels_test.go index b00dbe8..5d8c7b2 100644 --- a/forge/labels_test.go +++ b/forge/labels_test.go @@ -3,6 +3,8 @@ package forge import ( + "net/http" + "net/http/httptest" "testing" forgejo "codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2" @@ -35,6 +37,53 @@ func TestClient_ListRepoLabels_Bad_ServerError_Good(t *testing.T) { assert.Contains(t, err.Error(), "failed to list repo labels") } +func TestClient_ListRepoLabelsIter_Good_Paginates_Good(t *testing.T) { + mux := http.NewServeMux() + mux.HandleFunc("/api/v1/version", func(w http.ResponseWriter, r *http.Request) { + jsonResponse(w, map[string]string{"version": "1.21.0"}) + }) + mux.HandleFunc("/api/v1/repos/test-org/org-repo/labels", func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Query().Get("page") { + case "2": + jsonResponse(w, []map[string]any{ + {"id": 3, "name": "documentation", "color": "#00aa00"}, + }) + default: + w.Header().Set("Link", "; rel=\"next\", ; rel=\"last\"") + jsonResponse(w, []map[string]any{ + {"id": 1, "name": "bug", "color": "#ff0000"}, + {"id": 2, "name": "feature", "color": "#0000ff"}, + }) + } + }) + + srv := httptest.NewServer(mux) + defer srv.Close() + + client, err := New(srv.URL, "test-token") + require.NoError(t, err) + + var names []string + for label, err := range client.ListRepoLabelsIter("test-org", "org-repo") { + require.NoError(t, err) + names = append(names, label.Name) + } + + require.Len(t, names, 3) + assert.Equal(t, []string{"bug", "feature", "documentation"}, names) +} + +func TestClient_ListRepoLabelsIter_Bad_ServerError_Good(t *testing.T) { + client, srv := newErrorServer(t) + defer srv.Close() + + for _, err := range client.ListRepoLabelsIter("test-org", "org-repo") { + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to list repo labels") + break + } +} + func TestClient_CreateRepoLabel_Good(t *testing.T) { client, srv := newTestClient(t) defer srv.Close()