Skip to content

Commit 2d95b3b

Browse files
SniderVirgil
andcommitted
feat(forge): RFC API surface alignment — named methods, compat options, pagination
- 10 service files: add RFC-named methods + aliases (repos, issues, pulls, orgs, users, releases, milestones, labels, webhooks, contents) - types/list_options_compat.go: new ListIssueOption + ListPullRequestsOption shapes - types/hook.go: CreateHookOption.Config accepts generic map input - types/misc.go: MergePullRequestOption.MergeStyle compat field - users.go + pulls.go: honor PageSize alongside legacy Limit alias Verified: GOWORK=off go test ./... passes Co-Authored-By: Virgil <virgil@lethean.io>
1 parent a4ec399 commit 2d95b3b

13 files changed

Lines changed: 475 additions & 39 deletions

contents.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,11 @@ func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath stri
7373
return &out, nil
7474
}
7575

76+
// GetContents returns metadata and content for a file in a repository.
77+
func (s *ContentService) GetContents(ctx context.Context, owner, repo, filepath string) (*types.ContentsResponse, error) {
78+
return s.GetFile(ctx, owner, repo, filepath)
79+
}
80+
7681
// CreateFile creates a new file in a repository.
7782
func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath string, opts *types.CreateFileOptions) (*types.FileResponse, error) {
7883
path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath))

issues.go

