feat(forge): add repo label iterator
Some checks failed
Security Scan / security (push) Failing after 12s
Test / test (push) Successful in 2m12s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 09:28:20 +00:00
parent 0d80388d18
commit f27a01d3c5
3 changed files with 81 additions and 1 deletions

View file

@ -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` |

View file

@ -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) {

View file

@ -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", "<http://"+r.Host+"/api/v1/repos/test-org/org-repo/labels?page=2>; rel=\"next\", <http://"+r.Host+"/api/v1/repos/test-org/org-repo/labels?page=2>; 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()