feat(scm): add issue iterators
Some checks failed
Security Scan / security (push) Failing after 11s
Test / test (push) Successful in 2m18s

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-01 07:23:21 +00:00
parent 64042ac8a6
commit 0193bd50ea
4 changed files with 125 additions and 0 deletions

View file

@ -67,6 +67,55 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*forgejo
return all, nil
}
// ListIssuesIter returns an iterator over issues for the given repository.
// Usage: ListIssuesIter(...)
func (c *Client) ListIssuesIter(owner, repo string, opts ListIssuesOpts) iter.Seq2[*forgejo.Issue, error] {
state := forgejo.StateOpen
switch opts.State {
case "closed":
state = forgejo.StateClosed
case "all":
state = forgejo.StateAll
}
limit := opts.Limit
if limit == 0 {
limit = 50
}
page := opts.Page
if page == 0 {
page = 1
}
return func(yield func(*forgejo.Issue, error) bool) {
for {
issues, resp, err := c.api.ListRepoIssues(owner, repo, forgejo.ListIssueOption{
ListOptions: forgejo.ListOptions{Page: page, PageSize: limit},
State: state,
Type: forgejo.IssueTypeIssue,
Labels: opts.Labels,
})
if err != nil {
yield(nil, log.E("forge.ListIssues", "failed to list issues", err))
return
}
for _, issue := range issues {
if !yield(issue, nil) {
return
}
}
if len(issues) < limit || len(issues) == 0 {
break
}
if resp != nil && resp.LastPage > 0 && page >= resp.LastPage {
break
}
page++
}
}
}
// GetIssue returns a single issue by number.
// Usage: GetIssue(...)
func (c *Client) GetIssue(owner, repo string, number int64) (*forgejo.Issue, error) {

View file

@ -62,6 +62,20 @@ func TestClient_ListIssues_Good_Paginates_Good(t *testing.T) {
assert.Equal(t, "Issue 2", issues[1].Title)
}
func TestClient_ListIssuesIter_Good_Paginates_Good(t *testing.T) {
client, srv := newPaginatedIssuesClient(t)
defer srv.Close()
var titles []string
for issue, err := range client.ListIssuesIter("test-org", "org-repo", ListIssuesOpts{Limit: 1}) {
require.NoError(t, err)
titles = append(titles, issue.Title)
}
require.Len(t, titles, 2)
assert.Equal(t, []string{"Issue 1", "Issue 2"}, titles)
}
func TestClient_ListIssues_Good_StateMapping_Good(t *testing.T) {
tests := []struct {
name string

View file

@ -63,6 +63,54 @@ func (c *Client) ListIssues(owner, repo string, opts ListIssuesOpts) ([]*gitea.I
return all, nil
}
// ListIssuesIter returns an iterator over issues for the given repository.
// Usage: ListIssuesIter(...)
func (c *Client) ListIssuesIter(owner, repo string, opts ListIssuesOpts) iter.Seq2[*gitea.Issue, error] {
state := gitea.StateOpen
switch opts.State {
case "closed":
state = gitea.StateClosed
case "all":
state = gitea.StateAll
}
limit := opts.Limit
if limit == 0 {
limit = 50
}
page := opts.Page
if page == 0 {
page = 1
}
return func(yield func(*gitea.Issue, error) bool) {
for {
issues, resp, err := c.api.ListRepoIssues(owner, repo, gitea.ListIssueOption{
ListOptions: gitea.ListOptions{Page: page, PageSize: limit},
State: state,
Type: gitea.IssueTypeIssue,
})
if err != nil {
yield(nil, log.E("gitea.ListIssues", "failed to list issues", err))
return
}
for _, issue := range issues {
if !yield(issue, nil) {
return
}
}
if len(issues) < limit || len(issues) == 0 {
break
}
if resp != nil && resp.LastPage > 0 && page >= resp.LastPage {
break
}
page++
}
}
}
// GetIssue returns a single issue by number.
// Usage: GetIssue(...)
func (c *Client) GetIssue(owner, repo string, number int64) (*gitea.Issue, error) {

View file

@ -62,6 +62,20 @@ func TestClient_ListIssues_Good_Paginates_Good(t *testing.T) {
assert.Equal(t, "Issue 2", issues[1].Title)
}
func TestClient_ListIssuesIter_Good_Paginates_Good(t *testing.T) {
client, srv := newPaginatedIssuesClient(t)
defer srv.Close()
var titles []string
for issue, err := range client.ListIssuesIter("test-org", "org-repo", ListIssuesOpts{Limit: 1}) {
require.NoError(t, err)
titles = append(titles, issue.Title)
}
require.Len(t, titles, 2)
assert.Equal(t, []string{"Issue 1", "Issue 2"}, titles)
}
func TestClient_ListIssues_Good_StateMapping_Good(t *testing.T) {
tests := []struct {
name string