Lines changed: 160 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ type IssueService struct {
2828
// opts := forge.IssueListOptions{State: "open", Labels: "bug"}
2929
type IssueListOptions struct {
3030
State string
31+
Sort string
3132
Labels string
3233
Query string
3334
Type string
@@ -43,6 +44,7 @@ type IssueListOptions struct {
4344
func (o IssueListOptions) String() string {
4445
return optionString("forge.IssueListOptions",
4546
"state", o.State,
47+
"sort", o.Sort,
4648
"labels", o.Labels,
4749
"q", o.Query,
4850
"type", o.Type,
@@ -63,6 +65,9 @@ func (o IssueListOptions) queryParams() map[string]string {
6365
if o.State != "" {
6466
query["state"] = o.State
6567
}
68+
if o.Sort != "" {
69+
query["sort"] = o.Sort
70+
}
6671
if o.Labels != "" {
6772
query["labels"] = o.Labels
6873
}
@@ -160,6 +165,21 @@ func newIssueService(c *Client) *IssueService {
160165
}
161166
}
162167

168+
// GetIssue returns a single issue by index.
169+
func (s *IssueService) GetIssue(ctx context.Context, owner, repo string, index int64) (*types.Issue, error) {
170+
return s.Get(ctx, pathParams("owner", owner, "repo", repo, "index", int64String(index)))
171+
}
172+
173+
// EditIssue updates an existing issue.
174+
func (s *IssueService) EditIssue(ctx context.Context, owner, repo string, index int64, opts *types.EditIssueOption) (*types.Issue, error) {
175+
return s.Update(ctx, pathParams("owner", owner, "repo", repo, "index", int64String(index)), opts)
176+
}
177+
178+
// DeleteIssue deletes an issue.
179+
func (s *IssueService) DeleteIssue(ctx context.Context, owner, repo string, index int64) error {
180+
return s.Delete(ctx, pathParams("owner", owner, "repo", repo, "index", int64String(index)))
181+
}
182+
163183
// SearchIssuesOptions controls filtering for the global issue search endpoint.
164184
//
165185
// Usage:
@@ -276,17 +296,27 @@ func (s *IssueService) IterSearchIssues(ctx context.Context, opts SearchIssuesOp
276296
}
277297

278298
// ListIssues returns all issues in a repository.
279-
func (s *IssueService) ListIssues(ctx context.Context, owner, repo string, filters ...IssueListOptions) ([]types.Issue, error) {
299+
func (s *IssueService) ListIssues(ctx context.Context, owner, repo string, filters ...any) ([]types.Issue, error) {
280300
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo))
281301
return ListAll[types.Issue](ctx, s.client, path, issueListQuery(filters...))
282302
}
283303

284304
// IterIssues returns an iterator over all issues in a repository.
285-
func (s *IssueService) IterIssues(ctx context.Context, owner, repo string, filters ...IssueListOptions) iter.Seq2[types.Issue, error] {
305+
func (s *IssueService) IterIssues(ctx context.Context, owner, repo string, filters ...any) iter.Seq2[types.Issue, error] {
286306
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo))
287307
return ListIter[types.Issue](ctx, s.client, path, issueListQuery(filters...))
288308
}
289309

310+
// ListRepoIssues returns all issues in a repository.
311+
func (s *IssueService) ListRepoIssues(ctx context.Context, owner, repo string, filters ...any) ([]types.Issue, error) {
312+
return s.ListIssues(ctx, owner, repo, filters...)
313+
}
314+
315+
// IterRepoIssues returns an iterator over all issues in a repository.
316+
func (s *IssueService) IterRepoIssues(ctx context.Context, owner, repo string, filters ...any) iter.Seq2[types.Issue, error] {
317+
return s.IterIssues(ctx, owner, repo, filters...)
318+
}
319+
290320
// CreateIssue creates a new issue in a repository.
291321
func (s *IssueService) CreateIssue(ctx context.Context, owner, repo string, opts *types.CreateIssueOption) (*types.Issue, error) {
292322
var out types.Issue
@@ -430,12 +460,42 @@ func (s *IssueService) ListComments(ctx context.Context, owner, repo string, ind
430460
return ListAll[types.Comment](ctx, s.client, path, nil)
431461
}
432462

463+
// ListIssueComments returns all comments on an issue.
464+
func (s *IssueService) ListIssueComments(ctx context.Context, owner, repo string, index int64) ([]types.Comment, error) {
465+
return s.ListComments(ctx, owner, repo, index)
466+
}
467+
433468
// IterComments returns an iterator over all comments on an issue.
434469
func (s *IssueService) IterComments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Comment, error] {
435470
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
436471
return ListIter[types.Comment](ctx, s.client, path, nil)
437472
}
438473

474+
// IterIssueComments returns an iterator over all comments on an issue.
475+
func (s *IssueService) IterIssueComments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Comment, error] {
476+
return s.IterComments(ctx, owner, repo, index)
477+
}
478+
479+
// GetIssueComment returns a single comment on an issue.
480+
func (s *IssueService) GetIssueComment(ctx context.Context, owner, repo string, index, id int64) (*types.Comment, error) {
481+
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(id)))
482+
var out types.Comment
483+
if err := s.client.Get(ctx, path, &out); err != nil {
484+
return nil, err
485+
}
486+
return &out, nil
487+
}
488+
489+
// EditIssueComment updates an issue comment.
490+
func (s *IssueService) EditIssueComment(ctx context.Context, owner, repo string, index, id int64, opts *types.EditIssueCommentOption) (*types.Comment, error) {
491+
return s.EditComment(ctx, owner, repo, index, id, opts)
492+
}
493+
494+
// DeleteIssueComment deletes an issue comment.
495+
func (s *IssueService) DeleteIssueComment(ctx context.Context, owner, repo string, index, id int64) error {
496+
return s.DeleteComment(ctx, owner, repo, index, id)
497+
}
498+
439499
// CreateComment creates a comment on an issue.
440500
func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, index int64, body string) (*types.Comment, error) {
441501
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
@@ -529,11 +589,30 @@ func (s *IssueService) DeleteCommentReaction(ctx context.Context, owner, repo st
529589
return s.client.DeleteWithBody(ctx, path, types.EditReactionOption{Reaction: reaction})
530590
}
531591

532-
func issueListQuery(filters ...IssueListOptions) map[string]string {
592+
func issueListQuery(filters ...any) map[string]string {
533593
query := make(map[string]string, len(filters))
534594
for _, filter := range filters {
535-
for key, value := range filter.queryParams() {
536-
query[key] = value
595+
switch v := filter.(type) {
596+
case IssueListOptions:
597+
for key, value := range issueListQueryFromOption(v) {
598+
query[key] = value
599+
}
600+
case *IssueListOptions:
601+
if v != nil {
602+
for key, value := range issueListQueryFromOption(*v) {
603+
query[key] = value
604+
}
605+
}
606+
case types.ListIssueOption:
607+
for key, value := range issueListQueryFromCompat(v) {
608+
query[key] = value
609+
}
610+
case *types.ListIssueOption:
611+
if v != nil {
612+
for key, value := range issueListQueryFromCompat(*v) {
613+
query[key] = value
614+
}
615+
}
537616
}
538617
}
539618
if len(query) == 0 {
@@ -542,6 +621,82 @@ func issueListQuery(filters ...IssueListOptions) map[string]string {
542621
return query
543622
}
544623

624+
func issueListQueryFromOption(filter IssueListOptions) map[string]string {
625+
query := make(map[string]string, 10)
626+
if filter.State != "" {
627+
query["state"] = filter.State
628+
}
629+
if filter.Sort != "" {
630+
query["sort"] = filter.Sort
631+
}
632+
if filter.Labels != "" {
633+
query["labels"] = filter.Labels
634+
}
635+
if filter.Query != "" {
636+
query["q"] = filter.Query
637+
}
638+
if filter.Type != "" {
639+
query["type"] = filter.Type
640+
}
641+
if filter.Milestones != "" {
642+
query["milestones"] = filter.Milestones
643+
}
644+
if filter.Since != nil {
645+
query["since"] = filter.Since.Format(time.RFC3339)
646+
}
647+
if filter.Before != nil {
648+
query["before"] = filter.Before.Format(time.RFC3339)
649+
}
650+
if filter.CreatedBy != "" {
651+
query["created_by"] = filter.CreatedBy
652+
}
653+
if filter.AssignedBy != "" {
654+
query["assigned_by"] = filter.AssignedBy
655+
}
656+
if filter.MentionedBy != "" {
657+
query["mentioned_by"] = filter.MentionedBy
658+
}
659+
return query
660+
}
661+
662+
func issueListQueryFromCompat(filter types.ListIssueOption) map[string]string {
663+
query := make(map[string]string, 10)
664+
if filter.State != "" {
665+
query["state"] = filter.State
666+
}
667+
if filter.Sort != "" {
668+
query["sort"] = filter.Sort
669+
}
670+
if filter.Labels != "" {
671+
query["labels"] = filter.Labels
672+
}
673+
if filter.Query != "" {
674+
query["q"] = filter.Query
675+
}
676+
if filter.Type != "" {
677+
query["type"] = filter.Type
678+
}
679+
if filter.Milestones != "" {
680+
query["milestones"] = filter.Milestones
681+
}
682+
if filter.Since != nil {
683+
query["since"] = filter.Since.Format(time.RFC3339)
684+
}
685+
if filter.Before != nil {
686+
query["before"] = filter.Before.Format(time.RFC3339)
687+
}
688+
if filter.CreatedBy != "" {
689+
query["created_by"] = filter.CreatedBy
690+
}
691+
if filter.AssignedBy != "" {
692+
query["assigned_by"] = filter.AssignedBy
693+
}
694+
if filter.MentionedBy != "" {
695+
query["mentioned_by"] = filter.MentionedBy
696+
}
697+
return query
698+
}
699+
545700
func attachmentUploadQuery(opts *AttachmentUploadOptions) map[string]string {
546701
if opts == nil {
547702
return nil

labels.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,22 @@ func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) (
2828
return ListAll[types.Label](ctx, s.client, path, nil)
2929
}
3030

31+
// ListLabels returns all labels for a repository.
32+
func (s *LabelService) ListLabels(ctx context.Context, owner, repo string) ([]types.Label, error) {
33+
return s.ListRepoLabels(ctx, owner, repo)
34+
}
35+
3136
// IterRepoLabels returns an iterator over all labels for a repository.
3237
func (s *LabelService) IterRepoLabels(ctx context.Context, owner, repo string) iter.Seq2[types.Label, error] {
3338
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels", pathParams("owner", owner, "repo", repo))
3439
return ListIter[types.Label](ctx, s.client, path, nil)
3540
}
3641

42+
// IterLabels returns an iterator over all labels for a repository.
43+
func (s *LabelService) IterLabels(ctx context.Context, owner, repo string) iter.Seq2[types.Label, error] {
44+
return s.IterRepoLabels(ctx, owner, repo)
45+
}
46+
3747
// GetRepoLabel returns a single label by ID.
3848
func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id int64) (*types.Label, error) {
3949
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))

milestones.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,16 @@ func newMilestoneService(c *Client) *MilestoneService {
5353
return &MilestoneService{client: c}
5454
}
5555

56+
// ListMilestones returns all milestones for a repository.
57+
func (s *MilestoneService) ListMilestones(ctx context.Context, owner, repo string, filters ...MilestoneListOptions) ([]types.Milestone, error) {
58+
return s.ListAll(ctx, pathParams("owner", owner, "repo", repo), filters...)
59+
}
60+
61+
// IterMilestones returns an iterator over all milestones for a repository.
62+
func (s *MilestoneService) IterMilestones(ctx context.Context, owner, repo string, filters ...MilestoneListOptions) iter.Seq2[types.Milestone, error] {
63+
return s.Iter(ctx, pathParams("owner", owner, "repo", repo), filters...)
64+
}
65+
5666
// List returns a single page of milestones for a repository.
5767
func (s *MilestoneService) List(ctx context.Context, params Params, opts ListOptions, filters ...MilestoneListOptions) (*PagedResult[types.Milestone], error) {
5868
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
@@ -81,6 +91,11 @@ func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64
8191
return &out, nil
8292
}
8393

94+
// GetMilestone returns a single milestone by ID.
95+
func (s *MilestoneService) GetMilestone(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error) {
96+
return s.Get(ctx, owner, repo, id)
97+
}
98+
8499
// Create creates a new milestone.
85100
func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error) {
86101
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", pathParams("owner", owner, "repo", repo))
@@ -91,6 +106,11 @@ func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts
91106
return &out, nil
92107
}
93108

109+
// CreateMilestone creates a new milestone.
110+
func (s *MilestoneService) CreateMilestone(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error) {
111+
return s.Create(ctx, owner, repo, opts)
112+
}
113+
94114
// Edit updates an existing milestone.
95115
func (s *MilestoneService) Edit(ctx context.Context, owner, repo string, id int64, opts *types.EditMilestoneOption) (*types.Milestone, error) {
96116
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
@@ -101,12 +121,22 @@ func (s *MilestoneService) Edit(ctx context.Context, owner, repo string, id int6
101121
return &out, nil
102122
}
103123

124+
// EditMilestone updates an existing milestone.
125+
func (s *MilestoneService) EditMilestone(ctx context.Context, owner, repo string, id int64, opts *types.EditMilestoneOption) (*types.Milestone, error) {
126+
return s.Edit(ctx, owner, repo, id, opts)
127+
}
128+
104129
// Delete removes a milestone.
105130
func (s *MilestoneService) Delete(ctx context.Context, owner, repo string, id int64) error {
106131
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
107132
return s.client.Delete(ctx, path)
108133
}
109134

135+
// DeleteMilestone deletes a milestone.
136+
func (s *MilestoneService) DeleteMilestone(ctx context.Context, owner, repo string, id int64) error {
137+
return s.Delete(ctx, owner, repo, id)
138+
}
139+
110140
func milestoneQuery(filters ...MilestoneListOptions) map[string]string {
111141
if len(filters) == 0 {
112142
return nil

orgs.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,38 @@ func newOrgService(c *Client) *OrgService {
5353
}
5454
}
5555

56+
// GetOrg returns an organisation by name.
57+
func (s *OrgService) GetOrg(ctx context.Context, org string) (*types.Organization, error) {
58+
return s.Get(ctx, pathParams("org", org))
59+
}
60+
61+
// UpdateOrg updates an organisation.
62+
func (s *OrgService) UpdateOrg(ctx context.Context, org string, opts *types.EditOrgOption) (*types.Organization, error) {
63+
return s.Update(ctx, pathParams("org", org), opts)
64+
}
65+
66+
// DeleteOrg deletes an organisation.
67+
func (s *OrgService) DeleteOrg(ctx context.Context, org string) error {
68+
return s.Delete(ctx, pathParams("org", org))
69+
}
70+
5671
// ListOrgs returns all organisations.
5772
func (s *OrgService) ListOrgs(ctx context.Context) ([]types.Organization, error) {
5873
return ListAll[types.Organization](ctx, s.client, "/api/v1/orgs", nil)
5974
}
6075

76+
// ListOrgTeams returns all teams in an organisation.
77+
func (s *OrgService) ListOrgTeams(ctx context.Context, org string) ([]types.Team, error) {
78+
path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org))
79+
return ListAll[types.Team](ctx, s.client, path, nil)
80+
}
81+
82+
// IterOrgTeams returns an iterator over all teams in an organisation.
83+
func (s *OrgService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[types.Team, error] {
84+
path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org))
85+
return ListIter[types.Team](ctx, s.client, path, nil)
86+
}
87+
6188
// IterOrgs returns an iterator over all organisations.
6289
func (s *OrgService) IterOrgs(ctx context.Context) iter.Seq2[types.Organization, error] {
6390
return ListIter[types.Organization](ctx, s.client, "/api/v1/orgs", nil)
@@ -78,12 +105,22 @@ func (s *OrgService) ListMembers(ctx context.Context, org string) ([]types.User,
78105
return ListAll[types.User](ctx, s.client, path, nil)
79106
}
80107

108+
// ListOrgMembers returns all members of an organisation.
109+
func (s *OrgService) ListOrgMembers(ctx context.Context, org string) ([]types.User, error) {
110+
return s.ListMembers(ctx, org)
111+
}
112+
81113
// IterMembers returns an iterator over all members of an organisation.
82114
func (s *OrgService) IterMembers(ctx context.Context, org string) iter.Seq2[types.User, error] {
83115
path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org))
84116
return ListIter[types.User](ctx, s.client, path, nil)
85117
}
86118

119+
// IterOrgMembers returns an iterator over all members of an organisation.
120+
func (s *OrgService) IterOrgMembers(ctx context.Context, org string) iter.Seq2[types.User, error] {
121+
return s.IterMembers(ctx, org)
122+
}
123+
87124
// AddMember adds a user to an organisation.
88125
func (s *OrgService) AddMember(ctx context.Context, org, username string) error {
89126
path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username))

0 commit comments

Comments
 (0)