Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
| 1895ee9bbd |
115 changed files with 1750 additions and 19185 deletions
|
|
@ -33,9 +33,9 @@ The library is a flat package (`package forge`) with a layered design:
|
|||
|
||||
5. **`config.go`** — Config resolution: flags > env (`FORGE_URL`, `FORGE_TOKEN`) > defaults (`http://localhost:3000`).
|
||||
|
||||
6. **`forge.go`** — Top-level `Forge` struct aggregating all 20 service fields. Created via `NewForge(url, token)` or `NewForgeFromConfig(flagURL, flagToken)`.
|
||||
6. **`forge.go`** — Top-level `Forge` struct aggregating all 18 service fields. Created via `NewForge(url, token)` or `NewForgeFromConfig(flagURL, flagToken)`.
|
||||
|
||||
7. **Service files** (`repos.go`, `issues.go`, etc.) — Each service struct embeds `Resource[T,C,U]` for standard CRUD, then adds hand-written action methods (e.g. `Fork`, `Pin`, `MirrorSync`). 20 services total: repos, issues, pulls, orgs, users, teams, admin, branches, releases, labels, webhooks, notifications, packages, actions, contents, wiki, commits, milestones, misc, activitypub.
|
||||
7. **Service files** (`repos.go`, `issues.go`, etc.) — Each service struct embeds `Resource[T,C,U]` for standard CRUD, then adds hand-written action methods (e.g. `Fork`, `Pin`, `MirrorSync`). 18 services total: repos, issues, pulls, orgs, users, teams, admin, branches, releases, labels, webhooks, notifications, packages, actions, contents, wiki, commits, misc.
|
||||
|
||||
8. **`types/`** — Generated Go types from `swagger.v1.json` (229 types). The `//go:generate` directive lives in `types/generate.go`. **Do not hand-edit generated type files** — modify `cmd/forgegen/` instead.
|
||||
|
||||
|
|
|
|||
194
actions.go
194
actions.go
|
|
@ -2,22 +2,15 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// ActionsService handles CI/CD actions operations across repositories and
|
||||
// organisations — secrets, variables, workflow dispatches, and tasks.
|
||||
// organisations — secrets, variables, and workflow dispatches.
|
||||
// No Resource embedding — heterogeneous endpoints across repo and org levels.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Actions.ListRepoSecrets(ctx, "core", "go-forge")
|
||||
type ActionsService struct {
|
||||
client *Client
|
||||
}
|
||||
|
|
@ -28,239 +21,82 @@ func newActionsService(c *Client) *ActionsService {
|
|||
|
||||
// ListRepoSecrets returns all secrets for a repository.
|
||||
func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string) ([]types.Secret, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo)
|
||||
return ListAll[types.Secret](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterRepoSecrets returns an iterator over all secrets for a repository.
|
||||
func (s *ActionsService) IterRepoSecrets(ctx context.Context, owner, repo string) iter.Seq2[types.Secret, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo)
|
||||
return ListIter[types.Secret](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CreateRepoSecret creates or updates a secret in a repository.
|
||||
// Forgejo expects a PUT with {"data": "secret-value"} body.
|
||||
func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{secretname}", pathParams("owner", owner, "repo", repo, "secretname", name))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name)
|
||||
body := map[string]string{"data": data}
|
||||
return s.client.Put(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// DeleteRepoSecret removes a secret from a repository.
|
||||
func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{secretname}", pathParams("owner", owner, "repo", repo, "secretname", name))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListRepoVariables returns all action variables for a repository.
|
||||
func (s *ActionsService) ListRepoVariables(ctx context.Context, owner, repo string) ([]types.ActionVariable, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo)
|
||||
return ListAll[types.ActionVariable](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterRepoVariables returns an iterator over all action variables for a repository.
|
||||
func (s *ActionsService) IterRepoVariables(ctx context.Context, owner, repo string) iter.Seq2[types.ActionVariable, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo)
|
||||
return ListIter[types.ActionVariable](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CreateRepoVariable creates a new action variable in a repository.
|
||||
// Forgejo expects a POST with {"value": "var-value"} body.
|
||||
func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{variablename}", pathParams("owner", owner, "repo", repo, "variablename", name))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/%s", owner, repo, name)
|
||||
body := types.CreateVariableOption{Value: value}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// UpdateRepoVariable updates an existing action variable in a repository.
|
||||
func (s *ActionsService) UpdateRepoVariable(ctx context.Context, owner, repo, name string, opts *types.UpdateVariableOption) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{variablename}", pathParams("owner", owner, "repo", repo, "variablename", name))
|
||||
return s.client.Put(ctx, path, opts, nil)
|
||||
}
|
||||
|
||||
// DeleteRepoVariable removes an action variable from a repository.
|
||||
func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{variablename}", pathParams("owner", owner, "repo", repo, "variablename", name))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/%s", owner, repo, name)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListOrgSecrets returns all secrets for an organisation.
|
||||
func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string) ([]types.Secret, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/secrets", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/actions/secrets", org)
|
||||
return ListAll[types.Secret](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterOrgSecrets returns an iterator over all secrets for an organisation.
|
||||
func (s *ActionsService) IterOrgSecrets(ctx context.Context, org string) iter.Seq2[types.Secret, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/secrets", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/actions/secrets", org)
|
||||
return ListIter[types.Secret](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// ListOrgVariables returns all action variables for an organisation.
|
||||
func (s *ActionsService) ListOrgVariables(ctx context.Context, org string) ([]types.ActionVariable, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/variables", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/actions/variables", org)
|
||||
return ListAll[types.ActionVariable](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterOrgVariables returns an iterator over all action variables for an organisation.
|
||||
func (s *ActionsService) IterOrgVariables(ctx context.Context, org string) iter.Seq2[types.ActionVariable, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/variables", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/actions/variables", org)
|
||||
return ListIter[types.ActionVariable](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetOrgVariable returns a single action variable for an organisation.
|
||||
func (s *ActionsService) GetOrgVariable(ctx context.Context, org, name string) (*types.ActionVariable, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
|
||||
var out types.ActionVariable
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateOrgVariable creates a new action variable in an organisation.
|
||||
func (s *ActionsService) CreateOrgVariable(ctx context.Context, org, name, value string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
|
||||
body := types.CreateVariableOption{Value: value}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// UpdateOrgVariable updates an existing action variable in an organisation.
|
||||
func (s *ActionsService) UpdateOrgVariable(ctx context.Context, org, name string, opts *types.UpdateVariableOption) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
|
||||
return s.client.Put(ctx, path, opts, nil)
|
||||
}
|
||||
|
||||
// DeleteOrgVariable removes an action variable from an organisation.
|
||||
func (s *ActionsService) DeleteOrgVariable(ctx context.Context, org, name string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/variables/{variablename}", pathParams("org", org, "variablename", name))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// CreateOrgSecret creates or updates a secret in an organisation.
|
||||
func (s *ActionsService) CreateOrgSecret(ctx context.Context, org, name, data string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/secrets/{secretname}", pathParams("org", org, "secretname", name))
|
||||
body := map[string]string{"data": data}
|
||||
return s.client.Put(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// DeleteOrgSecret removes a secret from an organisation.
|
||||
func (s *ActionsService) DeleteOrgSecret(ctx context.Context, org, name string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/secrets/{secretname}", pathParams("org", org, "secretname", name))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListUserVariables returns all action variables for the authenticated user.
|
||||
func (s *ActionsService) ListUserVariables(ctx context.Context) ([]types.ActionVariable, error) {
|
||||
return ListAll[types.ActionVariable](ctx, s.client, "/api/v1/user/actions/variables", nil)
|
||||
}
|
||||
|
||||
// IterUserVariables returns an iterator over all action variables for the authenticated user.
|
||||
func (s *ActionsService) IterUserVariables(ctx context.Context) iter.Seq2[types.ActionVariable, error] {
|
||||
return ListIter[types.ActionVariable](ctx, s.client, "/api/v1/user/actions/variables", nil)
|
||||
}
|
||||
|
||||
// GetUserVariable returns a single action variable for the authenticated user.
|
||||
func (s *ActionsService) GetUserVariable(ctx context.Context, name string) (*types.ActionVariable, error) {
|
||||
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
|
||||
var out types.ActionVariable
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateUserVariable creates a new action variable for the authenticated user.
|
||||
func (s *ActionsService) CreateUserVariable(ctx context.Context, name, value string) error {
|
||||
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
|
||||
body := types.CreateVariableOption{Value: value}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// UpdateUserVariable updates an existing action variable for the authenticated user.
|
||||
func (s *ActionsService) UpdateUserVariable(ctx context.Context, name string, opts *types.UpdateVariableOption) error {
|
||||
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
|
||||
return s.client.Put(ctx, path, opts, nil)
|
||||
}
|
||||
|
||||
// DeleteUserVariable removes an action variable for the authenticated user.
|
||||
func (s *ActionsService) DeleteUserVariable(ctx context.Context, name string) error {
|
||||
path := ResolvePath("/api/v1/user/actions/variables/{variablename}", pathParams("variablename", name))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// CreateUserSecret creates or updates a secret for the authenticated user.
|
||||
func (s *ActionsService) CreateUserSecret(ctx context.Context, name, data string) error {
|
||||
path := ResolvePath("/api/v1/user/actions/secrets/{secretname}", pathParams("secretname", name))
|
||||
body := map[string]string{"data": data}
|
||||
return s.client.Put(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// DeleteUserSecret removes a secret for the authenticated user.
|
||||
func (s *ActionsService) DeleteUserSecret(ctx context.Context, name string) error {
|
||||
path := ResolvePath("/api/v1/user/actions/secrets/{secretname}", pathParams("secretname", name))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// DispatchWorkflow triggers a workflow run.
|
||||
func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/workflows/{workflowname}/dispatches", pathParams("owner", owner, "repo", repo, "workflowname", workflow))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/dispatches", owner, repo, workflow)
|
||||
return s.client.Post(ctx, path, opts, nil)
|
||||
}
|
||||
|
||||
// ListRepoTasks returns a single page of action tasks for a repository.
|
||||
func (s *ActionsService) ListRepoTasks(ctx context.Context, owner, repo string, opts ListOptions) (*types.ActionTaskResponse, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/tasks", pathParams("owner", owner, "repo", repo))
|
||||
|
||||
if opts.Page > 0 || opts.Limit > 0 {
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, core.E("ActionsService.ListRepoTasks", "forge: parse path", err)
|
||||
}
|
||||
q := u.Query()
|
||||
if opts.Page > 0 {
|
||||
q.Set("page", strconv.Itoa(opts.Page))
|
||||
}
|
||||
if opts.Limit > 0 {
|
||||
q.Set("limit", strconv.Itoa(opts.Limit))
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
path = u.String()
|
||||
}
|
||||
|
||||
var out types.ActionTaskResponse
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// IterRepoTasks returns an iterator over all action tasks for a repository.
|
||||
func (s *ActionsService) IterRepoTasks(ctx context.Context, owner, repo string) iter.Seq2[types.ActionTask, error] {
|
||||
return func(yield func(types.ActionTask, error) bool) {
|
||||
const limit = 50
|
||||
var seen int64
|
||||
for page := 1; ; page++ {
|
||||
resp, err := s.ListRepoTasks(ctx, owner, repo, ListOptions{Page: page, Limit: limit})
|
||||
if err != nil {
|
||||
yield(*new(types.ActionTask), err)
|
||||
return
|
||||
}
|
||||
for _, item := range resp.Entries {
|
||||
if !yield(*item, nil) {
|
||||
return
|
||||
}
|
||||
seen++
|
||||
}
|
||||
if resp.TotalCount > 0 {
|
||||
if seen >= resp.TotalCount {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
if len(resp.Entries) < limit {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
316
actions_test.go
316
actions_test.go
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestActionsService_ListRepoSecrets_Good(t *testing.T) {
|
||||
func TestActionsService_Good_ListRepoSecrets(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -42,7 +42,7 @@ func TestActionsService_ListRepoSecrets_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_CreateRepoSecret_Good(t *testing.T) {
|
||||
func TestActionsService_Good_CreateRepoSecret(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
|
|
@ -68,7 +68,7 @@ func TestActionsService_CreateRepoSecret_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_DeleteRepoSecret_Good(t *testing.T) {
|
||||
func TestActionsService_Good_DeleteRepoSecret(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -87,7 +87,7 @@ func TestActionsService_DeleteRepoSecret_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_ListRepoVariables_Good(t *testing.T) {
|
||||
func TestActionsService_Good_ListRepoVariables(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -115,7 +115,7 @@ func TestActionsService_ListRepoVariables_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_CreateRepoVariable_Good(t *testing.T) {
|
||||
func TestActionsService_Good_CreateRepoVariable(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -141,39 +141,7 @@ func TestActionsService_CreateRepoVariable_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_UpdateRepoVariable_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/actions/variables/CI_ENV" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.UpdateVariableOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Name != "CI_ENV_NEW" {
|
||||
t.Errorf("got name=%q, want %q", body.Name, "CI_ENV_NEW")
|
||||
}
|
||||
if body.Value != "production" {
|
||||
t.Errorf("got value=%q, want %q", body.Value, "production")
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
err := f.Actions.UpdateRepoVariable(context.Background(), "core", "go-forge", "CI_ENV", &types.UpdateVariableOption{
|
||||
Name: "CI_ENV_NEW",
|
||||
Value: "production",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_DeleteRepoVariable_Good(t *testing.T) {
|
||||
func TestActionsService_Good_DeleteRepoVariable(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -192,7 +160,7 @@ func TestActionsService_DeleteRepoVariable_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_ListOrgSecrets_Good(t *testing.T) {
|
||||
func TestActionsService_Good_ListOrgSecrets(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -220,7 +188,7 @@ func TestActionsService_ListOrgSecrets_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_ListOrgVariables_Good(t *testing.T) {
|
||||
func TestActionsService_Good_ListOrgVariables(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -248,188 +216,7 @@ func TestActionsService_ListOrgVariables_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_GetOrgVariable_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/lethean/actions/variables/ORG_VAR" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.ActionVariable{Name: "ORG_VAR", Data: "org-value"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
variable, err := f.Actions.GetOrgVariable(context.Background(), "lethean", "ORG_VAR")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if variable.Name != "ORG_VAR" || variable.Data != "org-value" {
|
||||
t.Fatalf("unexpected variable: %#v", variable)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_CreateUserVariable_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/actions/variables/CI_ENV" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.CreateVariableOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Value != "production" {
|
||||
t.Errorf("got value=%q, want %q", body.Value, "production")
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Actions.CreateUserVariable(context.Background(), "CI_ENV", "production"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_ListUserVariables_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/actions/variables" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.ActionVariable{{Name: "CI_ENV", Data: "production"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
vars, err := f.Actions.ListUserVariables(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(vars) != 1 || vars[0].Name != "CI_ENV" {
|
||||
t.Fatalf("unexpected variables: %#v", vars)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_GetUserVariable_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/actions/variables/CI_ENV" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.ActionVariable{Name: "CI_ENV", Data: "production"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
variable, err := f.Actions.GetUserVariable(context.Background(), "CI_ENV")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if variable.Name != "CI_ENV" || variable.Data != "production" {
|
||||
t.Fatalf("unexpected variable: %#v", variable)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_UpdateUserVariable_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/actions/variables/CI_ENV" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.UpdateVariableOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Name != "CI_ENV_NEW" || body.Value != "staging" {
|
||||
t.Fatalf("unexpected body: %#v", body)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Actions.UpdateUserVariable(context.Background(), "CI_ENV", &types.UpdateVariableOption{
|
||||
Name: "CI_ENV_NEW",
|
||||
Value: "staging",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_DeleteUserVariable_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/actions/variables/OLD_VAR" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Actions.DeleteUserVariable(context.Background(), "OLD_VAR"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_CreateUserSecret_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/actions/secrets/DEPLOY_KEY" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body map[string]string
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body["data"] != "secret-value" {
|
||||
t.Errorf("got data=%q, want %q", body["data"], "secret-value")
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Actions.CreateUserSecret(context.Background(), "DEPLOY_KEY", "secret-value"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_DeleteUserSecret_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/user/actions/secrets/OLD_KEY" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Actions.DeleteUserSecret(context.Background(), "OLD_KEY"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_DispatchWorkflow_Good(t *testing.T) {
|
||||
func TestActionsService_Good_DispatchWorkflow(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -457,88 +244,7 @@ func TestActionsService_DispatchWorkflow_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestActionsService_ListRepoTasks_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/actions/tasks" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("page"); got != "2" {
|
||||
t.Errorf("got page=%q, want %q", got, "2")
|
||||
}
|
||||
if got := r.URL.Query().Get("limit"); got != "25" {
|
||||
t.Errorf("got limit=%q, want %q", got, "25")
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.ActionTaskResponse{
|
||||
Entries: []*types.ActionTask{
|
||||
{ID: 101, Name: "build"},
|
||||
{ID: 102, Name: "test"},
|
||||
},
|
||||
TotalCount: 2,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
resp, err := f.Actions.ListRepoTasks(context.Background(), "core", "go-forge", ListOptions{Page: 2, Limit: 25})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if resp.TotalCount != 2 {
|
||||
t.Fatalf("got total_count=%d, want 2", resp.TotalCount)
|
||||
}
|
||||
if len(resp.Entries) != 2 {
|
||||
t.Fatalf("got %d tasks, want 2", len(resp.Entries))
|
||||
}
|
||||
if resp.Entries[0].ID != 101 || resp.Entries[1].Name != "test" {
|
||||
t.Fatalf("unexpected tasks: %#v", resp.Entries)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_IterRepoTasks_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/actions/tasks" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
switch r.URL.Query().Get("page") {
|
||||
case "1":
|
||||
json.NewEncoder(w).Encode(types.ActionTaskResponse{
|
||||
Entries: []*types.ActionTask{{ID: 1, Name: "build"}},
|
||||
TotalCount: 2,
|
||||
})
|
||||
case "2":
|
||||
json.NewEncoder(w).Encode(types.ActionTaskResponse{
|
||||
Entries: []*types.ActionTask{{ID: 2, Name: "test"}},
|
||||
TotalCount: 2,
|
||||
})
|
||||
default:
|
||||
t.Fatalf("unexpected page %q", r.URL.Query().Get("page"))
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []types.ActionTask
|
||||
for task, err := range f.Actions.IterRepoTasks(context.Background(), "core", "go-forge") {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, task)
|
||||
}
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("got %d tasks, want 2", len(got))
|
||||
}
|
||||
if got[0].ID != 1 || got[1].Name != "test" {
|
||||
t.Fatalf("unexpected tasks: %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActionsService_NotFound_Bad(t *testing.T) {
|
||||
func TestActionsService_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// ActivityPubService handles ActivityPub actor and inbox endpoints.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.ActivityPub.GetInstanceActor(ctx)
|
||||
type ActivityPubService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
func newActivityPubService(c *Client) *ActivityPubService {
|
||||
return &ActivityPubService{client: c}
|
||||
}
|
||||
|
||||
// GetInstanceActor returns the instance's ActivityPub actor.
|
||||
func (s *ActivityPubService) GetInstanceActor(ctx context.Context) (*types.ActivityPub, error) {
|
||||
var out types.ActivityPub
|
||||
if err := s.client.Get(ctx, "/activitypub/actor", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// SendInstanceActorInbox sends an ActivityPub object to the instance inbox.
|
||||
func (s *ActivityPubService) SendInstanceActorInbox(ctx context.Context, body *types.ForgeLike) error {
|
||||
return s.client.Post(ctx, "/activitypub/actor/inbox", body, nil)
|
||||
}
|
||||
|
||||
// GetRepositoryActor returns the ActivityPub actor for a repository.
|
||||
func (s *ActivityPubService) GetRepositoryActor(ctx context.Context, repositoryID int64) (*types.ActivityPub, error) {
|
||||
path := ResolvePath("/activitypub/repository-id/{repository-id}", Params{"repository-id": int64String(repositoryID)})
|
||||
var out types.ActivityPub
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// SendRepositoryInbox sends an ActivityPub object to a repository inbox.
|
||||
func (s *ActivityPubService) SendRepositoryInbox(ctx context.Context, repositoryID int64, body *types.ForgeLike) error {
|
||||
path := ResolvePath("/activitypub/repository-id/{repository-id}/inbox", Params{"repository-id": int64String(repositoryID)})
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// GetPersonActor returns the Person actor for a user.
|
||||
func (s *ActivityPubService) GetPersonActor(ctx context.Context, userID int64) (*types.ActivityPub, error) {
|
||||
path := ResolvePath("/activitypub/user-id/{user-id}", Params{"user-id": int64String(userID)})
|
||||
var out types.ActivityPub
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// SendPersonInbox sends an ActivityPub object to a user's inbox.
|
||||
func (s *ActivityPubService) SendPersonInbox(ctx context.Context, userID int64, body *types.ForgeLike) error {
|
||||
path := ResolvePath("/activitypub/user-id/{user-id}/inbox", Params{"user-id": int64String(userID)})
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestActivityPubService_GetInstanceActor_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/activitypub/actor" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.ActivityPub{Context: "https://www.w3.org/ns/activitystreams"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
actor, err := f.ActivityPub.GetInstanceActor(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if actor.Context != "https://www.w3.org/ns/activitystreams" {
|
||||
t.Fatalf("got context=%q", actor.Context)
|
||||
}
|
||||
}
|
||||
|
||||
func TestActivityPubService_SendRepositoryInbox_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/activitypub/repository-id/42/inbox" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var body types.ForgeLike
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.ActivityPub.SendRepositoryInbox(context.Background(), 42, &types.ForgeLike{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
439
admin.go
439
admin.go
|
|
@ -3,100 +3,17 @@ package forge
|
|||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// AdminService handles site administration operations.
|
||||
// Unlike other services, AdminService does not embed Resource[T,C,U]
|
||||
// because admin endpoints are heterogeneous.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Admin.ListUsers(ctx)
|
||||
type AdminService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// AdminActionsRunListOptions controls filtering for admin Actions run listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.AdminActionsRunListOptions{Event: "push", Status: "success"}
|
||||
type AdminActionsRunListOptions struct {
|
||||
Event string
|
||||
Branch string
|
||||
Status string
|
||||
Actor string
|
||||
HeadSHA string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the admin Actions run filters.
|
||||
func (o AdminActionsRunListOptions) String() string {
|
||||
return optionString("forge.AdminActionsRunListOptions",
|
||||
"event", o.Event,
|
||||
"branch", o.Branch,
|
||||
"status", o.Status,
|
||||
"actor", o.Actor,
|
||||
"head_sha", o.HeadSHA,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the admin Actions run filters.
|
||||
func (o AdminActionsRunListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o AdminActionsRunListOptions) queryParams() map[string]string {
|
||||
query := make(map[string]string, 5)
|
||||
if o.Event != "" {
|
||||
query["event"] = o.Event
|
||||
}
|
||||
if o.Branch != "" {
|
||||
query["branch"] = o.Branch
|
||||
}
|
||||
if o.Status != "" {
|
||||
query["status"] = o.Status
|
||||
}
|
||||
if o.Actor != "" {
|
||||
query["actor"] = o.Actor
|
||||
}
|
||||
if o.HeadSHA != "" {
|
||||
query["head_sha"] = o.HeadSHA
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// AdminUnadoptedListOptions controls filtering for unadopted repository listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.AdminUnadoptedListOptions{Pattern: "core/*"}
|
||||
type AdminUnadoptedListOptions struct {
|
||||
Pattern string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the unadopted repository filters.
|
||||
func (o AdminUnadoptedListOptions) String() string {
|
||||
return optionString("forge.AdminUnadoptedListOptions", "pattern", o.Pattern)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the unadopted repository filters.
|
||||
func (o AdminUnadoptedListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o AdminUnadoptedListOptions) queryParams() map[string]string {
|
||||
if o.Pattern == "" {
|
||||
return nil
|
||||
}
|
||||
return map[string]string{"pattern": o.Pattern}
|
||||
}
|
||||
|
||||
func newAdminService(c *Client) *AdminService {
|
||||
return &AdminService{client: c}
|
||||
}
|
||||
|
|
@ -120,58 +37,6 @@ func (s *AdminService) CreateUser(ctx context.Context, opts *types.CreateUserOpt
|
|||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateUserKey adds a public key on behalf of a user.
|
||||
func (s *AdminService) CreateUserKey(ctx context.Context, username string, opts *types.CreateKeyOption) (*types.PublicKey, error) {
|
||||
path := ResolvePath("/api/v1/admin/users/{username}/keys", Params{"username": username})
|
||||
var out types.PublicKey
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteUserKey deletes a user's public key.
|
||||
func (s *AdminService) DeleteUserKey(ctx context.Context, username string, id int64) error {
|
||||
path := ResolvePath("/api/v1/admin/users/{username}/keys/{id}", Params{"username": username, "id": int64String(id)})
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// CreateUserOrg creates an organisation on behalf of a user.
|
||||
func (s *AdminService) CreateUserOrg(ctx context.Context, username string, opts *types.CreateOrgOption) (*types.Organization, error) {
|
||||
path := ResolvePath("/api/v1/admin/users/{username}/orgs", Params{"username": username})
|
||||
var out types.Organization
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetUserQuota returns a user's quota information.
|
||||
func (s *AdminService) GetUserQuota(ctx context.Context, username string) (*types.QuotaInfo, error) {
|
||||
path := ResolvePath("/api/v1/admin/users/{username}/quota", Params{"username": username})
|
||||
var out types.QuotaInfo
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// SetUserQuotaGroups sets the user's quota groups to a given list.
|
||||
func (s *AdminService) SetUserQuotaGroups(ctx context.Context, username string, opts *types.SetUserQuotaGroupsOptions) error {
|
||||
path := ResolvePath("/api/v1/admin/users/{username}/quota/groups", Params{"username": username})
|
||||
return s.client.Post(ctx, path, opts, nil)
|
||||
}
|
||||
|
||||
// CreateUserRepo creates a repository on behalf of a user.
|
||||
func (s *AdminService) CreateUserRepo(ctx context.Context, username string, opts *types.CreateRepoOption) (*types.Repository, error) {
|
||||
path := ResolvePath("/api/v1/admin/users/{username}/repos", Params{"username": username})
|
||||
var out types.Repository
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// EditUser edits an existing user (admin only).
|
||||
func (s *AdminService) EditUser(ctx context.Context, username string, opts map[string]any) error {
|
||||
path := ResolvePath("/api/v1/admin/users/{username}", Params{"username": username})
|
||||
|
|
@ -200,219 +65,6 @@ func (s *AdminService) IterOrgs(ctx context.Context) iter.Seq2[types.Organizatio
|
|||
return ListIter[types.Organization](ctx, s.client, "/api/v1/admin/orgs", nil)
|
||||
}
|
||||
|
||||
// ListEmails returns all email addresses (admin only).
|
||||
func (s *AdminService) ListEmails(ctx context.Context) ([]types.Email, error) {
|
||||
return ListAll[types.Email](ctx, s.client, "/api/v1/admin/emails", nil)
|
||||
}
|
||||
|
||||
// IterEmails returns an iterator over all email addresses (admin only).
|
||||
func (s *AdminService) IterEmails(ctx context.Context) iter.Seq2[types.Email, error] {
|
||||
return ListIter[types.Email](ctx, s.client, "/api/v1/admin/emails", nil)
|
||||
}
|
||||
|
||||
// ListHooks returns all global hooks (admin only).
|
||||
func (s *AdminService) ListHooks(ctx context.Context) ([]types.Hook, error) {
|
||||
return ListAll[types.Hook](ctx, s.client, "/api/v1/admin/hooks", nil)
|
||||
}
|
||||
|
||||
// IterHooks returns an iterator over all global hooks (admin only).
|
||||
func (s *AdminService) IterHooks(ctx context.Context) iter.Seq2[types.Hook, error] {
|
||||
return ListIter[types.Hook](ctx, s.client, "/api/v1/admin/hooks", nil)
|
||||
}
|
||||
|
||||
// GetHook returns a single global hook by ID (admin only).
|
||||
func (s *AdminService) GetHook(ctx context.Context, id int64) (*types.Hook, error) {
|
||||
path := ResolvePath("/api/v1/admin/hooks/{id}", Params{"id": int64String(id)})
|
||||
var out types.Hook
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateHook creates a new global hook (admin only).
|
||||
func (s *AdminService) CreateHook(ctx context.Context, opts *types.CreateHookOption) (*types.Hook, error) {
|
||||
var out types.Hook
|
||||
if err := s.client.Post(ctx, "/api/v1/admin/hooks", opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// EditHook updates an existing global hook (admin only).
|
||||
func (s *AdminService) EditHook(ctx context.Context, id int64, opts *types.EditHookOption) (*types.Hook, error) {
|
||||
path := ResolvePath("/api/v1/admin/hooks/{id}", Params{"id": int64String(id)})
|
||||
var out types.Hook
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteHook deletes a global hook (admin only).
|
||||
func (s *AdminService) DeleteHook(ctx context.Context, id int64) error {
|
||||
path := ResolvePath("/api/v1/admin/hooks/{id}", Params{"id": int64String(id)})
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListQuotaGroups returns all available quota groups.
|
||||
func (s *AdminService) ListQuotaGroups(ctx context.Context) ([]types.QuotaGroup, error) {
|
||||
return ListAll[types.QuotaGroup](ctx, s.client, "/api/v1/admin/quota/groups", nil)
|
||||
}
|
||||
|
||||
// IterQuotaGroups returns an iterator over all available quota groups.
|
||||
func (s *AdminService) IterQuotaGroups(ctx context.Context) iter.Seq2[types.QuotaGroup, error] {
|
||||
return func(yield func(types.QuotaGroup, error) bool) {
|
||||
groups, err := s.ListQuotaGroups(ctx)
|
||||
if err != nil {
|
||||
yield(*new(types.QuotaGroup), err)
|
||||
return
|
||||
}
|
||||
for _, group := range groups {
|
||||
if !yield(group, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateQuotaGroup creates a new quota group.
|
||||
func (s *AdminService) CreateQuotaGroup(ctx context.Context, opts *types.CreateQuotaGroupOptions) (*types.QuotaGroup, error) {
|
||||
var out types.QuotaGroup
|
||||
if err := s.client.Post(ctx, "/api/v1/admin/quota/groups", opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetQuotaGroup returns information about a quota group.
|
||||
func (s *AdminService) GetQuotaGroup(ctx context.Context, quotagroup string) (*types.QuotaGroup, error) {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}", Params{"quotagroup": quotagroup})
|
||||
var out types.QuotaGroup
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteQuotaGroup deletes a quota group.
|
||||
func (s *AdminService) DeleteQuotaGroup(ctx context.Context, quotagroup string) error {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}", Params{"quotagroup": quotagroup})
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// AddQuotaGroupRule adds a quota rule to a quota group.
|
||||
func (s *AdminService) AddQuotaGroupRule(ctx context.Context, quotagroup, quotarule string) error {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/rules/{quotarule}", Params{"quotagroup": quotagroup, "quotarule": quotarule})
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// RemoveQuotaGroupRule removes a quota rule from a quota group.
|
||||
func (s *AdminService) RemoveQuotaGroupRule(ctx context.Context, quotagroup, quotarule string) error {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/rules/{quotarule}", Params{"quotagroup": quotagroup, "quotarule": quotarule})
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListQuotaGroupUsers returns all users in a quota group.
|
||||
func (s *AdminService) ListQuotaGroupUsers(ctx context.Context, quotagroup string) ([]types.User, error) {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/users", Params{"quotagroup": quotagroup})
|
||||
return ListAll[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterQuotaGroupUsers returns an iterator over all users in a quota group.
|
||||
func (s *AdminService) IterQuotaGroupUsers(ctx context.Context, quotagroup string) iter.Seq2[types.User, error] {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/users", Params{"quotagroup": quotagroup})
|
||||
return ListIter[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// AddQuotaGroupUser adds a user to a quota group.
|
||||
func (s *AdminService) AddQuotaGroupUser(ctx context.Context, quotagroup, username string) error {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/users/{username}", Params{"quotagroup": quotagroup, "username": username})
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// RemoveQuotaGroupUser removes a user from a quota group.
|
||||
func (s *AdminService) RemoveQuotaGroupUser(ctx context.Context, quotagroup, username string) error {
|
||||
path := ResolvePath("/api/v1/admin/quota/groups/{quotagroup}/users/{username}", Params{"quotagroup": quotagroup, "username": username})
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListQuotaRules returns all available quota rules.
|
||||
func (s *AdminService) ListQuotaRules(ctx context.Context) ([]types.QuotaRuleInfo, error) {
|
||||
return ListAll[types.QuotaRuleInfo](ctx, s.client, "/api/v1/admin/quota/rules", nil)
|
||||
}
|
||||
|
||||
// IterQuotaRules returns an iterator over all available quota rules.
|
||||
func (s *AdminService) IterQuotaRules(ctx context.Context) iter.Seq2[types.QuotaRuleInfo, error] {
|
||||
return func(yield func(types.QuotaRuleInfo, error) bool) {
|
||||
rules, err := s.ListQuotaRules(ctx)
|
||||
if err != nil {
|
||||
yield(*new(types.QuotaRuleInfo), err)
|
||||
return
|
||||
}
|
||||
for _, rule := range rules {
|
||||
if !yield(rule, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateQuotaRule creates a new quota rule.
|
||||
func (s *AdminService) CreateQuotaRule(ctx context.Context, opts *types.CreateQuotaRuleOptions) (*types.QuotaRuleInfo, error) {
|
||||
var out types.QuotaRuleInfo
|
||||
if err := s.client.Post(ctx, "/api/v1/admin/quota/rules", opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetQuotaRule returns information about a quota rule.
|
||||
func (s *AdminService) GetQuotaRule(ctx context.Context, quotarule string) (*types.QuotaRuleInfo, error) {
|
||||
path := ResolvePath("/api/v1/admin/quota/rules/{quotarule}", Params{"quotarule": quotarule})
|
||||
var out types.QuotaRuleInfo
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// EditQuotaRule updates an existing quota rule.
|
||||
func (s *AdminService) EditQuotaRule(ctx context.Context, quotarule string, opts *types.EditQuotaRuleOptions) (*types.QuotaRuleInfo, error) {
|
||||
path := ResolvePath("/api/v1/admin/quota/rules/{quotarule}", Params{"quotarule": quotarule})
|
||||
var out types.QuotaRuleInfo
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteQuotaRule deletes a quota rule.
|
||||
func (s *AdminService) DeleteQuotaRule(ctx context.Context, quotarule string) error {
|
||||
path := ResolvePath("/api/v1/admin/quota/rules/{quotarule}", Params{"quotarule": quotarule})
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListUnadoptedRepos returns all unadopted repositories on the instance.
|
||||
func (s *AdminService) ListUnadoptedRepos(ctx context.Context, filters ...AdminUnadoptedListOptions) ([]string, error) {
|
||||
return ListAll[string](ctx, s.client, "/api/v1/admin/unadopted", adminUnadoptedQuery(filters...))
|
||||
}
|
||||
|
||||
// IterUnadoptedRepos returns an iterator over all unadopted repositories on the instance.
|
||||
func (s *AdminService) IterUnadoptedRepos(ctx context.Context, filters ...AdminUnadoptedListOptions) iter.Seq2[string, error] {
|
||||
return ListIter[string](ctx, s.client, "/api/v1/admin/unadopted", adminUnadoptedQuery(filters...))
|
||||
}
|
||||
|
||||
// SearchEmails searches all email addresses by keyword (admin only).
|
||||
func (s *AdminService) SearchEmails(ctx context.Context, q string) ([]types.Email, error) {
|
||||
return ListAll[types.Email](ctx, s.client, "/api/v1/admin/emails/search", map[string]string{"q": q})
|
||||
}
|
||||
|
||||
// IterSearchEmails returns an iterator over all email addresses matching a keyword (admin only).
|
||||
func (s *AdminService) IterSearchEmails(ctx context.Context, q string) iter.Seq2[types.Email, error] {
|
||||
return ListIter[types.Email](ctx, s.client, "/api/v1/admin/emails/search", map[string]string{"q": q})
|
||||
}
|
||||
|
||||
// RunCron runs a cron task by name (admin only).
|
||||
func (s *AdminService) RunCron(ctx context.Context, task string) error {
|
||||
path := ResolvePath("/api/v1/admin/cron/{task}", Params{"task": task})
|
||||
|
|
@ -429,103 +81,12 @@ func (s *AdminService) IterCron(ctx context.Context) iter.Seq2[types.Cron, error
|
|||
return ListIter[types.Cron](ctx, s.client, "/api/v1/admin/cron", nil)
|
||||
}
|
||||
|
||||
// ListActionsRuns returns a single page of Actions workflow runs across the instance.
|
||||
func (s *AdminService) ListActionsRuns(ctx context.Context, filters AdminActionsRunListOptions, opts ListOptions) (*PagedResult[types.ActionTask], error) {
|
||||
if opts.Page < 1 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.Limit < 1 {
|
||||
opts.Limit = 50
|
||||
}
|
||||
|
||||
u, err := url.Parse("/api/v1/admin/actions/runs")
|
||||
if err != nil {
|
||||
return nil, core.E("AdminService.ListActionsRuns", "forge: parse path", err)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
for key, value := range filters.queryParams() {
|
||||
q.Set(key, value)
|
||||
}
|
||||
q.Set("page", strconv.Itoa(opts.Page))
|
||||
q.Set("limit", strconv.Itoa(opts.Limit))
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
var out types.ActionTaskResponse
|
||||
resp, err := s.client.doJSON(ctx, http.MethodGet, u.String(), nil, &out)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCount, _ := strconv.Atoi(resp.Header.Get("X-Total-Count"))
|
||||
items := make([]types.ActionTask, 0, len(out.Entries))
|
||||
for _, run := range out.Entries {
|
||||
if run != nil {
|
||||
items = append(items, *run)
|
||||
}
|
||||
}
|
||||
|
||||
return &PagedResult[types.ActionTask]{
|
||||
Items: items,
|
||||
TotalCount: totalCount,
|
||||
Page: opts.Page,
|
||||
HasMore: (totalCount > 0 && (opts.Page-1)*opts.Limit+len(items) < totalCount) ||
|
||||
(totalCount == 0 && len(items) >= opts.Limit),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IterActionsRuns returns an iterator over all Actions workflow runs across the instance.
|
||||
func (s *AdminService) IterActionsRuns(ctx context.Context, filters AdminActionsRunListOptions) iter.Seq2[types.ActionTask, error] {
|
||||
return func(yield func(types.ActionTask, error) bool) {
|
||||
page := 1
|
||||
for {
|
||||
result, err := s.ListActionsRuns(ctx, filters, ListOptions{Page: page, Limit: 50})
|
||||
if err != nil {
|
||||
yield(*new(types.ActionTask), err)
|
||||
return
|
||||
}
|
||||
for _, item := range result.Items {
|
||||
if !yield(item, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !result.HasMore {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AdoptRepo adopts an unadopted repository (admin only).
|
||||
func (s *AdminService) AdoptRepo(ctx context.Context, owner, repo string) error {
|
||||
path := ResolvePath("/api/v1/admin/unadopted/{owner}/{repo}", Params{"owner": owner, "repo": repo})
|
||||
return s.client.Post(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// DeleteUnadoptedRepo deletes an unadopted repository's files.
|
||||
func (s *AdminService) DeleteUnadoptedRepo(ctx context.Context, owner, repo string) error {
|
||||
path := ResolvePath("/api/v1/admin/unadopted/{owner}/{repo}", Params{"owner": owner, "repo": repo})
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
func adminUnadoptedQuery(filters ...AdminUnadoptedListOptions) map[string]string {
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := make(map[string]string, 1)
|
||||
for _, filter := range filters {
|
||||
if filter.Pattern != "" {
|
||||
query["pattern"] = filter.Pattern
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// GenerateRunnerToken generates an actions runner registration token.
|
||||
func (s *AdminService) GenerateRunnerToken(ctx context.Context) (string, error) {
|
||||
var out struct {
|
||||
|
|
|
|||
798
admin_test.go
798
admin_test.go
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestAdminService_ListUsers_Good(t *testing.T) {
|
||||
func TestAdminService_Good_ListUsers(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -39,7 +39,7 @@ func TestAdminService_ListUsers_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_CreateUser_Good(t *testing.T) {
|
||||
func TestAdminService_Good_CreateUser(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -78,7 +78,7 @@ func TestAdminService_CreateUser_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_DeleteUser_Good(t *testing.T) {
|
||||
func TestAdminService_Good_DeleteUser(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -96,7 +96,7 @@ func TestAdminService_DeleteUser_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_RunCron_Good(t *testing.T) {
|
||||
func TestAdminService_Good_RunCron(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -114,7 +114,7 @@ func TestAdminService_RunCron_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_EditUser_Good(t *testing.T) {
|
||||
func TestAdminService_Good_EditUser(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
|
|
@ -142,7 +142,7 @@ func TestAdminService_EditUser_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_RenameUser_Good(t *testing.T) {
|
||||
func TestAdminService_Good_RenameUser(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -167,7 +167,7 @@ func TestAdminService_RenameUser_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListOrgs_Good(t *testing.T) {
|
||||
func TestAdminService_Good_ListOrgs(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -195,633 +195,7 @@ func TestAdminService_ListOrgs_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListEmails_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/emails" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.Email{
|
||||
{Email: "alice@example.com", Primary: true},
|
||||
{Email: "bob@example.com", Verified: true},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
emails, err := f.Admin.ListEmails(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(emails) != 2 {
|
||||
t.Errorf("got %d emails, want 2", len(emails))
|
||||
}
|
||||
if emails[0].Email != "alice@example.com" || !emails[0].Primary {
|
||||
t.Errorf("got first email=%+v, want primary alice@example.com", emails[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListHooks_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/hooks" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Hook{
|
||||
{ID: 7, Type: "forgejo", URL: "https://example.com/admin-hook", Active: true},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
hooks, err := f.Admin.ListHooks(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(hooks) != 1 {
|
||||
t.Fatalf("got %d hooks, want 1", len(hooks))
|
||||
}
|
||||
if hooks[0].ID != 7 || hooks[0].URL != "https://example.com/admin-hook" {
|
||||
t.Errorf("unexpected hook: %+v", hooks[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_CreateHook_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/hooks" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.CreateHookOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Type != "forgejo" {
|
||||
t.Errorf("got type=%q, want %q", opts.Type, "forgejo")
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Hook{
|
||||
ID: 12,
|
||||
Type: opts.Type,
|
||||
Active: opts.Active,
|
||||
Events: opts.Events,
|
||||
URL: "https://example.com/admin-hook",
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
hook, err := f.Admin.CreateHook(context.Background(), &types.CreateHookOption{
|
||||
Type: "forgejo",
|
||||
Active: true,
|
||||
Events: []string{"push"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hook.ID != 12 {
|
||||
t.Errorf("got id=%d, want 12", hook.ID)
|
||||
}
|
||||
if hook.Type != "forgejo" {
|
||||
t.Errorf("got type=%q, want %q", hook.Type, "forgejo")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_GetHook_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/hooks/7" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Hook{
|
||||
ID: 7,
|
||||
Type: "forgejo",
|
||||
Active: true,
|
||||
URL: "https://example.com/admin-hook",
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
hook, err := f.Admin.GetHook(context.Background(), 7)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hook.ID != 7 {
|
||||
t.Errorf("got id=%d, want 7", hook.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_EditHook_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/hooks/7" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.EditHookOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !opts.Active {
|
||||
t.Error("expected active=true")
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Hook{
|
||||
ID: 7,
|
||||
Type: "forgejo",
|
||||
Active: opts.Active,
|
||||
URL: "https://example.com/admin-hook",
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
hook, err := f.Admin.EditHook(context.Background(), 7, &types.EditHookOption{Active: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hook.ID != 7 || !hook.Active {
|
||||
t.Errorf("unexpected hook: %+v", hook)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_DeleteHook_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/hooks/7" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Admin.DeleteHook(context.Background(), 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListQuotaGroups_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.QuotaGroup{
|
||||
{
|
||||
Name: "default",
|
||||
Rules: []*types.QuotaRuleInfo{
|
||||
{Name: "git", Limit: 200000000, Subjects: []string{"size:repos:all"}},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "premium",
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
groups, err := f.Admin.ListQuotaGroups(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(groups) != 2 {
|
||||
t.Fatalf("got %d groups, want 2", len(groups))
|
||||
}
|
||||
if groups[0].Name != "default" {
|
||||
t.Errorf("got name=%q, want %q", groups[0].Name, "default")
|
||||
}
|
||||
if len(groups[0].Rules) != 1 || groups[0].Rules[0].Name != "git" {
|
||||
t.Errorf("unexpected rules: %+v", groups[0].Rules)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_IterQuotaGroups_Good(t *testing.T) {
|
||||
var requests int
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.QuotaGroup{
|
||||
{Name: "default"},
|
||||
{Name: "premium"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []string
|
||||
for group, err := range f.Admin.IterQuotaGroups(context.Background()) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, group.Name)
|
||||
}
|
||||
if requests != 1 {
|
||||
t.Fatalf("expected 1 request, got %d", requests)
|
||||
}
|
||||
if len(got) != 2 || got[0] != "default" || got[1] != "premium" {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_CreateQuotaGroup_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.CreateQuotaGroupOptions
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Name != "newgroup" {
|
||||
t.Errorf("got name=%q, want %q", opts.Name, "newgroup")
|
||||
}
|
||||
if len(opts.Rules) != 1 || opts.Rules[0].Name != "git" {
|
||||
t.Fatalf("unexpected rules: %+v", opts.Rules)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(types.QuotaGroup{
|
||||
Name: opts.Name,
|
||||
Rules: []*types.QuotaRuleInfo{
|
||||
{
|
||||
Name: opts.Rules[0].Name,
|
||||
Limit: opts.Rules[0].Limit,
|
||||
Subjects: opts.Rules[0].Subjects,
|
||||
},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
group, err := f.Admin.CreateQuotaGroup(context.Background(), &types.CreateQuotaGroupOptions{
|
||||
Name: "newgroup",
|
||||
Rules: []*types.CreateQuotaRuleOptions{
|
||||
{
|
||||
Name: "git",
|
||||
Limit: 200000000,
|
||||
Subjects: []string{"size:repos:all"},
|
||||
},
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if group.Name != "newgroup" {
|
||||
t.Errorf("got name=%q, want %q", group.Name, "newgroup")
|
||||
}
|
||||
if len(group.Rules) != 1 || group.Rules[0].Limit != 200000000 {
|
||||
t.Errorf("unexpected rules: %+v", group.Rules)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_GetQuotaGroup_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups/default" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.QuotaGroup{
|
||||
Name: "default",
|
||||
Rules: []*types.QuotaRuleInfo{
|
||||
{Name: "git", Limit: 200000000, Subjects: []string{"size:repos:all"}},
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
group, err := f.Admin.GetQuotaGroup(context.Background(), "default")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if group.Name != "default" {
|
||||
t.Errorf("got name=%q, want %q", group.Name, "default")
|
||||
}
|
||||
if len(group.Rules) != 1 || group.Rules[0].Name != "git" {
|
||||
t.Fatalf("unexpected rules: %+v", group.Rules)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_DeleteQuotaGroup_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups/default" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Admin.DeleteQuotaGroup(context.Background(), "default"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListQuotaGroupUsers_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups/default/users" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.User{
|
||||
{ID: 1, UserName: "alice"},
|
||||
{ID: 2, UserName: "bob"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
users, err := f.Admin.ListQuotaGroupUsers(context.Background(), "default")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(users) != 2 {
|
||||
t.Fatalf("got %d users, want 2", len(users))
|
||||
}
|
||||
if users[0].UserName != "alice" {
|
||||
t.Errorf("got username=%q, want %q", users[0].UserName, "alice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_AddQuotaGroupUser_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups/default/users/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Admin.AddQuotaGroupUser(context.Background(), "default", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_RemoveQuotaGroupUser_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/groups/default/users/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Admin.RemoveQuotaGroupUser(context.Background(), "default", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListQuotaRules_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/rules" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.QuotaRuleInfo{
|
||||
{Name: "git", Limit: 200000000, Subjects: []string{"size:repos:all"}},
|
||||
{Name: "artifacts", Limit: 50000000, Subjects: []string{"size:assets:artifacts"}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
rules, err := f.Admin.ListQuotaRules(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(rules) != 2 {
|
||||
t.Fatalf("got %d rules, want 2", len(rules))
|
||||
}
|
||||
if rules[0].Name != "git" {
|
||||
t.Errorf("got name=%q, want %q", rules[0].Name, "git")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_IterQuotaRules_Good(t *testing.T) {
|
||||
var requests int
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/rules" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.QuotaRuleInfo{
|
||||
{Name: "git"},
|
||||
{Name: "artifacts"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []string
|
||||
for rule, err := range f.Admin.IterQuotaRules(context.Background()) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, rule.Name)
|
||||
}
|
||||
if requests != 1 {
|
||||
t.Fatalf("expected 1 request, got %d", requests)
|
||||
}
|
||||
if len(got) != 2 || got[0] != "git" || got[1] != "artifacts" {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_CreateQuotaRule_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/rules" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.CreateQuotaRuleOptions
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Name != "git" || opts.Limit != 200000000 {
|
||||
t.Fatalf("unexpected options: %+v", opts)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(types.QuotaRuleInfo{
|
||||
Name: opts.Name,
|
||||
Limit: opts.Limit,
|
||||
Subjects: opts.Subjects,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
rule, err := f.Admin.CreateQuotaRule(context.Background(), &types.CreateQuotaRuleOptions{
|
||||
Name: "git",
|
||||
Limit: 200000000,
|
||||
Subjects: []string{"size:repos:all"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rule.Name != "git" || rule.Limit != 200000000 {
|
||||
t.Errorf("unexpected rule: %+v", rule)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_GetQuotaRule_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/rules/git" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.QuotaRuleInfo{
|
||||
Name: "git",
|
||||
Limit: 200000000,
|
||||
Subjects: []string{"size:repos:all"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
rule, err := f.Admin.GetQuotaRule(context.Background(), "git")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rule.Name != "git" {
|
||||
t.Errorf("got name=%q, want %q", rule.Name, "git")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_EditQuotaRule_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/rules/git" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.EditQuotaRuleOptions
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Limit != 500000000 {
|
||||
t.Fatalf("unexpected options: %+v", opts)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.QuotaRuleInfo{
|
||||
Name: "git",
|
||||
Limit: opts.Limit,
|
||||
Subjects: opts.Subjects,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
rule, err := f.Admin.EditQuotaRule(context.Background(), "git", &types.EditQuotaRuleOptions{
|
||||
Limit: 500000000,
|
||||
Subjects: []string{"size:repos:all", "size:assets:packages"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rule.Limit != 500000000 {
|
||||
t.Errorf("got limit=%d, want 500000000", rule.Limit)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_DeleteQuotaRule_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/quota/rules/git" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Admin.DeleteQuotaRule(context.Background(), "git"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_SearchEmails_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/emails/search" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("q"); got != "alice" {
|
||||
t.Errorf("got q=%q, want %q", got, "alice")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Email{
|
||||
{Email: "alice@example.com", Primary: true},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
emails, err := f.Admin.SearchEmails(context.Background(), "alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(emails) != 1 {
|
||||
t.Errorf("got %d emails, want 1", len(emails))
|
||||
}
|
||||
if emails[0].Email != "alice@example.com" {
|
||||
t.Errorf("got email=%q, want %q", emails[0].Email, "alice@example.com")
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListCron_Good(t *testing.T) {
|
||||
func TestAdminService_Good_ListCron(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -849,7 +223,7 @@ func TestAdminService_ListCron_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_AdoptRepo_Good(t *testing.T) {
|
||||
func TestAdminService_Good_AdoptRepo(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -867,153 +241,7 @@ func TestAdminService_AdoptRepo_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListUnadoptedRepos_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/unadopted" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if got := r.URL.Query().Get("pattern"); got != "core/*" {
|
||||
t.Errorf("got pattern=%q, want %q", got, "core/*")
|
||||
}
|
||||
if got := r.URL.Query().Get("page"); got != "1" {
|
||||
t.Errorf("got page=%q, want %q", got, "1")
|
||||
}
|
||||
if got := r.URL.Query().Get("limit"); got != "50" {
|
||||
t.Errorf("got limit=%q, want %q", got, "50")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]string{"core/myrepo"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
repos, err := f.Admin.ListUnadoptedRepos(context.Background(), AdminUnadoptedListOptions{Pattern: "core/*"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(repos) != 1 || repos[0] != "core/myrepo" {
|
||||
t.Fatalf("unexpected result: %#v", repos)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_DeleteUnadoptedRepo_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/unadopted/alice/myrepo" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Admin.DeleteUnadoptedRepo(context.Background(), "alice", "myrepo"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_ListActionsRuns_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/admin/actions/runs" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("status"); got != "in_progress" {
|
||||
t.Errorf("got status=%q, want %q", got, "in_progress")
|
||||
}
|
||||
if got := r.URL.Query().Get("branch"); got != "main" {
|
||||
t.Errorf("got branch=%q, want %q", got, "main")
|
||||
}
|
||||
if got := r.URL.Query().Get("actor"); got != "alice" {
|
||||
t.Errorf("got actor=%q, want %q", got, "alice")
|
||||
}
|
||||
if got := r.URL.Query().Get("page"); got != "2" {
|
||||
t.Errorf("got page=%q, want %q", got, "2")
|
||||
}
|
||||
if got := r.URL.Query().Get("limit"); got != "25" {
|
||||
t.Errorf("got limit=%q, want %q", got, "25")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "3")
|
||||
json.NewEncoder(w).Encode(types.ActionTaskResponse{
|
||||
Entries: []*types.ActionTask{
|
||||
{ID: 101, Name: "build", Status: "in_progress", Event: "push"},
|
||||
{ID: 102, Name: "test", Status: "queued", Event: "push"},
|
||||
},
|
||||
TotalCount: 3,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
result, err := f.Admin.ListActionsRuns(context.Background(), AdminActionsRunListOptions{
|
||||
Status: "in_progress",
|
||||
Branch: "main",
|
||||
Actor: "alice",
|
||||
}, ListOptions{Page: 2, Limit: 25})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if result.TotalCount != 3 {
|
||||
t.Fatalf("got total count=%d, want 3", result.TotalCount)
|
||||
}
|
||||
if len(result.Items) != 2 {
|
||||
t.Fatalf("got %d runs, want 2", len(result.Items))
|
||||
}
|
||||
if result.Items[0].ID != 101 || result.Items[0].Name != "build" {
|
||||
t.Errorf("unexpected first run: %+v", result.Items[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_IterActionsRuns_Good(t *testing.T) {
|
||||
calls := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
calls++
|
||||
if r.URL.Path != "/api/v1/admin/actions/runs" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
switch calls {
|
||||
case 1:
|
||||
json.NewEncoder(w).Encode(types.ActionTaskResponse{
|
||||
Entries: []*types.ActionTask{
|
||||
{ID: 201, Name: "build"},
|
||||
},
|
||||
TotalCount: 2,
|
||||
})
|
||||
default:
|
||||
json.NewEncoder(w).Encode(types.ActionTaskResponse{
|
||||
Entries: []*types.ActionTask{
|
||||
{ID: 202, Name: "test"},
|
||||
},
|
||||
TotalCount: 2,
|
||||
})
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var ids []int64
|
||||
for run, err := range f.Admin.IterActionsRuns(context.Background(), AdminActionsRunListOptions{}) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ids = append(ids, run.ID)
|
||||
}
|
||||
if len(ids) != 2 || ids[0] != 201 || ids[1] != 202 {
|
||||
t.Fatalf("unexpected run ids: %v", ids)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdminService_GenerateRunnerToken_Good(t *testing.T) {
|
||||
func TestAdminService_Good_GenerateRunnerToken(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -1035,7 +263,7 @@ func TestAdminService_GenerateRunnerToken_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_DeleteUser_NotFound_Bad(t *testing.T) {
|
||||
func TestAdminService_Bad_DeleteUser_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "user not found"})
|
||||
|
|
@ -1049,7 +277,7 @@ func TestAdminService_DeleteUser_NotFound_Bad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAdminService_CreateUser_Forbidden_Bad(t *testing.T) {
|
||||
func TestAdminService_Bad_CreateUser_Forbidden(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "only admins can create users"})
|
||||
|
|
|
|||
|
|
@ -1,266 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestParams_String_Good(t *testing.T) {
|
||||
params := Params{"repo": "go-forge", "owner": "core"}
|
||||
want := `forge.Params{owner="core", repo="go-forge"}`
|
||||
if got := params.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(params); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", params); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParams_String_NilSafe(t *testing.T) {
|
||||
var params Params
|
||||
want := "forge.Params{<nil>}"
|
||||
if got := params.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(params); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", params); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListOptions_String_Good(t *testing.T) {
|
||||
opts := ListOptions{Page: 2, Limit: 25}
|
||||
want := "forge.ListOptions{page=2, limit=25}"
|
||||
if got := opts.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(opts); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", opts); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRateLimit_String_Good(t *testing.T) {
|
||||
rl := RateLimit{Limit: 80, Remaining: 79, Reset: 1700000003}
|
||||
want := "forge.RateLimit{limit=80, remaining=79, reset=1700000003}"
|
||||
if got := rl.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(rl); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", rl); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPagedResult_String_Good(t *testing.T) {
|
||||
page := PagedResult[int]{
|
||||
Items: []int{1, 2, 3},
|
||||
TotalCount: 10,
|
||||
Page: 2,
|
||||
HasMore: true,
|
||||
}
|
||||
want := "forge.PagedResult{items=3, totalCount=10, page=2, hasMore=true}"
|
||||
if got := page.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(page); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", page); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOption_Stringers_Good(t *testing.T) {
|
||||
when := time.Date(2026, time.April, 2, 8, 3, 4, 0, time.UTC)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
got fmt.Stringer
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "AdminActionsRunListOptions",
|
||||
got: AdminActionsRunListOptions{Event: "push", Status: "success"},
|
||||
want: `forge.AdminActionsRunListOptions{event="push", status="success"}`,
|
||||
},
|
||||
{
|
||||
name: "AttachmentUploadOptions",
|
||||
got: AttachmentUploadOptions{Name: "screenshot.png", UpdatedAt: &when},
|
||||
want: `forge.AttachmentUploadOptions{name="screenshot.png", updated_at="2026-04-02T08:03:04Z"}`,
|
||||
},
|
||||
{
|
||||
name: "NotificationListOptions",
|
||||
got: NotificationListOptions{All: true, StatusTypes: []string{"unread"}, SubjectTypes: []string{"issue"}},
|
||||
want: `forge.NotificationListOptions{all=true, status_types=[]string{"unread"}, subject_types=[]string{"issue"}}`,
|
||||
},
|
||||
{
|
||||
name: "SearchIssuesOptions",
|
||||
got: SearchIssuesOptions{State: "open", PriorityRepoID: 99, Assigned: true, Query: "build"},
|
||||
want: `forge.SearchIssuesOptions{state="open", q="build", priority_repo_id=99, assigned=true}`,
|
||||
},
|
||||
{
|
||||
name: "IssueListOptions",
|
||||
got: IssueListOptions{State: "open", Labels: "bug", Query: "panic", CreatedBy: "alice"},
|
||||
want: `forge.IssueListOptions{state="open", labels="bug", q="panic", created_by="alice"}`,
|
||||
},
|
||||
{
|
||||
name: "PullListOptions",
|
||||
got: PullListOptions{State: "open", Sort: "priority", Milestone: 7, Labels: []int64{1, 2}, Poster: "alice"},
|
||||
want: `forge.PullListOptions{state="open", sort="priority", milestone=7, labels=[]int64{1, 2}, poster="alice"}`,
|
||||
},
|
||||
{
|
||||
name: "ReleaseListOptions",
|
||||
got: ReleaseListOptions{Draft: true, PreRelease: true, Query: "1.0"},
|
||||
want: `forge.ReleaseListOptions{draft=true, pre-release=true, q="1.0"}`,
|
||||
},
|
||||
{
|
||||
name: "CommitListOptions",
|
||||
got: func() CommitListOptions {
|
||||
stat := false
|
||||
verification := false
|
||||
files := false
|
||||
return CommitListOptions{
|
||||
Sha: "main",
|
||||
Path: "docs",
|
||||
Stat: &stat,
|
||||
Verification: &verification,
|
||||
Files: &files,
|
||||
Not: "deadbeef",
|
||||
}
|
||||
}(),
|
||||
want: `forge.CommitListOptions{sha="main", path="docs", stat=false, verification=false, files=false, not="deadbeef"}`,
|
||||
},
|
||||
{
|
||||
name: "ReleaseAttachmentUploadOptions",
|
||||
got: ReleaseAttachmentUploadOptions{Name: "release.zip"},
|
||||
want: `forge.ReleaseAttachmentUploadOptions{name="release.zip"}`,
|
||||
},
|
||||
{
|
||||
name: "UserSearchOptions",
|
||||
got: UserSearchOptions{UID: 1001},
|
||||
want: `forge.UserSearchOptions{uid=1001}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := tc.got.String(); got != tc.want {
|
||||
t.Fatalf("got String()=%q, want %q", got, tc.want)
|
||||
}
|
||||
if got := fmt.Sprint(tc.got); got != tc.want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, tc.want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", tc.got); got != tc.want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestOption_Stringers_Empty(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
got fmt.Stringer
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "AdminUnadoptedListOptions",
|
||||
got: AdminUnadoptedListOptions{},
|
||||
want: `forge.AdminUnadoptedListOptions{}`,
|
||||
},
|
||||
{
|
||||
name: "MilestoneListOptions",
|
||||
got: MilestoneListOptions{},
|
||||
want: `forge.MilestoneListOptions{}`,
|
||||
},
|
||||
{
|
||||
name: "UserKeyListOptions",
|
||||
got: UserKeyListOptions{},
|
||||
want: `forge.UserKeyListOptions{}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := tc.got.String(); got != tc.want {
|
||||
t.Fatalf("got String()=%q, want %q", got, tc.want)
|
||||
}
|
||||
if got := fmt.Sprint(tc.got); got != tc.want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, tc.want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", tc.got); got != tc.want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Stringers_Good(t *testing.T) {
|
||||
client := NewClient("https://forge.example", "token")
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
got fmt.Stringer
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "RepoService",
|
||||
got: newRepoService(client),
|
||||
want: `forge.RepoService{resource=forge.Resource{path="/api/v1/repos/{owner}/{repo}", collection="/api/v1/repos/{owner}"}}`,
|
||||
},
|
||||
{
|
||||
name: "AdminService",
|
||||
got: newAdminService(client),
|
||||
want: `forge.AdminService{client=forge.Client{baseURL="https://forge.example", token=set, userAgent="go-forge/0.1"}}`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if got := tc.got.String(); got != tc.want {
|
||||
t.Fatalf("got String()=%q, want %q", got, tc.want)
|
||||
}
|
||||
if got := fmt.Sprint(tc.got); got != tc.want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, tc.want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", tc.got); got != tc.want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Stringers_NilSafe(t *testing.T) {
|
||||
var repo *RepoService
|
||||
if got, want := repo.String(), "forge.RepoService{<nil>}"; got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got, want := fmt.Sprint(repo), "forge.RepoService{<nil>}"; got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got, want := fmt.Sprintf("%#v", repo), "forge.RepoService{<nil>}"; got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
|
||||
var admin *AdminService
|
||||
if got, want := admin.String(), "forge.AdminService{<nil>}"; got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got, want := fmt.Sprint(admin), "forge.AdminService{<nil>}"; got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got, want := fmt.Sprintf("%#v", admin), "forge.AdminService{<nil>}"; got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
65
branches.go
65
branches.go
|
|
@ -2,87 +2,40 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// BranchService handles branch operations within a repository.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Branches.ListBranchProtections(ctx, "core", "go-forge")
|
||||
type BranchService struct {
|
||||
Resource[types.Branch, types.CreateBranchRepoOption, types.UpdateBranchRepoOption]
|
||||
Resource[types.Branch, types.CreateBranchRepoOption, struct{}]
|
||||
}
|
||||
|
||||
func newBranchService(c *Client) *BranchService {
|
||||
return &BranchService{
|
||||
Resource: *NewResource[types.Branch, types.CreateBranchRepoOption, types.UpdateBranchRepoOption](
|
||||
Resource: *NewResource[types.Branch, types.CreateBranchRepoOption, struct{}](
|
||||
c, "/api/v1/repos/{owner}/{repo}/branches/{branch}",
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
// ListBranches returns all branches for a repository.
|
||||
func (s *BranchService) ListBranches(ctx context.Context, owner, repo string) ([]types.Branch, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branches", pathParams("owner", owner, "repo", repo))
|
||||
return ListAll[types.Branch](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterBranches returns an iterator over all branches for a repository.
|
||||
func (s *BranchService) IterBranches(ctx context.Context, owner, repo string) iter.Seq2[types.Branch, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branches", pathParams("owner", owner, "repo", repo))
|
||||
return ListIter[types.Branch](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CreateBranch creates a new branch in a repository.
|
||||
func (s *BranchService) CreateBranch(ctx context.Context, owner, repo string, opts *types.CreateBranchRepoOption) (*types.Branch, error) {
|
||||
var out types.Branch
|
||||
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/branches", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetBranch returns a single branch by name.
|
||||
func (s *BranchService) GetBranch(ctx context.Context, owner, repo, branch string) (*types.Branch, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branches/{branch}", pathParams("owner", owner, "repo", repo, "branch", branch))
|
||||
var out types.Branch
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// UpdateBranch renames a branch in a repository.
|
||||
func (s *BranchService) UpdateBranch(ctx context.Context, owner, repo, branch string, opts *types.UpdateBranchRepoOption) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branches/{branch}", pathParams("owner", owner, "repo", repo, "branch", branch))
|
||||
return s.client.Patch(ctx, path, opts, nil)
|
||||
}
|
||||
|
||||
// DeleteBranch removes a branch from a repository.
|
||||
func (s *BranchService) DeleteBranch(ctx context.Context, owner, repo, branch string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branches/{branch}", pathParams("owner", owner, "repo", repo, "branch", branch))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListBranchProtections returns all branch protections for a repository.
|
||||
func (s *BranchService) ListBranchProtections(ctx context.Context, owner, repo string) ([]types.BranchProtection, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
|
||||
return ListAll[types.BranchProtection](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterBranchProtections returns an iterator over all branch protections for a repository.
|
||||
func (s *BranchService) IterBranchProtections(ctx context.Context, owner, repo string) iter.Seq2[types.BranchProtection, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
|
||||
return ListIter[types.BranchProtection](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetBranchProtection returns a single branch protection by name.
|
||||
func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, name string) (*types.BranchProtection, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections/{name}", pathParams("owner", owner, "repo", repo, "name", name))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
|
||||
var out types.BranchProtection
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -92,7 +45,7 @@ func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, na
|
|||
|
||||
// CreateBranchProtection creates a new branch protection rule.
|
||||
func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo string, opts *types.CreateBranchProtectionOption) (*types.BranchProtection, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
|
||||
var out types.BranchProtection
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -102,7 +55,7 @@ func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo
|
|||
|
||||
// EditBranchProtection updates an existing branch protection rule.
|
||||
func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, name string, opts *types.EditBranchProtectionOption) (*types.BranchProtection, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections/{name}", pathParams("owner", owner, "repo", repo, "name", name))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
|
||||
var out types.BranchProtection
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -112,6 +65,6 @@ func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, n
|
|||
|
||||
// DeleteBranchProtection deletes a branch protection rule.
|
||||
func (s *BranchService) DeleteBranchProtection(ctx context.Context, owner, repo, name string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections/{name}", pathParams("owner", owner, "repo", repo, "name", name))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestBranchService_ListBranches_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/branches" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Branch{{Name: "main"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
branches, err := f.Branches.ListBranches(context.Background(), "core", "go-forge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(branches) != 1 || branches[0].Name != "main" {
|
||||
t.Fatalf("got %#v", branches)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBranchService_CreateBranch_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/branches" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.CreateBranchRepoOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.BranchName != "release/v1" {
|
||||
t.Fatalf("unexpected body: %+v", body)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Branch{Name: body.BranchName})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
branch, err := f.Branches.CreateBranch(context.Background(), "core", "go-forge", &types.CreateBranchRepoOption{
|
||||
BranchName: "release/v1",
|
||||
OldRefName: "main",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if branch.Name != "release/v1" {
|
||||
t.Fatalf("got name=%q", branch.Name)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestBranchService_List_Good(t *testing.T) {
|
||||
func TestBranchService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -36,7 +36,7 @@ func TestBranchService_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBranchService_Get_Good(t *testing.T) {
|
||||
func TestBranchService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -61,34 +61,7 @@ func TestBranchService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestBranchService_UpdateBranch_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/branches/main" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.UpdateBranchRepoOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Name != "develop" {
|
||||
t.Errorf("got name=%q, want %q", opts.Name, "develop")
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Branches.UpdateBranch(context.Background(), "core", "go-forge", "main", &types.UpdateBranchRepoOption{
|
||||
Name: "develop",
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBranchService_CreateProtection_Good(t *testing.T) {
|
||||
func TestBranchService_Good_CreateProtection(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
|
|||
376
client.go
376
client.go
|
|
@ -3,160 +3,67 @@ package forge
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"mime/multipart"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
goio "io"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
// APIError represents an error response from the Forgejo API.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// if apiErr, ok := err.(*forge.APIError); ok {
|
||||
// _ = apiErr.StatusCode
|
||||
// }
|
||||
type APIError struct {
|
||||
StatusCode int
|
||||
Message string
|
||||
URL string
|
||||
}
|
||||
|
||||
// Error returns the formatted Forge API error string.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := (&forge.APIError{StatusCode: 404, Message: "not found", URL: "/api/v1/repos/x/y"}).Error()
|
||||
func (e *APIError) Error() string {
|
||||
if e == nil {
|
||||
return "forge.APIError{<nil>}"
|
||||
}
|
||||
return core.Concat("forge: ", e.URL, " ", strconv.Itoa(e.StatusCode), ": ", e.Message)
|
||||
return fmt.Sprintf("forge: %s %d: %s", e.URL, e.StatusCode, e.Message)
|
||||
}
|
||||
|
||||
// String returns a safe summary of the API error.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := err.String()
|
||||
func (e *APIError) String() string { return e.Error() }
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the API error.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := fmt.Sprintf("%#v", err)
|
||||
func (e *APIError) GoString() string { return e.Error() }
|
||||
|
||||
// IsNotFound returns true if the error is a 404 response.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// if forge.IsNotFound(err) {
|
||||
// return nil
|
||||
// }
|
||||
func IsNotFound(err error) bool {
|
||||
var apiErr *APIError
|
||||
return core.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound
|
||||
return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound
|
||||
}
|
||||
|
||||
// IsForbidden returns true if the error is a 403 response.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// if forge.IsForbidden(err) {
|
||||
// return nil
|
||||
// }
|
||||
func IsForbidden(err error) bool {
|
||||
var apiErr *APIError
|
||||
return core.As(err, &apiErr) && apiErr.StatusCode == http.StatusForbidden
|
||||
return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusForbidden
|
||||
}
|
||||
|
||||
// IsConflict returns true if the error is a 409 response.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// if forge.IsConflict(err) {
|
||||
// return nil
|
||||
// }
|
||||
func IsConflict(err error) bool {
|
||||
var apiErr *APIError
|
||||
return core.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict
|
||||
return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict
|
||||
}
|
||||
|
||||
// Option configures the Client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := []forge.Option{forge.WithUserAgent("go-forge/1.0")}
|
||||
type Option func(*Client)
|
||||
|
||||
// WithHTTPClient sets a custom http.Client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// c := forge.NewClient(url, token, forge.WithHTTPClient(http.DefaultClient))
|
||||
func WithHTTPClient(hc *http.Client) Option {
|
||||
return func(c *Client) { c.httpClient = hc }
|
||||
}
|
||||
|
||||
// WithUserAgent sets the User-Agent header.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// c := forge.NewClient(url, token, forge.WithUserAgent("go-forge/1.0"))
|
||||
func WithUserAgent(ua string) Option {
|
||||
return func(c *Client) { c.userAgent = ua }
|
||||
}
|
||||
|
||||
// RateLimit represents the rate limit information from the Forgejo API.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// rl := client.RateLimit()
|
||||
// _ = rl.Remaining
|
||||
type RateLimit struct {
|
||||
Limit int
|
||||
Remaining int
|
||||
Reset int64
|
||||
}
|
||||
|
||||
// String returns a safe summary of the rate limit state.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// rl := client.RateLimit()
|
||||
// _ = rl.String()
|
||||
func (r RateLimit) String() string {
|
||||
return core.Concat(
|
||||
"forge.RateLimit{limit=",
|
||||
strconv.Itoa(r.Limit),
|
||||
", remaining=",
|
||||
strconv.Itoa(r.Remaining),
|
||||
", reset=",
|
||||
strconv.FormatInt(r.Reset, 10),
|
||||
"}",
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the rate limit state.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = fmt.Sprintf("%#v", client.RateLimit())
|
||||
func (r RateLimit) GoString() string { return r.String() }
|
||||
|
||||
// Client is a low-level HTTP client for the Forgejo API.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// c := forge.NewClient("https://forge.lthn.ai", "token")
|
||||
// _ = c
|
||||
type Client struct {
|
||||
baseURL string
|
||||
token string
|
||||
|
|
@ -165,100 +72,15 @@ type Client struct {
|
|||
rateLimit RateLimit
|
||||
}
|
||||
|
||||
// BaseURL returns the configured Forgejo base URL.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// baseURL := client.BaseURL()
|
||||
func (c *Client) BaseURL() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return c.baseURL
|
||||
}
|
||||
|
||||
// RateLimit returns the last known rate limit information.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// rl := client.RateLimit()
|
||||
func (c *Client) RateLimit() RateLimit {
|
||||
if c == nil {
|
||||
return RateLimit{}
|
||||
}
|
||||
return c.rateLimit
|
||||
}
|
||||
|
||||
// UserAgent returns the configured User-Agent header value.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ua := client.UserAgent()
|
||||
func (c *Client) UserAgent() string {
|
||||
if c == nil {
|
||||
return ""
|
||||
}
|
||||
return c.userAgent
|
||||
}
|
||||
|
||||
// HTTPClient returns the configured underlying HTTP client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// hc := client.HTTPClient()
|
||||
func (c *Client) HTTPClient() *http.Client {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.httpClient
|
||||
}
|
||||
|
||||
// String returns a safe summary of the client configuration.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := client.String()
|
||||
func (c *Client) String() string {
|
||||
if c == nil {
|
||||
return "forge.Client{<nil>}"
|
||||
}
|
||||
tokenState := "unset"
|
||||
if c.HasToken() {
|
||||
tokenState = "set"
|
||||
}
|
||||
return core.Concat("forge.Client{baseURL=", strconv.Quote(c.baseURL), ", token=", tokenState, ", userAgent=", strconv.Quote(c.userAgent), "}")
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the client configuration.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := fmt.Sprintf("%#v", client)
|
||||
func (c *Client) GoString() string { return c.String() }
|
||||
|
||||
// HasToken reports whether the client was configured with an API token.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// if c.HasToken() {
|
||||
// _ = "authenticated"
|
||||
// }
|
||||
func (c *Client) HasToken() bool {
|
||||
if c == nil {
|
||||
return false
|
||||
}
|
||||
return c.token != ""
|
||||
}
|
||||
|
||||
// NewClient creates a new Forgejo API client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// c := forge.NewClient("https://forge.lthn.ai", "token")
|
||||
// _ = c
|
||||
func NewClient(url, token string, opts ...Option) *Client {
|
||||
c := &Client{
|
||||
baseURL: trimTrailingSlashes(url),
|
||||
baseURL: strings.TrimRight(url, "/"),
|
||||
token: token,
|
||||
httpClient: &http.Client{
|
||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
||||
|
|
@ -274,64 +96,36 @@ func NewClient(url, token string, opts ...Option) *Client {
|
|||
}
|
||||
|
||||
// Get performs a GET request.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// var out map[string]string
|
||||
// err := client.Get(ctx, "/api/v1/user", &out)
|
||||
func (c *Client) Get(ctx context.Context, path string, out any) error {
|
||||
_, err := c.doJSON(ctx, http.MethodGet, path, nil, out)
|
||||
return err
|
||||
}
|
||||
|
||||
// Post performs a POST request.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// var out map[string]any
|
||||
// err := client.Post(ctx, "/api/v1/orgs/core/repos", body, &out)
|
||||
func (c *Client) Post(ctx context.Context, path string, body, out any) error {
|
||||
_, err := c.doJSON(ctx, http.MethodPost, path, body, out)
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch performs a PATCH request.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// var out map[string]any
|
||||
// err := client.Patch(ctx, "/api/v1/repos/core/go-forge", body, &out)
|
||||
func (c *Client) Patch(ctx context.Context, path string, body, out any) error {
|
||||
_, err := c.doJSON(ctx, http.MethodPatch, path, body, out)
|
||||
return err
|
||||
}
|
||||
|
||||
// Put performs a PUT request.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// var out map[string]any
|
||||
// err := client.Put(ctx, "/api/v1/repos/core/go-forge", body, &out)
|
||||
func (c *Client) Put(ctx context.Context, path string, body, out any) error {
|
||||
_, err := c.doJSON(ctx, http.MethodPut, path, body, out)
|
||||
return err
|
||||
}
|
||||
|
||||
// Delete performs a DELETE request.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := client.Delete(ctx, "/api/v1/repos/core/go-forge")
|
||||
func (c *Client) Delete(ctx context.Context, path string) error {
|
||||
_, err := c.doJSON(ctx, http.MethodDelete, path, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteWithBody performs a DELETE request with a JSON body.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := client.DeleteWithBody(ctx, "/api/v1/repos/core/go-forge/labels", body)
|
||||
func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) error {
|
||||
_, err := c.doJSON(ctx, http.MethodDelete, path, body, nil)
|
||||
return err
|
||||
|
|
@ -340,29 +134,21 @@ func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) erro
|
|||
// PostRaw performs a POST request with a JSON body and returns the raw
|
||||
// response body as bytes instead of JSON-decoding. Useful for endpoints
|
||||
// such as /markdown that return raw HTML text.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// body, err := client.PostRaw(ctx, "/api/v1/markdown", payload)
|
||||
func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, error) {
|
||||
return c.postRawJSON(ctx, path, body)
|
||||
}
|
||||
|
||||
func (c *Client) postRawJSON(ctx context.Context, path string, body any) ([]byte, error) {
|
||||
url := c.baseURL + path
|
||||
|
||||
var bodyReader goio.Reader
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.PostRaw", "forge: marshal body", err)
|
||||
return nil, coreerr.E("Client.PostRaw", "forge: marshal body", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.PostRaw", "forge: create request", err)
|
||||
return nil, coreerr.E("Client.PostRaw", "forge: create request", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
|
|
@ -373,136 +159,30 @@ func (c *Client) postRawJSON(ctx context.Context, path string, body any) ([]byte
|
|||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.PostRaw", "forge: request POST "+path, err)
|
||||
return nil, coreerr.E("Client.PostRaw", "forge: request POST "+path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
c.updateRateLimit(resp)
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, c.parseError(resp, path)
|
||||
}
|
||||
|
||||
data, err := goio.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.PostRaw", "forge: read response body", err)
|
||||
return nil, coreerr.E("Client.PostRaw", "forge: read response body", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Client) postRawText(ctx context.Context, path, body string) ([]byte, error) {
|
||||
url := c.baseURL + path
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewBufferString(body))
|
||||
if err != nil {
|
||||
return nil, core.E("Client.PostText", "forge: create request", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
req.Header.Set("Accept", "text/html")
|
||||
req.Header.Set("Content-Type", "text/plain")
|
||||
if c.userAgent != "" {
|
||||
req.Header.Set("User-Agent", c.userAgent)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.PostText", "forge: request POST "+path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
c.updateRateLimit(resp)
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, c.parseError(resp, path)
|
||||
}
|
||||
|
||||
data, err := goio.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.PostText", "forge: read response body", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (c *Client) postMultipartJSON(ctx context.Context, path string, query map[string]string, fields map[string]string, fieldName, fileName string, content goio.Reader, out any) error {
|
||||
target, err := url.Parse(c.baseURL + path)
|
||||
if err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: parse url", err)
|
||||
}
|
||||
if len(query) > 0 {
|
||||
values := target.Query()
|
||||
for key, value := range query {
|
||||
values.Set(key, value)
|
||||
}
|
||||
target.RawQuery = values.Encode()
|
||||
}
|
||||
|
||||
var body bytes.Buffer
|
||||
writer := multipart.NewWriter(&body)
|
||||
for key, value := range fields {
|
||||
if err := writer.WriteField(key, value); err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: create multipart form field", err)
|
||||
}
|
||||
}
|
||||
if fieldName != "" {
|
||||
part, err := writer.CreateFormFile(fieldName, fileName)
|
||||
if err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: create multipart form file", err)
|
||||
}
|
||||
if content != nil {
|
||||
if _, err := goio.Copy(part, content); err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: write multipart form file", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := writer.Close(); err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: close multipart writer", err)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, target.String(), &body)
|
||||
if err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: create request", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
req.Header.Set("Content-Type", writer.FormDataContentType())
|
||||
if c.userAgent != "" {
|
||||
req.Header.Set("User-Agent", c.userAgent)
|
||||
}
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: request POST "+path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return c.parseError(resp, path)
|
||||
}
|
||||
|
||||
if out == nil {
|
||||
return nil
|
||||
}
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return core.E("Client.PostMultipart", "forge: decode response body", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRaw performs a GET request and returns the raw response body as bytes
|
||||
// instead of JSON-decoding. Useful for endpoints that return raw file content.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// body, err := client.GetRaw(ctx, "/api/v1/signing-key.gpg")
|
||||
func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) {
|
||||
url := c.baseURL + path
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.GetRaw", "forge: create request", err)
|
||||
return nil, coreerr.E("Client.GetRaw", "forge: create request", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
|
|
@ -512,19 +192,17 @@ func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) {
|
|||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.GetRaw", "forge: request GET "+path, err)
|
||||
return nil, coreerr.E("Client.GetRaw", "forge: request GET "+path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
c.updateRateLimit(resp)
|
||||
|
||||
if resp.StatusCode >= 400 {
|
||||
return nil, c.parseError(resp, path)
|
||||
}
|
||||
|
||||
data, err := goio.ReadAll(resp.Body)
|
||||
data, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.GetRaw", "forge: read response body", err)
|
||||
return nil, coreerr.E("Client.GetRaw", "forge: read response body", err)
|
||||
}
|
||||
|
||||
return data, nil
|
||||
|
|
@ -538,18 +216,18 @@ func (c *Client) do(ctx context.Context, method, path string, body, out any) err
|
|||
func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) (*http.Response, error) {
|
||||
url := c.baseURL + path
|
||||
|
||||
var bodyReader goio.Reader
|
||||
var bodyReader io.Reader
|
||||
if body != nil {
|
||||
data, err := json.Marshal(body)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.doJSON", "forge: marshal body", err)
|
||||
return nil, coreerr.E("Client.doJSON", "forge: marshal body", err)
|
||||
}
|
||||
bodyReader = bytes.NewReader(data)
|
||||
}
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.doJSON", "forge: create request", err)
|
||||
return nil, coreerr.E("Client.doJSON", "forge: create request", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
|
|
@ -563,7 +241,7 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any)
|
|||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, core.E("Client.doJSON", "forge: request "+method+" "+path, err)
|
||||
return nil, coreerr.E("Client.doJSON", "forge: request "+method+" "+path, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
|
|
@ -575,7 +253,7 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any)
|
|||
|
||||
if out != nil && resp.StatusCode != http.StatusNoContent {
|
||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
||||
return nil, core.E("Client.doJSON", "forge: decode response", err)
|
||||
return nil, coreerr.E("Client.doJSON", "forge: decode response", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -588,7 +266,7 @@ func (c *Client) parseError(resp *http.Response, path string) error {
|
|||
}
|
||||
|
||||
// Read a bit of the body to see if we can get a message
|
||||
data, _ := goio.ReadAll(goio.LimitReader(resp.Body, 1024))
|
||||
data, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
|
||||
_ = json.Unmarshal(data, &errBody)
|
||||
|
||||
msg := errBody.Message
|
||||
|
|
|
|||
160
client_test.go
160
client_test.go
|
|
@ -2,16 +2,14 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
func TestClient_Get_Good(t *testing.T) {
|
||||
func TestClient_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -37,7 +35,7 @@ func TestClient_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_Post_Good(t *testing.T) {
|
||||
func TestClient_Good_Post(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -64,37 +62,7 @@ func TestClient_Post_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_PostRaw_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if got := r.URL.Path; got != "/api/v1/markdown" {
|
||||
t.Errorf("wrong path: %s", got)
|
||||
}
|
||||
w.Header().Set("X-RateLimit-Limit", "100")
|
||||
w.Header().Set("X-RateLimit-Remaining", "98")
|
||||
w.Header().Set("X-RateLimit-Reset", "1700000001")
|
||||
w.Write([]byte("<p>Hello</p>"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL, "test-token")
|
||||
body := map[string]string{"text": "Hello"}
|
||||
got, err := c.PostRaw(context.Background(), "/api/v1/markdown", body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != "<p>Hello</p>" {
|
||||
t.Errorf("got body=%q", string(got))
|
||||
}
|
||||
rl := c.RateLimit()
|
||||
if rl.Limit != 100 || rl.Remaining != 98 || rl.Reset != 1700000001 {
|
||||
t.Fatalf("unexpected rate limit: %+v", rl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_Delete_Good(t *testing.T) {
|
||||
func TestClient_Good_Delete(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -110,36 +78,7 @@ func TestClient_Delete_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_GetRaw_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if got := r.URL.Path; got != "/api/v1/signing-key.gpg" {
|
||||
t.Errorf("wrong path: %s", got)
|
||||
}
|
||||
w.Header().Set("X-RateLimit-Limit", "60")
|
||||
w.Header().Set("X-RateLimit-Remaining", "59")
|
||||
w.Header().Set("X-RateLimit-Reset", "1700000002")
|
||||
w.Write([]byte("key-data"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := NewClient(srv.URL, "test-token")
|
||||
got, err := c.GetRaw(context.Background(), "/api/v1/signing-key.gpg")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(got) != "key-data" {
|
||||
t.Errorf("got body=%q", string(got))
|
||||
}
|
||||
rl := c.RateLimit()
|
||||
if rl.Limit != 60 || rl.Remaining != 59 || rl.Reset != 1700000002 {
|
||||
t.Fatalf("unexpected rate limit: %+v", rl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_ServerError_Bad(t *testing.T) {
|
||||
func TestClient_Bad_ServerError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "internal error"})
|
||||
|
|
@ -152,7 +91,7 @@ func TestClient_ServerError_Bad(t *testing.T) {
|
|||
t.Fatal("expected error")
|
||||
}
|
||||
var apiErr *APIError
|
||||
if !core.As(err, &apiErr) {
|
||||
if !errors.As(err, &apiErr) {
|
||||
t.Fatalf("expected APIError, got %T", err)
|
||||
}
|
||||
if apiErr.StatusCode != 500 {
|
||||
|
|
@ -160,7 +99,7 @@ func TestClient_ServerError_Bad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_NotFound_Bad(t *testing.T) {
|
||||
func TestClient_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
||||
|
|
@ -174,7 +113,7 @@ func TestClient_NotFound_Bad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_ContextCancellation_Good(t *testing.T) {
|
||||
func TestClient_Good_ContextCancellation(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
<-r.Context().Done()
|
||||
}))
|
||||
|
|
@ -189,117 +128,54 @@ func TestClient_ContextCancellation_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_Options_Good(t *testing.T) {
|
||||
func TestClient_Good_Options(t *testing.T) {
|
||||
c := NewClient("https://forge.lthn.ai", "tok",
|
||||
WithUserAgent("go-forge/1.0"),
|
||||
)
|
||||
if c.userAgent != "go-forge/1.0" {
|
||||
t.Errorf("got user agent=%q", c.userAgent)
|
||||
}
|
||||
if got := c.UserAgent(); got != "go-forge/1.0" {
|
||||
t.Errorf("got UserAgent()=%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_HasToken_Good(t *testing.T) {
|
||||
c := NewClient("https://forge.lthn.ai", "tok")
|
||||
if !c.HasToken() {
|
||||
t.Fatal("expected HasToken to report configured token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_HasToken_Bad(t *testing.T) {
|
||||
c := NewClient("https://forge.lthn.ai", "")
|
||||
if c.HasToken() {
|
||||
t.Fatal("expected HasToken to report missing token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_NilSafeAccessors(t *testing.T) {
|
||||
var c *Client
|
||||
if got := c.BaseURL(); got != "" {
|
||||
t.Fatalf("got BaseURL()=%q, want empty string", got)
|
||||
}
|
||||
if got := c.RateLimit(); got != (RateLimit{}) {
|
||||
t.Fatalf("got RateLimit()=%#v, want zero value", got)
|
||||
}
|
||||
if got := c.UserAgent(); got != "" {
|
||||
t.Fatalf("got UserAgent()=%q, want empty string", got)
|
||||
}
|
||||
if got := c.HTTPClient(); got != nil {
|
||||
t.Fatal("expected HTTPClient() to return nil")
|
||||
}
|
||||
if got := c.HasToken(); got {
|
||||
t.Fatal("expected HasToken() to report false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_WithHTTPClient_Good(t *testing.T) {
|
||||
func TestClient_Good_WithHTTPClient(t *testing.T) {
|
||||
custom := &http.Client{}
|
||||
c := NewClient("https://forge.lthn.ai", "tok", WithHTTPClient(custom))
|
||||
if c.httpClient != custom {
|
||||
t.Error("expected custom HTTP client to be set")
|
||||
}
|
||||
if got := c.HTTPClient(); got != custom {
|
||||
t.Error("expected HTTPClient() to return the configured HTTP client")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_String_Good(t *testing.T) {
|
||||
c := NewClient("https://forge.lthn.ai", "tok", WithUserAgent("go-forge/1.0"))
|
||||
got := fmt.Sprint(c)
|
||||
want := `forge.Client{baseURL="https://forge.lthn.ai", token=set, userAgent="go-forge/1.0"}`
|
||||
if got != want {
|
||||
t.Fatalf("got %q, want %q", got, want)
|
||||
}
|
||||
if got := c.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", c); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIError_Error_Good(t *testing.T) {
|
||||
func TestAPIError_Good_Error(t *testing.T) {
|
||||
e := &APIError{StatusCode: 404, Message: "not found", URL: "/api/v1/repos/x/y"}
|
||||
got := e.Error()
|
||||
want := "forge: /api/v1/repos/x/y 404: not found"
|
||||
if got != want {
|
||||
t.Errorf("got %q, want %q", got, want)
|
||||
}
|
||||
if got := e.String(); got != want {
|
||||
t.Errorf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(e); got != want {
|
||||
t.Errorf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", e); got != want {
|
||||
t.Errorf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConflict_Match_Good(t *testing.T) {
|
||||
func TestIsConflict_Good(t *testing.T) {
|
||||
err := &APIError{StatusCode: http.StatusConflict, Message: "conflict", URL: "/test"}
|
||||
if !IsConflict(err) {
|
||||
t.Error("expected IsConflict to return true for 409")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsConflict_NotConflict_Bad(t *testing.T) {
|
||||
func TestIsConflict_Bad_NotConflict(t *testing.T) {
|
||||
err := &APIError{StatusCode: http.StatusNotFound, Message: "not found", URL: "/test"}
|
||||
if IsConflict(err) {
|
||||
t.Error("expected IsConflict to return false for 404")
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsForbidden_NotForbidden_Bad(t *testing.T) {
|
||||
func TestIsForbidden_Bad_NotForbidden(t *testing.T) {
|
||||
err := &APIError{StatusCode: http.StatusNotFound, Message: "not found", URL: "/test"}
|
||||
if IsForbidden(err) {
|
||||
t.Error("expected IsForbidden to return false for 404")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClient_RateLimit_Good(t *testing.T) {
|
||||
func TestClient_Good_RateLimit(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-RateLimit-Limit", "100")
|
||||
w.Header().Set("X-RateLimit-Remaining", "99")
|
||||
|
|
@ -326,7 +202,7 @@ func TestClient_RateLimit_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_Forbidden_Bad(t *testing.T) {
|
||||
func TestClient_Bad_Forbidden(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "forbidden"})
|
||||
|
|
@ -340,7 +216,7 @@ func TestClient_Forbidden_Bad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestClient_Conflict_Bad(t *testing.T) {
|
||||
func TestClient_Bad_Conflict(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "already exists"})
|
||||
|
|
|
|||
|
|
@ -2,15 +2,14 @@ package main
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"maps"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
// typeGrouping maps type name prefixes to output file names.
|
||||
|
|
@ -111,7 +110,7 @@ func classifyType(name string) string {
|
|||
bestKey := ""
|
||||
bestGroup := ""
|
||||
for key, group := range typeGrouping {
|
||||
if core.HasPrefix(name, key) && len(key) > len(bestKey) {
|
||||
if strings.HasPrefix(name, key) && len(key) > len(bestKey) {
|
||||
bestKey = key
|
||||
bestGroup = group
|
||||
}
|
||||
|
|
@ -123,10 +122,10 @@ func classifyType(name string) string {
|
|||
// Strip CRUD prefixes and Option suffix, then retry.
|
||||
base := name
|
||||
for _, prefix := range []string{"Create", "Edit", "Delete", "Update", "Add", "Submit", "Replace", "Set", "Transfer"} {
|
||||
base = core.TrimPrefix(base, prefix)
|
||||
base = strings.TrimPrefix(base, prefix)
|
||||
}
|
||||
base = core.TrimSuffix(base, "Option")
|
||||
base = core.TrimSuffix(base, "Options")
|
||||
base = strings.TrimSuffix(base, "Option")
|
||||
base = strings.TrimSuffix(base, "Options")
|
||||
|
||||
if base != name && base != "" {
|
||||
if group, ok := typeGrouping[base]; ok {
|
||||
|
|
@ -136,7 +135,7 @@ func classifyType(name string) string {
|
|||
bestKey = ""
|
||||
bestGroup = ""
|
||||
for key, group := range typeGrouping {
|
||||
if core.HasPrefix(base, key) && len(key) > len(bestKey) {
|
||||
if strings.HasPrefix(base, key) && len(key) > len(bestKey) {
|
||||
bestKey = key
|
||||
bestGroup = group
|
||||
}
|
||||
|
|
@ -152,7 +151,7 @@ func classifyType(name string) string {
|
|||
// sanitiseLine collapses a multi-line string into a single line,
|
||||
// replacing newlines and consecutive whitespace with a single space.
|
||||
func sanitiseLine(s string) string {
|
||||
return core.Join(" ", splitFields(s)...)
|
||||
return strings.Join(strings.Fields(s), " ")
|
||||
}
|
||||
|
||||
// enumConstName generates a Go constant name for an enum value.
|
||||
|
|
@ -177,12 +176,6 @@ import "time"
|
|||
{{- if .Description}}
|
||||
// {{.Name}} — {{sanitise .Description}}
|
||||
{{- end}}
|
||||
{{- if .Usage}}
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := {{.Usage}}
|
||||
{{- end}}
|
||||
{{- if .IsEnum}}
|
||||
type {{.Name}} string
|
||||
|
||||
|
|
@ -191,8 +184,6 @@ const (
|
|||
{{enumConstName $t.Name .}} {{$t.Name}} = "{{.}}"
|
||||
{{- end}}
|
||||
)
|
||||
{{- else if .IsAlias}}
|
||||
type {{.Name}} {{.AliasType}}
|
||||
{{- else if (eq (len .Fields) 0)}}
|
||||
// {{.Name}} has no fields in the swagger spec.
|
||||
type {{.Name}} struct{}
|
||||
|
|
@ -213,18 +204,11 @@ type templateData struct {
|
|||
}
|
||||
|
||||
// Generate writes Go source files for the extracted types, grouped by logical domain.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := Generate(types, pairs, "types")
|
||||
// _ = err
|
||||
func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error {
|
||||
if err := coreio.Local.EnsureDir(outDir); err != nil {
|
||||
return core.E("Generate", "create output directory", err)
|
||||
return coreerr.E("Generate", "create output directory", err)
|
||||
}
|
||||
|
||||
populateUsageExamples(types)
|
||||
|
||||
// Group types by output file.
|
||||
groups := make(map[string][]*GoType)
|
||||
for _, gt := range types {
|
||||
|
|
@ -235,7 +219,7 @@ func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error {
|
|||
// Sort types within each group for deterministic output.
|
||||
for file := range groups {
|
||||
slices.SortFunc(groups[file], func(a, b *GoType) int {
|
||||
return cmp.Compare(a.Name, b.Name)
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -244,158 +228,20 @@ func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error {
|
|||
slices.Sort(fileNames)
|
||||
|
||||
for _, file := range fileNames {
|
||||
outPath := core.JoinPath(outDir, file+".go")
|
||||
outPath := filepath.Join(outDir, file+".go")
|
||||
if err := writeFile(outPath, groups[file]); err != nil {
|
||||
return core.E("Generate", "write "+outPath, err)
|
||||
return coreerr.E("Generate", "write "+outPath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func populateUsageExamples(types map[string]*GoType) {
|
||||
for _, gt := range types {
|
||||
gt.Usage = usageExample(gt)
|
||||
}
|
||||
}
|
||||
|
||||
func usageExample(gt *GoType) string {
|
||||
switch {
|
||||
case gt.IsEnum && len(gt.EnumValues) > 0:
|
||||
return enumConstName(gt.Name, gt.EnumValues[0])
|
||||
case gt.IsAlias:
|
||||
return gt.Name + "(" + exampleTypeExpression(gt.AliasType) + ")"
|
||||
default:
|
||||
example := exampleTypeLiteral(gt)
|
||||
if example == "" {
|
||||
example = gt.Name + "{}"
|
||||
}
|
||||
return example
|
||||
}
|
||||
}
|
||||
|
||||
func exampleTypeLiteral(gt *GoType) string {
|
||||
if len(gt.Fields) == 0 {
|
||||
return gt.Name + "{}"
|
||||
}
|
||||
|
||||
field := chooseUsageField(gt.Fields)
|
||||
if field.GoName == "" {
|
||||
return gt.Name + "{}"
|
||||
}
|
||||
|
||||
return gt.Name + "{" + field.GoName + ": " + exampleValue(field) + "}"
|
||||
}
|
||||
|
||||
func exampleTypeExpression(typeName string) string {
|
||||
switch {
|
||||
case typeName == "string":
|
||||
return strconv.Quote("example")
|
||||
case typeName == "bool":
|
||||
return "true"
|
||||
case typeName == "int", typeName == "int32", typeName == "int64", typeName == "uint", typeName == "uint32", typeName == "uint64":
|
||||
return "1"
|
||||
case typeName == "float32", typeName == "float64":
|
||||
return "1.0"
|
||||
case typeName == "time.Time":
|
||||
return "time.Now()"
|
||||
case core.HasPrefix(typeName, "[]string"):
|
||||
return "[]string{\"example\"}"
|
||||
case core.HasPrefix(typeName, "[]int64"):
|
||||
return "[]int64{1}"
|
||||
case core.HasPrefix(typeName, "[]int"):
|
||||
return "[]int{1}"
|
||||
case core.HasPrefix(typeName, "map["):
|
||||
return typeName + "{\"key\": \"value\"}"
|
||||
default:
|
||||
return typeName + "{}"
|
||||
}
|
||||
}
|
||||
|
||||
func chooseUsageField(fields []GoField) GoField {
|
||||
best := fields[0]
|
||||
bestScore := usageFieldScore(best)
|
||||
for _, field := range fields[1:] {
|
||||
score := usageFieldScore(field)
|
||||
if score < bestScore || (score == bestScore && field.GoName < best.GoName) {
|
||||
best = field
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func usageFieldScore(field GoField) int {
|
||||
score := 100
|
||||
if field.Required {
|
||||
score -= 50
|
||||
}
|
||||
switch {
|
||||
case core.HasSuffix(field.GoType, "string"):
|
||||
score -= 30
|
||||
case core.Contains(field.GoType, "time.Time"):
|
||||
score -= 25
|
||||
case core.HasSuffix(field.GoType, "bool"):
|
||||
score -= 20
|
||||
case core.Contains(field.GoType, "int"):
|
||||
score -= 15
|
||||
case core.HasPrefix(field.GoType, "[]"):
|
||||
score -= 10
|
||||
}
|
||||
if core.Contains(field.GoName, "Name") || core.Contains(field.GoName, "Title") || core.Contains(field.GoName, "Body") || core.Contains(field.GoName, "Description") {
|
||||
score -= 10
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
func exampleValue(field GoField) string {
|
||||
switch {
|
||||
case core.HasPrefix(field.GoType, "*"):
|
||||
return "&" + core.TrimPrefix(field.GoType, "*") + "{}"
|
||||
case field.GoType == "string":
|
||||
return exampleStringValue(field.GoName)
|
||||
case field.GoType == "time.Time":
|
||||
return "time.Now()"
|
||||
case field.GoType == "bool":
|
||||
return "true"
|
||||
case core.HasSuffix(field.GoType, "int64"), core.HasSuffix(field.GoType, "int"), core.HasSuffix(field.GoType, "uint64"), core.HasSuffix(field.GoType, "uint"):
|
||||
return "1"
|
||||
case core.HasPrefix(field.GoType, "[]string"):
|
||||
return "[]string{\"example\"}"
|
||||
case core.HasPrefix(field.GoType, "[]int64"):
|
||||
return "[]int64{1}"
|
||||
case core.HasPrefix(field.GoType, "[]int"):
|
||||
return "[]int{1}"
|
||||
case core.HasPrefix(field.GoType, "map["):
|
||||
return field.GoType + "{\"key\": \"value\"}"
|
||||
default:
|
||||
return "{}"
|
||||
}
|
||||
}
|
||||
|
||||
func exampleStringValue(fieldName string) string {
|
||||
switch {
|
||||
case core.Contains(fieldName, "URL"):
|
||||
return "\"https://example.com\""
|
||||
case core.Contains(fieldName, "Email"):
|
||||
return "\"alice@example.com\""
|
||||
case core.Contains(fieldName, "Tag"):
|
||||
return "\"v1.0.0\""
|
||||
case core.Contains(fieldName, "Branch"), core.Contains(fieldName, "Ref"):
|
||||
return "\"main\""
|
||||
default:
|
||||
return "\"example\""
|
||||
}
|
||||
}
|
||||
|
||||
// writeFile renders and writes a single Go source file for the given types.
|
||||
func writeFile(path string, types []*GoType) error {
|
||||
needTime := slices.ContainsFunc(types, func(gt *GoType) bool {
|
||||
if core.Contains(gt.AliasType, "time.Time") {
|
||||
return true
|
||||
}
|
||||
return slices.ContainsFunc(gt.Fields, func(f GoField) bool {
|
||||
return core.Contains(f.GoType, "time.Time")
|
||||
return strings.Contains(f.GoType, "time.Time")
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -406,12 +252,11 @@ func writeFile(path string, types []*GoType) error {
|
|||
|
||||
var buf bytes.Buffer
|
||||
if err := fileHeader.Execute(&buf, data); err != nil {
|
||||
return core.E("writeFile", "execute template", err)
|
||||
return coreerr.E("writeFile", "execute template", err)
|
||||
}
|
||||
|
||||
content := strings.TrimRight(buf.String(), "\n") + "\n"
|
||||
if err := coreio.Local.Write(path, content); err != nil {
|
||||
return core.E("writeFile", "write file", err)
|
||||
if err := coreio.Local.Write(path, buf.String()); err != nil {
|
||||
return coreerr.E("writeFile", "write file", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
)
|
||||
|
||||
func TestGenerate_CreatesFiles_Good(t *testing.T) {
|
||||
func TestGenerate_Good_CreatesFiles(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -21,10 +23,10 @@ func TestGenerate_CreatesFiles_Good(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries, _ := coreio.Local.List(outDir)
|
||||
entries, _ := os.ReadDir(outDir)
|
||||
goFiles := 0
|
||||
for _, e := range entries {
|
||||
if core.HasSuffix(e.Name(), ".go") {
|
||||
if strings.HasSuffix(e.Name(), ".go") {
|
||||
goFiles++
|
||||
}
|
||||
}
|
||||
|
|
@ -33,7 +35,7 @@ func TestGenerate_CreatesFiles_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGenerate_ValidGoSyntax_Good(t *testing.T) {
|
||||
func TestGenerate_Good_ValidGoSyntax(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -47,11 +49,11 @@ func TestGenerate_ValidGoSyntax_Good(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries, _ := coreio.Local.List(outDir)
|
||||
entries, _ := os.ReadDir(outDir)
|
||||
var content string
|
||||
for _, e := range entries {
|
||||
if core.HasSuffix(e.Name(), ".go") {
|
||||
content, err = coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
||||
if strings.HasSuffix(e.Name(), ".go") {
|
||||
content, err = coreio.Local.Read(filepath.Join(outDir, e.Name()))
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
|
@ -60,15 +62,15 @@ func TestGenerate_ValidGoSyntax_Good(t *testing.T) {
|
|||
if err != nil || content == "" {
|
||||
t.Fatal("could not read any generated file")
|
||||
}
|
||||
if !core.Contains(content, "package types") {
|
||||
if !strings.Contains(content, "package types") {
|
||||
t.Error("missing package declaration")
|
||||
}
|
||||
if !core.Contains(content, "// Code generated") {
|
||||
if !strings.Contains(content, "// Code generated") {
|
||||
t.Error("missing generated comment")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_RepositoryType_Good(t *testing.T) {
|
||||
func TestGenerate_Good_RepositoryType(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -83,10 +85,10 @@ func TestGenerate_RepositoryType_Good(t *testing.T) {
|
|||
}
|
||||
|
||||
var content string
|
||||
entries, _ := coreio.Local.List(outDir)
|
||||
entries, _ := os.ReadDir(outDir)
|
||||
for _, e := range entries {
|
||||
data, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
||||
if core.Contains(data, "type Repository struct") {
|
||||
data, _ := coreio.Local.Read(filepath.Join(outDir, e.Name()))
|
||||
if strings.Contains(data, "type Repository struct") {
|
||||
content = data
|
||||
break
|
||||
}
|
||||
|
|
@ -105,13 +107,13 @@ func TestGenerate_RepositoryType_Good(t *testing.T) {
|
|||
"`json:\"private,omitempty\"`",
|
||||
}
|
||||
for _, check := range checks {
|
||||
if !core.Contains(content, check) {
|
||||
if !strings.Contains(content, check) {
|
||||
t.Errorf("missing field with tag %s", check)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_TimeImport_Good(t *testing.T) {
|
||||
func TestGenerate_Good_TimeImport(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -125,131 +127,11 @@ func TestGenerate_TimeImport_Good(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries, _ := coreio.Local.List(outDir)
|
||||
entries, _ := os.ReadDir(outDir)
|
||||
for _, e := range entries {
|
||||
content, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
||||
if core.Contains(content, "time.Time") && !core.Contains(content, "\"time\"") {
|
||||
content, _ := coreio.Local.Read(filepath.Join(outDir, e.Name()))
|
||||
if strings.Contains(content, "time.Time") && !strings.Contains(content, "\"time\"") {
|
||||
t.Errorf("file %s uses time.Time but doesn't import time", e.Name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_AdditionalProperties_Good(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
types := ExtractTypes(spec)
|
||||
pairs := DetectCRUDPairs(spec)
|
||||
|
||||
outDir := t.TempDir()
|
||||
if err := Generate(types, pairs, outDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries, _ := coreio.Local.List(outDir)
|
||||
var hookContent string
|
||||
var teamContent string
|
||||
for _, e := range entries {
|
||||
data, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
||||
if core.Contains(data, "type CreateHookOptionConfig") {
|
||||
hookContent = data
|
||||
}
|
||||
if core.Contains(data, "UnitsMap map[string]string `json:\"units_map,omitempty\"`") {
|
||||
teamContent = data
|
||||
}
|
||||
}
|
||||
if hookContent == "" {
|
||||
t.Fatal("CreateHookOptionConfig type not found in any generated file")
|
||||
}
|
||||
if !core.Contains(hookContent, "type CreateHookOptionConfig map[string]any") {
|
||||
t.Fatalf("generated alias not found in file:\n%s", hookContent)
|
||||
}
|
||||
if teamContent == "" {
|
||||
t.Fatal("typed units_map field not found in any generated file")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_UsageExamples_Good(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
types := ExtractTypes(spec)
|
||||
pairs := DetectCRUDPairs(spec)
|
||||
|
||||
outDir := t.TempDir()
|
||||
if err := Generate(types, pairs, outDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries, _ := coreio.Local.List(outDir)
|
||||
var content string
|
||||
for _, e := range entries {
|
||||
data, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
||||
if core.Contains(data, "type CreateIssueOption struct") {
|
||||
content = data
|
||||
break
|
||||
}
|
||||
}
|
||||
if content == "" {
|
||||
t.Fatal("CreateIssueOption type not found in any generated file")
|
||||
}
|
||||
if !core.Contains(content, "// Usage:") {
|
||||
t.Fatalf("generated option type is missing usage documentation:\n%s", content)
|
||||
}
|
||||
if !core.Contains(content, "opts :=") {
|
||||
t.Fatalf("generated usage example is missing assignment syntax:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerate_UsageExamples_AllKinds_Good(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
types := ExtractTypes(spec)
|
||||
pairs := DetectCRUDPairs(spec)
|
||||
|
||||
outDir := t.TempDir()
|
||||
if err := Generate(types, pairs, outDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries, _ := coreio.Local.List(outDir)
|
||||
var content string
|
||||
for _, e := range entries {
|
||||
data, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
||||
if core.Contains(data, "type CommitStatusState string") {
|
||||
content = data
|
||||
break
|
||||
}
|
||||
}
|
||||
if content == "" {
|
||||
t.Fatal("CommitStatusState type not found in any generated file")
|
||||
}
|
||||
if !core.Contains(content, "type CommitStatusState string") {
|
||||
t.Fatalf("CommitStatusState type not generated:\n%s", content)
|
||||
}
|
||||
if !core.Contains(content, "// Usage:") {
|
||||
t.Fatalf("generated enum type is missing usage documentation:\n%s", content)
|
||||
}
|
||||
|
||||
content = ""
|
||||
for _, e := range entries {
|
||||
data, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
||||
if core.Contains(data, "type CreateHookOptionConfig map[string]any") {
|
||||
content = data
|
||||
break
|
||||
}
|
||||
}
|
||||
if content == "" {
|
||||
t.Fatal("CreateHookOptionConfig type not found in any generated file")
|
||||
}
|
||||
if !core.Contains(content, "CreateHookOptionConfig(map[string]any{\"key\": \"value\"})") {
|
||||
t.Fatalf("generated alias type is missing a valid usage example:\n%s", content)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,41 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"unicode"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
func splitFields(s string) []string {
|
||||
return splitFunc(s, unicode.IsSpace)
|
||||
}
|
||||
|
||||
func splitSnakeKebab(s string) []string {
|
||||
return splitFunc(s, func(r rune) bool {
|
||||
return r == '_' || r == '-'
|
||||
})
|
||||
}
|
||||
|
||||
func splitFunc(s string, isDelimiter func(rune) bool) []string {
|
||||
var parts []string
|
||||
buf := core.NewBuilder()
|
||||
|
||||
flush := func() {
|
||||
if buf.Len() == 0 {
|
||||
return
|
||||
}
|
||||
parts = append(parts, buf.String())
|
||||
buf.Reset()
|
||||
}
|
||||
|
||||
for _, r := range s {
|
||||
if isDelimiter(r) {
|
||||
flush()
|
||||
continue
|
||||
}
|
||||
buf.WriteRune(r)
|
||||
}
|
||||
flush()
|
||||
|
||||
return parts
|
||||
}
|
||||
|
|
@ -2,9 +2,8 @@ package main
|
|||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
|
@ -12,26 +11,20 @@ func main() {
|
|||
outDir := flag.String("out", "types", "output directory for generated types")
|
||||
flag.Parse()
|
||||
|
||||
if err := run(*specPath, *outDir); err != nil {
|
||||
core.Print(os.Stderr, "forgegen: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func run(specPath, outDir string) error {
|
||||
spec, err := LoadSpec(specPath)
|
||||
spec, err := LoadSpec(*specPath)
|
||||
if err != nil {
|
||||
return core.E("forgegen.main", "load spec", err)
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
types := ExtractTypes(spec)
|
||||
pairs := DetectCRUDPairs(spec)
|
||||
|
||||
core.Print(nil, "Loaded %d types, %d CRUD pairs", len(types), len(pairs))
|
||||
core.Print(nil, "Output dir: %s", outDir)
|
||||
fmt.Printf("Loaded %d types, %d CRUD pairs\n", len(types), len(pairs))
|
||||
fmt.Printf("Output dir: %s\n", *outDir)
|
||||
|
||||
if err := Generate(types, pairs, outDir); err != nil {
|
||||
return core.E("forgegen.main", "generate types", err)
|
||||
if err := Generate(types, pairs, *outDir); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
)
|
||||
|
||||
func TestMain_Run_Good(t *testing.T) {
|
||||
outDir := t.TempDir()
|
||||
if err := run("../../testdata/swagger.v1.json", outDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
entries, err := coreio.Local.List(outDir)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
goFiles := 0
|
||||
for _, e := range entries {
|
||||
if core.HasSuffix(e.Name(), ".go") {
|
||||
goFiles++
|
||||
}
|
||||
}
|
||||
if goFiles == 0 {
|
||||
t.Fatal("no .go files generated by run")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMain_Run_Bad(t *testing.T) {
|
||||
err := run("/does/not/exist/swagger.v1.json", t.TempDir())
|
||||
if err == nil {
|
||||
t.Fatal("expected error for invalid spec path")
|
||||
}
|
||||
if !core.Contains(err.Error(), "load spec") {
|
||||
t.Fatalf("got error %q, expected load spec context", err.Error())
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,16 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"cmp"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
// Spec represents a Swagger 2.0 specification document.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// spec, err := LoadSpec("testdata/swagger.v1.json")
|
||||
// _ = spec
|
||||
type Spec struct {
|
||||
Swagger string `json:"swagger"`
|
||||
Info SpecInfo `json:"info"`
|
||||
|
|
@ -23,70 +19,42 @@ type Spec struct {
|
|||
}
|
||||
|
||||
// SpecInfo holds metadata about the API specification.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = SpecInfo{Title: "Forgejo API", Version: "1.0"}
|
||||
type SpecInfo struct {
|
||||
Title string `json:"title"`
|
||||
Version string `json:"version"`
|
||||
}
|
||||
|
||||
// SchemaDefinition represents a single type definition in the swagger spec.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = SchemaDefinition{Type: "object"}
|
||||
type SchemaDefinition struct {
|
||||
Description string `json:"description"`
|
||||
Format string `json:"format"`
|
||||
Ref string `json:"$ref"`
|
||||
Items *SchemaProperty `json:"items"`
|
||||
Type string `json:"type"`
|
||||
Properties map[string]SchemaProperty `json:"properties"`
|
||||
Required []string `json:"required"`
|
||||
Enum []any `json:"enum"`
|
||||
AdditionalProperties *SchemaProperty `json:"additionalProperties"`
|
||||
XGoName string `json:"x-go-name"`
|
||||
Description string `json:"description"`
|
||||
Type string `json:"type"`
|
||||
Properties map[string]SchemaProperty `json:"properties"`
|
||||
Required []string `json:"required"`
|
||||
Enum []any `json:"enum"`
|
||||
XGoName string `json:"x-go-name"`
|
||||
}
|
||||
|
||||
// SchemaProperty represents a single property within a schema definition.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = SchemaProperty{Type: "string"}
|
||||
type SchemaProperty struct {
|
||||
Type string `json:"type"`
|
||||
Format string `json:"format"`
|
||||
Description string `json:"description"`
|
||||
Ref string `json:"$ref"`
|
||||
Items *SchemaProperty `json:"items"`
|
||||
Enum []any `json:"enum"`
|
||||
AdditionalProperties *SchemaProperty `json:"additionalProperties"`
|
||||
XGoName string `json:"x-go-name"`
|
||||
Type string `json:"type"`
|
||||
Format string `json:"format"`
|
||||
Description string `json:"description"`
|
||||
Ref string `json:"$ref"`
|
||||
Items *SchemaProperty `json:"items"`
|
||||
Enum []any `json:"enum"`
|
||||
XGoName string `json:"x-go-name"`
|
||||
}
|
||||
|
||||
// GoType is the intermediate representation for a Go type to be generated.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = GoType{Name: "Repository"}
|
||||
type GoType struct {
|
||||
Name string
|
||||
Description string
|
||||
Usage string
|
||||
Fields []GoField
|
||||
IsEnum bool
|
||||
EnumValues []string
|
||||
IsAlias bool
|
||||
AliasType string
|
||||
}
|
||||
|
||||
// GoField is the intermediate representation for a single struct field.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = GoField{GoName: "ID", GoType: "int64"}
|
||||
type GoField struct {
|
||||
GoName string
|
||||
GoType string
|
||||
|
|
@ -96,10 +64,6 @@ type GoField struct {
|
|||
}
|
||||
|
||||
// CRUDPair groups a base type with its corresponding Create and Edit option types.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = CRUDPair{Base: "Repository", Create: "CreateRepoOption", Edit: "EditRepoOption"}
|
||||
type CRUDPair struct {
|
||||
Base string
|
||||
Create string
|
||||
|
|
@ -107,29 +71,19 @@ type CRUDPair struct {
|
|||
}
|
||||
|
||||
// LoadSpec reads and parses a Swagger 2.0 JSON file from the given path.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// spec, err := LoadSpec("testdata/swagger.v1.json")
|
||||
// _ = spec
|
||||
func LoadSpec(path string) (*Spec, error) {
|
||||
content, err := coreio.Local.Read(path)
|
||||
if err != nil {
|
||||
return nil, core.E("LoadSpec", "read spec", err)
|
||||
return nil, coreerr.E("LoadSpec", "read spec", err)
|
||||
}
|
||||
var spec Spec
|
||||
if err := json.Unmarshal([]byte(content), &spec); err != nil {
|
||||
return nil, core.E("LoadSpec", "parse spec", err)
|
||||
return nil, coreerr.E("LoadSpec", "parse spec", err)
|
||||
}
|
||||
return &spec, nil
|
||||
}
|
||||
|
||||
// ExtractTypes converts all swagger definitions into Go type intermediate representations.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// types := ExtractTypes(spec)
|
||||
// _ = types["Repository"]
|
||||
func ExtractTypes(spec *Spec) map[string]*GoType {
|
||||
result := make(map[string]*GoType)
|
||||
for name, def := range spec.Definitions {
|
||||
|
|
@ -137,19 +91,12 @@ func ExtractTypes(spec *Spec) map[string]*GoType {
|
|||
if len(def.Enum) > 0 {
|
||||
gt.IsEnum = true
|
||||
for _, v := range def.Enum {
|
||||
gt.EnumValues = append(gt.EnumValues, core.Sprint(v))
|
||||
gt.EnumValues = append(gt.EnumValues, fmt.Sprintf("%v", v))
|
||||
}
|
||||
slices.Sort(gt.EnumValues)
|
||||
result[name] = gt
|
||||
continue
|
||||
}
|
||||
|
||||
if aliasType, ok := definitionAliasType(def, spec.Definitions); ok {
|
||||
gt.IsAlias = true
|
||||
gt.AliasType = aliasType
|
||||
result[name] = gt
|
||||
continue
|
||||
}
|
||||
required := make(map[string]bool)
|
||||
for _, r := range def.Required {
|
||||
required[r] = true
|
||||
|
|
@ -161,7 +108,7 @@ func ExtractTypes(spec *Spec) map[string]*GoType {
|
|||
}
|
||||
gf := GoField{
|
||||
GoName: goName,
|
||||
GoType: resolveGoType(prop, spec.Definitions),
|
||||
GoType: resolveGoType(prop),
|
||||
JSONName: fieldName,
|
||||
Comment: prop.Description,
|
||||
Required: required[fieldName],
|
||||
|
|
@ -169,69 +116,24 @@ func ExtractTypes(spec *Spec) map[string]*GoType {
|
|||
gt.Fields = append(gt.Fields, gf)
|
||||
}
|
||||
slices.SortFunc(gt.Fields, func(a, b GoField) int {
|
||||
return cmp.Compare(a.GoName, b.GoName)
|
||||
return strings.Compare(a.GoName, b.GoName)
|
||||
})
|
||||
result[name] = gt
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func definitionAliasType(def SchemaDefinition, defs map[string]SchemaDefinition) (string, bool) {
|
||||
if def.Ref != "" {
|
||||
return refName(def.Ref), true
|
||||
}
|
||||
|
||||
switch def.Type {
|
||||
case "string":
|
||||
return "string", true
|
||||
case "integer":
|
||||
switch def.Format {
|
||||
case "int64":
|
||||
return "int64", true
|
||||
case "int32":
|
||||
return "int32", true
|
||||
default:
|
||||
return "int", true
|
||||
}
|
||||
case "number":
|
||||
switch def.Format {
|
||||
case "float":
|
||||
return "float32", true
|
||||
default:
|
||||
return "float64", true
|
||||
}
|
||||
case "boolean":
|
||||
return "bool", true
|
||||
case "array":
|
||||
if def.Items != nil {
|
||||
return "[]" + resolveGoType(*def.Items, defs), true
|
||||
}
|
||||
return "[]any", true
|
||||
case "object":
|
||||
if def.AdditionalProperties != nil {
|
||||
return resolveMapType(*def.AdditionalProperties, defs), true
|
||||
}
|
||||
}
|
||||
|
||||
return "", false
|
||||
}
|
||||
|
||||
// DetectCRUDPairs finds Create*Option / Edit*Option pairs in the swagger definitions
|
||||
// and maps them back to the base type name.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// pairs := DetectCRUDPairs(spec)
|
||||
// _ = pairs
|
||||
func DetectCRUDPairs(spec *Spec) []CRUDPair {
|
||||
var pairs []CRUDPair
|
||||
for name := range spec.Definitions {
|
||||
if !core.HasPrefix(name, "Create") || !core.HasSuffix(name, "Option") {
|
||||
if !strings.HasPrefix(name, "Create") || !strings.HasSuffix(name, "Option") {
|
||||
continue
|
||||
}
|
||||
inner := core.TrimPrefix(name, "Create")
|
||||
inner = core.TrimSuffix(inner, "Option")
|
||||
editName := core.Concat("Edit", inner, "Option")
|
||||
inner := strings.TrimPrefix(name, "Create")
|
||||
inner = strings.TrimSuffix(inner, "Option")
|
||||
editName := "Edit" + inner + "Option"
|
||||
pair := CRUDPair{Base: inner, Create: name}
|
||||
if _, ok := spec.Definitions[editName]; ok {
|
||||
pair.Edit = editName
|
||||
|
|
@ -239,15 +141,16 @@ func DetectCRUDPairs(spec *Spec) []CRUDPair {
|
|||
pairs = append(pairs, pair)
|
||||
}
|
||||
slices.SortFunc(pairs, func(a, b CRUDPair) int {
|
||||
return cmp.Compare(a.Base, b.Base)
|
||||
return strings.Compare(a.Base, b.Base)
|
||||
})
|
||||
return pairs
|
||||
}
|
||||
|
||||
// resolveGoType maps a swagger schema property to a Go type string.
|
||||
func resolveGoType(prop SchemaProperty, defs map[string]SchemaDefinition) string {
|
||||
func resolveGoType(prop SchemaProperty) string {
|
||||
if prop.Ref != "" {
|
||||
return refGoType(prop.Ref, defs)
|
||||
parts := strings.Split(prop.Ref, "/")
|
||||
return "*" + parts[len(parts)-1]
|
||||
}
|
||||
switch prop.Type {
|
||||
case "string":
|
||||
|
|
@ -279,74 +182,33 @@ func resolveGoType(prop SchemaProperty, defs map[string]SchemaDefinition) string
|
|||
return "bool"
|
||||
case "array":
|
||||
if prop.Items != nil {
|
||||
return "[]" + resolveGoType(*prop.Items, defs)
|
||||
return "[]" + resolveGoType(*prop.Items)
|
||||
}
|
||||
return "[]any"
|
||||
case "object":
|
||||
return resolveMapType(prop, defs)
|
||||
return "map[string]any"
|
||||
default:
|
||||
return "any"
|
||||
}
|
||||
}
|
||||
|
||||
// resolveMapType maps a swagger object with additionalProperties to a Go map type.
|
||||
func resolveMapType(prop SchemaProperty, defs map[string]SchemaDefinition) string {
|
||||
valueType := "any"
|
||||
if prop.AdditionalProperties != nil {
|
||||
valueType = resolveGoType(*prop.AdditionalProperties, defs)
|
||||
}
|
||||
return "map[string]" + valueType
|
||||
}
|
||||
|
||||
func refName(ref string) string {
|
||||
parts := core.Split(ref, "/")
|
||||
return parts[len(parts)-1]
|
||||
}
|
||||
|
||||
func refGoType(ref string, defs map[string]SchemaDefinition) string {
|
||||
name := refName(ref)
|
||||
def, ok := defs[name]
|
||||
if !ok {
|
||||
return "*" + name
|
||||
}
|
||||
if definitionNeedsPointer(def) {
|
||||
return "*" + name
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
||||
func definitionNeedsPointer(def SchemaDefinition) bool {
|
||||
if len(def.Enum) > 0 {
|
||||
return false
|
||||
}
|
||||
if def.Ref != "" {
|
||||
return false
|
||||
}
|
||||
switch def.Type {
|
||||
case "string", "integer", "number", "boolean", "array":
|
||||
return false
|
||||
case "object":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// pascalCase converts a snake_case or kebab-case string to PascalCase,
|
||||
// with common acronyms kept uppercase.
|
||||
func pascalCase(s string) string {
|
||||
var parts []string
|
||||
for _, p := range splitSnakeKebab(s) {
|
||||
for p := range strings.FieldsFuncSeq(s, func(r rune) bool {
|
||||
return r == '_' || r == '-'
|
||||
}) {
|
||||
if len(p) == 0 {
|
||||
continue
|
||||
}
|
||||
upper := core.Upper(p)
|
||||
upper := strings.ToUpper(p)
|
||||
switch upper {
|
||||
case "ID", "URL", "HTML", "SSH", "HTTP", "HTTPS", "API", "URI", "GPG", "IP", "CSS", "JS":
|
||||
parts = append(parts, upper)
|
||||
default:
|
||||
parts = append(parts, core.Concat(core.Upper(p[:1]), p[1:]))
|
||||
parts = append(parts, strings.ToUpper(p[:1])+p[1:])
|
||||
}
|
||||
}
|
||||
return core.Concat(parts...)
|
||||
return strings.Join(parts, "")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestParser_LoadSpec_Good(t *testing.T) {
|
||||
func TestParser_Good_LoadSpec(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -17,7 +17,7 @@ func TestParser_LoadSpec_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParser_ExtractTypes_Good(t *testing.T) {
|
||||
func TestParser_Good_ExtractTypes(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -38,7 +38,7 @@ func TestParser_ExtractTypes_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestParser_FieldTypes_Good(t *testing.T) {
|
||||
func TestParser_Good_FieldTypes(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -70,15 +70,11 @@ func TestParser_FieldTypes_Good(t *testing.T) {
|
|||
if f.GoType != "*User" {
|
||||
t.Errorf("owner: got %q, want *User", f.GoType)
|
||||
}
|
||||
case "units_map":
|
||||
if f.GoType != "map[string]string" {
|
||||
t.Errorf("units_map: got %q, want map[string]string", f.GoType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_DetectCreateEditPairs_Good(t *testing.T) {
|
||||
func TestParser_Good_DetectCreateEditPairs(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -105,65 +101,3 @@ func TestParser_DetectCreateEditPairs_Good(t *testing.T) {
|
|||
t.Fatal("Repo pair not found")
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_AdditionalPropertiesAlias_Good(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
types := ExtractTypes(spec)
|
||||
alias, ok := types["CreateHookOptionConfig"]
|
||||
if !ok {
|
||||
t.Fatal("CreateHookOptionConfig type not found")
|
||||
}
|
||||
if !alias.IsAlias {
|
||||
t.Fatal("expected CreateHookOptionConfig to be emitted as an alias")
|
||||
}
|
||||
if alias.AliasType != "map[string]any" {
|
||||
t.Fatalf("got alias type %q, want map[string]any", alias.AliasType)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParser_PrimitiveAndCollectionAliases_Good(t *testing.T) {
|
||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
types := ExtractTypes(spec)
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
wantType string
|
||||
}{
|
||||
{name: "CommitStatusState", wantType: "string"},
|
||||
{name: "IssueFormFieldType", wantType: "string"},
|
||||
{name: "IssueFormFieldVisible", wantType: "string"},
|
||||
{name: "NotifySubjectType", wantType: "string"},
|
||||
{name: "ReviewStateType", wantType: "string"},
|
||||
{name: "StateType", wantType: "string"},
|
||||
{name: "TimeStamp", wantType: "int64"},
|
||||
{name: "IssueTemplateLabels", wantType: "[]string"},
|
||||
{name: "QuotaGroupList", wantType: "[]*QuotaGroup"},
|
||||
{name: "QuotaUsedArtifactList", wantType: "[]*QuotaUsedArtifact"},
|
||||
{name: "QuotaUsedAttachmentList", wantType: "[]*QuotaUsedAttachment"},
|
||||
{name: "QuotaUsedPackageList", wantType: "[]*QuotaUsedPackage"},
|
||||
{name: "CreatePullReviewCommentOptions", wantType: "CreatePullReviewComment"},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
gt, ok := types[tc.name]
|
||||
if !ok {
|
||||
t.Fatalf("type %q not found", tc.name)
|
||||
}
|
||||
if !gt.IsAlias {
|
||||
t.Fatalf("type %q should be emitted as an alias", tc.name)
|
||||
}
|
||||
if gt.AliasType != tc.wantType {
|
||||
t.Fatalf("type %q: got alias %q, want %q", tc.name, gt.AliasType, tc.wantType)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
159
commits.go
159
commits.go
|
|
@ -2,8 +2,8 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
|
@ -12,71 +12,10 @@ import (
|
|||
// and git notes.
|
||||
// No Resource embedding — collection and item commit paths differ, and the
|
||||
// remaining endpoints are heterogeneous across status and note paths.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Commits.GetCombinedStatus(ctx, "core", "go-forge", "main")
|
||||
type CommitService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// CommitListOptions controls filtering for repository commit listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// stat := false
|
||||
// opts := forge.CommitListOptions{Sha: "main", Stat: &stat}
|
||||
type CommitListOptions struct {
|
||||
Sha string
|
||||
Path string
|
||||
Stat *bool
|
||||
Verification *bool
|
||||
Files *bool
|
||||
Not string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the commit list filters.
|
||||
func (o CommitListOptions) String() string {
|
||||
return optionString("forge.CommitListOptions",
|
||||
"sha", o.Sha,
|
||||
"path", o.Path,
|
||||
"stat", o.Stat,
|
||||
"verification", o.Verification,
|
||||
"files", o.Files,
|
||||
"not", o.Not,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the commit list filters.
|
||||
func (o CommitListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o CommitListOptions) queryParams() map[string]string {
|
||||
query := make(map[string]string, 6)
|
||||
if o.Sha != "" {
|
||||
query["sha"] = o.Sha
|
||||
}
|
||||
if o.Path != "" {
|
||||
query["path"] = o.Path
|
||||
}
|
||||
if o.Stat != nil {
|
||||
query["stat"] = strconv.FormatBool(*o.Stat)
|
||||
}
|
||||
if o.Verification != nil {
|
||||
query["verification"] = strconv.FormatBool(*o.Verification)
|
||||
}
|
||||
if o.Files != nil {
|
||||
query["files"] = strconv.FormatBool(*o.Files)
|
||||
}
|
||||
if o.Not != "" {
|
||||
query["not"] = o.Not
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
const (
|
||||
commitCollectionPath = "/api/v1/repos/{owner}/{repo}/commits"
|
||||
commitItemPath = "/api/v1/repos/{owner}/{repo}/git/commits/{sha}"
|
||||
|
|
@ -87,18 +26,18 @@ func newCommitService(c *Client) *CommitService {
|
|||
}
|
||||
|
||||
// List returns a single page of commits for a repository.
|
||||
func (s *CommitService) List(ctx context.Context, params Params, opts ListOptions, filters ...CommitListOptions) (*PagedResult[types.Commit], error) {
|
||||
return ListPage[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), commitListQuery(filters...), opts)
|
||||
func (s *CommitService) List(ctx context.Context, params Params, opts ListOptions) (*PagedResult[types.Commit], error) {
|
||||
return ListPage[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil, opts)
|
||||
}
|
||||
|
||||
// ListAll returns all commits for a repository.
|
||||
func (s *CommitService) ListAll(ctx context.Context, params Params, filters ...CommitListOptions) ([]types.Commit, error) {
|
||||
return ListAll[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), commitListQuery(filters...))
|
||||
func (s *CommitService) ListAll(ctx context.Context, params Params) ([]types.Commit, error) {
|
||||
return ListAll[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil)
|
||||
}
|
||||
|
||||
// Iter returns an iterator over all commits for a repository.
|
||||
func (s *CommitService) Iter(ctx context.Context, params Params, filters ...CommitListOptions) iter.Seq2[types.Commit, error] {
|
||||
return ListIter[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), commitListQuery(filters...))
|
||||
func (s *CommitService) Iter(ctx context.Context, params Params) iter.Seq2[types.Commit, error] {
|
||||
return ListIter[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil)
|
||||
}
|
||||
|
||||
// Get returns a single commit by SHA or ref.
|
||||
|
|
@ -110,40 +49,9 @@ func (s *CommitService) Get(ctx context.Context, params Params) (*types.Commit,
|
|||
return &out, nil
|
||||
}
|
||||
|
||||
// GetDiffOrPatch returns a commit diff or patch as raw bytes.
|
||||
func (s *CommitService) GetDiffOrPatch(ctx context.Context, owner, repo, sha, diffType string) ([]byte, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/commits/{sha}.{diffType}", pathParams("owner", owner, "repo", repo, "sha", sha, "diffType", diffType))
|
||||
return s.client.GetRaw(ctx, path)
|
||||
}
|
||||
|
||||
// GetPullRequest returns the pull request associated with a commit SHA.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Commits.GetPullRequest(ctx, "core", "go-forge", "abc123")
|
||||
func (s *CommitService) GetPullRequest(ctx context.Context, owner, repo, sha string) (*types.PullRequest, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/commits/{sha}/pull", pathParams("owner", owner, "repo", repo, "sha", sha))
|
||||
var out types.PullRequest
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetCombinedStatus returns the combined status for a given ref (branch, tag, or SHA).
|
||||
func (s *CommitService) GetCombinedStatus(ctx context.Context, owner, repo, ref string) (*types.CombinedStatus, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/statuses/{ref}", pathParams("owner", owner, "repo", repo, "ref", ref))
|
||||
var out types.CombinedStatus
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetCombinedStatusByRef returns the combined status for a given commit reference.
|
||||
func (s *CommitService) GetCombinedStatusByRef(ctx context.Context, owner, repo, ref string) (*types.CombinedStatus, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/commits/{ref}/status", pathParams("owner", owner, "repo", repo, "ref", ref))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", owner, repo, ref)
|
||||
var out types.CombinedStatus
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -153,7 +61,7 @@ func (s *CommitService) GetCombinedStatusByRef(ctx context.Context, owner, repo,
|
|||
|
||||
// ListStatuses returns all commit statuses for a given ref.
|
||||
func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref string) ([]types.CommitStatus, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/commits/{ref}/statuses", pathParams("owner", owner, "repo", repo, "ref", ref))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/commits/%s/statuses", owner, repo, ref)
|
||||
var out []types.CommitStatus
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -161,25 +69,9 @@ func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref strin
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// IterStatuses returns an iterator over all commit statuses for a given ref.
|
||||
func (s *CommitService) IterStatuses(ctx context.Context, owner, repo, ref string) iter.Seq2[types.CommitStatus, error] {
|
||||
return func(yield func(types.CommitStatus, error) bool) {
|
||||
statuses, err := s.ListStatuses(ctx, owner, repo, ref)
|
||||
if err != nil {
|
||||
yield(*new(types.CommitStatus), err)
|
||||
return
|
||||
}
|
||||
for _, status := range statuses {
|
||||
if !yield(status, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CreateStatus creates a new commit status for the given SHA.
|
||||
func (s *CommitService) CreateStatus(ctx context.Context, owner, repo, sha string, opts *types.CreateStatusOption) (*types.CommitStatus, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/statuses/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", owner, repo, sha)
|
||||
var out types.CommitStatus
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -189,39 +81,10 @@ func (s *CommitService) CreateStatus(ctx context.Context, owner, repo, sha strin
|
|||
|
||||
// GetNote returns the git note for a given commit SHA.
|
||||
func (s *CommitService) GetNote(ctx context.Context, owner, repo, sha string) (*types.Note, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/notes/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/git/notes/%s", owner, repo, sha)
|
||||
var out types.Note
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// SetNote creates or updates the git note for a given commit SHA.
|
||||
func (s *CommitService) SetNote(ctx context.Context, owner, repo, sha, message string) (*types.Note, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/notes/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha))
|
||||
var out types.Note
|
||||
if err := s.client.Post(ctx, path, types.NoteOptions{Message: message}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteNote removes the git note for a given commit SHA.
|
||||
func (s *CommitService) DeleteNote(ctx context.Context, owner, repo, sha string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/notes/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
func commitListQuery(filters ...CommitListOptions) map[string]string {
|
||||
query := make(map[string]string, len(filters))
|
||||
for _, filter := range filters {
|
||||
for key, value := range filter.queryParams() {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestCommitService_GetCombinedStatusByRef_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/commits/main/status" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.CombinedStatus{
|
||||
SHA: "main",
|
||||
TotalCount: 3,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
status, err := f.Commits.GetCombinedStatusByRef(context.Background(), "core", "go-forge", "main")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if status.SHA != "main" || status.TotalCount != 3 {
|
||||
t.Fatalf("got %#v", status)
|
||||
}
|
||||
}
|
||||
212
commits_test.go
212
commits_test.go
|
|
@ -2,8 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"io"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -11,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestCommitService_List_Good(t *testing.T) {
|
||||
func TestCommitService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -62,56 +61,7 @@ func TestCommitService_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCommitService_ListFiltered_Good(t *testing.T) {
|
||||
stat := false
|
||||
verification := false
|
||||
files := false
|
||||
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/commits" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
want := map[string]string{
|
||||
"sha": "main",
|
||||
"path": "docs",
|
||||
"stat": "false",
|
||||
"verification": "false",
|
||||
"files": "false",
|
||||
"not": "deadbeef",
|
||||
"page": "1",
|
||||
"limit": "50",
|
||||
}
|
||||
for key, wantValue := range want {
|
||||
if got := r.URL.Query().Get(key); got != wantValue {
|
||||
t.Errorf("got %s=%q, want %q", key, got, wantValue)
|
||||
}
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Commit{{SHA: "abc123"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
commits, err := f.Commits.ListAll(context.Background(), Params{"owner": "core", "repo": "go-forge"}, CommitListOptions{
|
||||
Sha: "main",
|
||||
Path: "docs",
|
||||
Stat: &stat,
|
||||
Verification: &verification,
|
||||
Files: &files,
|
||||
Not: "deadbeef",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(commits) != 1 || commits[0].SHA != "abc123" {
|
||||
t.Fatalf("got %#v", commits)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitService_Get_Good(t *testing.T) {
|
||||
func TestCommitService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -145,65 +95,7 @@ func TestCommitService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCommitService_GetDiffOrPatch_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/git/commits/abc123.diff" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/plain")
|
||||
io.WriteString(w, "diff --git a/README.md b/README.md")
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
data, err := f.Commits.GetDiffOrPatch(context.Background(), "core", "go-forge", "abc123", "diff")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(data) != "diff --git a/README.md b/README.md" {
|
||||
t.Fatalf("got body=%q", string(data))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitService_GetPullRequest_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/commits/abc123/pull" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.PullRequest{
|
||||
ID: 17,
|
||||
Index: 9,
|
||||
Title: "Add commit-linked pull request",
|
||||
Head: &types.PRBranchInfo{
|
||||
Ref: "feature/commit-link",
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
pr, err := f.Commits.GetPullRequest(context.Background(), "core", "go-forge", "abc123")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pr.ID != 17 {
|
||||
t.Errorf("got id=%d, want 17", pr.ID)
|
||||
}
|
||||
if pr.Index != 9 {
|
||||
t.Errorf("got index=%d, want 9", pr.Index)
|
||||
}
|
||||
if pr.Head == nil || pr.Head.Ref != "feature/commit-link" {
|
||||
t.Fatalf("unexpected head branch info: %+v", pr.Head)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitService_ListStatuses_Good(t *testing.T) {
|
||||
func TestCommitService_Good_ListStatuses(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -234,40 +126,7 @@ func TestCommitService_ListStatuses_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCommitService_IterStatuses_Good(t *testing.T) {
|
||||
var requests int
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/commits/abc123/statuses" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.CommitStatus{
|
||||
{ID: 1, Context: "ci/build"},
|
||||
{ID: 2, Context: "ci/test"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []string
|
||||
for status, err := range f.Commits.IterStatuses(context.Background(), "core", "go-forge", "abc123") {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, status.Context)
|
||||
}
|
||||
if requests != 1 {
|
||||
t.Fatalf("expected 1 request, got %d", requests)
|
||||
}
|
||||
if len(got) != 2 || got[0] != "ci/build" || got[1] != "ci/test" {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitService_CreateStatus_Good(t *testing.T) {
|
||||
func TestCommitService_Good_CreateStatus(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -310,7 +169,7 @@ func TestCommitService_CreateStatus_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCommitService_GetNote_Good(t *testing.T) {
|
||||
func TestCommitService_Good_GetNote(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -340,62 +199,7 @@ func TestCommitService_GetNote_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCommitService_SetNote_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/git/notes/abc123" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.NoteOptions
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Message != "reviewed and approved" {
|
||||
t.Errorf("got message=%q, want %q", opts.Message, "reviewed and approved")
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Note{
|
||||
Message: "reviewed and approved",
|
||||
Commit: &types.Commit{
|
||||
SHA: "abc123",
|
||||
},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
note, err := f.Commits.SetNote(context.Background(), "core", "go-forge", "abc123", "reviewed and approved")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if note.Message != "reviewed and approved" {
|
||||
t.Errorf("got message=%q, want %q", note.Message, "reviewed and approved")
|
||||
}
|
||||
if note.Commit.SHA != "abc123" {
|
||||
t.Errorf("got commit sha=%q, want %q", note.Commit.SHA, "abc123")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitService_DeleteNote_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/git/notes/abc123" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Commits.DeleteNote(context.Background(), "core", "go-forge", "abc123"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommitService_GetCombinedStatus_Good(t *testing.T) {
|
||||
func TestCommitService_Good_GetCombinedStatus(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -430,7 +234,7 @@ func TestCommitService_GetCombinedStatus_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCommitService_NotFound_Bad(t *testing.T) {
|
||||
func TestCommitService_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
||||
|
|
|
|||
122
config.go
122
config.go
|
|
@ -1,108 +1,26 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreio "dappco.re/go/core/io"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultURL is the fallback Forgejo instance URL when neither flag nor
|
||||
// environment variable is set.
|
||||
//
|
||||
// Usage:
|
||||
// cfgURL, _, _ := forge.ResolveConfig("", "")
|
||||
// _ = cfgURL == forge.DefaultURL
|
||||
DefaultURL = "http://localhost:3000"
|
||||
)
|
||||
|
||||
const defaultConfigPath = ".config/forge/config.json"
|
||||
|
||||
type configFile struct {
|
||||
URL string `json:"url"`
|
||||
Token string `json:"token"`
|
||||
}
|
||||
|
||||
// ConfigPath returns the default config file path used by SaveConfig and
|
||||
// ResolveConfig.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// path, err := forge.ConfigPath()
|
||||
// _ = path
|
||||
func ConfigPath() (string, error) {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return "", core.E("ConfigPath", "forge: resolve home directory", err)
|
||||
}
|
||||
return filepath.Join(home, defaultConfigPath), nil
|
||||
}
|
||||
|
||||
func readConfigFile() (url, token string, err error) {
|
||||
path, err := ConfigPath()
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
}
|
||||
|
||||
data, err := coreio.Local.Read(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return "", "", nil
|
||||
}
|
||||
return "", "", core.E("ResolveConfig", "forge: read config file", err)
|
||||
}
|
||||
|
||||
var cfg configFile
|
||||
if err := json.Unmarshal([]byte(data), &cfg); err != nil {
|
||||
return "", "", core.E("ResolveConfig", "forge: decode config file", err)
|
||||
}
|
||||
return cfg.URL, cfg.Token, nil
|
||||
}
|
||||
|
||||
// SaveConfig persists the Forgejo URL and API token to the default config file.
|
||||
// It creates the parent directory if it does not already exist.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = forge.SaveConfig("https://forge.example.com", "token")
|
||||
func SaveConfig(url, token string) error {
|
||||
path, err := ConfigPath()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := coreio.Local.EnsureDir(filepath.Dir(path)); err != nil {
|
||||
return core.E("SaveConfig", "forge: create config directory", err)
|
||||
}
|
||||
payload, err := json.MarshalIndent(configFile{URL: url, Token: token}, "", " ")
|
||||
if err != nil {
|
||||
return core.E("SaveConfig", "forge: encode config file", err)
|
||||
}
|
||||
return coreio.Local.WriteMode(path, string(payload), 0600)
|
||||
}
|
||||
|
||||
// ResolveConfig resolves the Forgejo URL and API token from flags, environment
|
||||
// variables, config file, and built-in defaults. Priority order:
|
||||
// flags > env > config file > defaults.
|
||||
// variables, and built-in defaults. Priority order: flags > env > defaults.
|
||||
//
|
||||
// Environment variables:
|
||||
// - FORGE_URL — base URL of the Forgejo instance
|
||||
// - FORGE_TOKEN — API token for authentication
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// url, token, err := forge.ResolveConfig("", "")
|
||||
// _ = url
|
||||
// _ = token
|
||||
func ResolveConfig(flagURL, flagToken string) (url, token string, err error) {
|
||||
if envURL, ok := os.LookupEnv("FORGE_URL"); ok && envURL != "" {
|
||||
url = envURL
|
||||
}
|
||||
if envToken, ok := os.LookupEnv("FORGE_TOKEN"); ok && envToken != "" {
|
||||
token = envToken
|
||||
}
|
||||
url = os.Getenv("FORGE_URL")
|
||||
token = os.Getenv("FORGE_TOKEN")
|
||||
|
||||
if flagURL != "" {
|
||||
url = flagURL
|
||||
|
|
@ -110,49 +28,21 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) {
|
|||
if flagToken != "" {
|
||||
token = flagToken
|
||||
}
|
||||
if url == "" || token == "" {
|
||||
fileURL, fileToken, fileErr := readConfigFile()
|
||||
if fileErr != nil {
|
||||
return "", "", fileErr
|
||||
}
|
||||
if url == "" {
|
||||
url = fileURL
|
||||
}
|
||||
if token == "" {
|
||||
token = fileToken
|
||||
}
|
||||
}
|
||||
if url == "" {
|
||||
url = DefaultURL
|
||||
}
|
||||
return url, token, nil
|
||||
}
|
||||
|
||||
// NewFromConfig creates a new Forge client using resolved configuration.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f, err := forge.NewFromConfig("", "")
|
||||
// _ = f
|
||||
func NewFromConfig(flagURL, flagToken string, opts ...Option) (*Forge, error) {
|
||||
return NewForgeFromConfig(flagURL, flagToken, opts...)
|
||||
}
|
||||
|
||||
// NewForgeFromConfig creates a new Forge client using resolved configuration.
|
||||
// It returns an error if no API token is available from flags, environment,
|
||||
// or the saved config file.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f, err := forge.NewForgeFromConfig("", "")
|
||||
// _ = f
|
||||
// It returns an error if no API token is available from flags or environment.
|
||||
func NewForgeFromConfig(flagURL, flagToken string, opts ...Option) (*Forge, error) {
|
||||
url, token, err := ResolveConfig(flagURL, flagToken)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if token == "" {
|
||||
return nil, core.E("NewForgeFromConfig", "forge: no API token configured (set FORGE_TOKEN or pass --token)", nil)
|
||||
return nil, coreerr.E("NewForgeFromConfig", "forge: no API token configured (set FORGE_TOKEN or pass --token)", nil)
|
||||
}
|
||||
return NewForge(url, token, opts...), nil
|
||||
}
|
||||
|
|
|
|||
162
config_test.go
162
config_test.go
|
|
@ -1,15 +1,11 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path/filepath"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
coreio "dappco.re/go/core/io"
|
||||
)
|
||||
|
||||
func TestResolveConfig_EnvOverrides_Good(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
func TestResolveConfig_Good_EnvOverrides(t *testing.T) {
|
||||
t.Setenv("FORGE_URL", "https://forge.example.com")
|
||||
t.Setenv("FORGE_TOKEN", "env-token")
|
||||
|
||||
|
|
@ -25,8 +21,7 @@ func TestResolveConfig_EnvOverrides_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveConfig_FlagOverridesEnv_Good(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
func TestResolveConfig_Good_FlagOverridesEnv(t *testing.T) {
|
||||
t.Setenv("FORGE_URL", "https://env.example.com")
|
||||
t.Setenv("FORGE_TOKEN", "env-token")
|
||||
|
||||
|
|
@ -42,10 +37,9 @@ func TestResolveConfig_FlagOverridesEnv_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveConfig_DefaultURL_Good(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
t.Setenv("FORGE_URL", "")
|
||||
t.Setenv("FORGE_TOKEN", "")
|
||||
func TestResolveConfig_Good_DefaultURL(t *testing.T) {
|
||||
os.Unsetenv("FORGE_URL")
|
||||
os.Unsetenv("FORGE_TOKEN")
|
||||
|
||||
url, _, err := ResolveConfig("", "")
|
||||
if err != nil {
|
||||
|
|
@ -56,150 +50,12 @@ func TestResolveConfig_DefaultURL_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolveConfig_ConfigFile_Good(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("FORGE_URL", "")
|
||||
t.Setenv("FORGE_TOKEN", "")
|
||||
|
||||
cfgPath := filepath.Join(home, ".config", "forge", "config.json")
|
||||
if err := coreio.Local.EnsureDir(filepath.Dir(cfgPath)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := json.Marshal(map[string]string{
|
||||
"url": "https://file.example.com",
|
||||
"token": "file-token",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := coreio.Local.WriteMode(cfgPath, string(data), 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
url, token, err := ResolveConfig("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if url != "https://file.example.com" {
|
||||
t.Errorf("got url=%q", url)
|
||||
}
|
||||
if token != "file-token" {
|
||||
t.Errorf("got token=%q", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveConfig_EnvOverridesConfig_Good(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("FORGE_URL", "https://env.example.com")
|
||||
t.Setenv("FORGE_TOKEN", "env-token")
|
||||
|
||||
if err := SaveConfig("https://file.example.com", "file-token"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
url, token, err := ResolveConfig("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if url != "https://env.example.com" {
|
||||
t.Errorf("got url=%q", url)
|
||||
}
|
||||
if token != "env-token" {
|
||||
t.Errorf("got token=%q", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveConfig_FlagOverridesBrokenConfig_Good(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
t.Setenv("FORGE_URL", "")
|
||||
t.Setenv("FORGE_TOKEN", "")
|
||||
|
||||
cfgPath := filepath.Join(home, ".config", "forge", "config.json")
|
||||
if err := coreio.Local.EnsureDir(filepath.Dir(cfgPath)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := coreio.Local.WriteMode(cfgPath, "{not-json", 0600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
url, token, err := ResolveConfig("https://flag.example.com", "flag-token")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if url != "https://flag.example.com" {
|
||||
t.Errorf("got url=%q", url)
|
||||
}
|
||||
if token != "flag-token" {
|
||||
t.Errorf("got token=%q", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewForgeFromConfig_NoToken_Bad(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
t.Setenv("FORGE_URL", "")
|
||||
t.Setenv("FORGE_TOKEN", "")
|
||||
func TestNewForgeFromConfig_Bad_NoToken(t *testing.T) {
|
||||
os.Unsetenv("FORGE_URL")
|
||||
os.Unsetenv("FORGE_TOKEN")
|
||||
|
||||
_, err := NewForgeFromConfig("", "")
|
||||
if err == nil {
|
||||
t.Fatal("expected error for missing token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewFromConfig_Good(t *testing.T) {
|
||||
t.Setenv("HOME", t.TempDir())
|
||||
t.Setenv("FORGE_URL", "https://forge.example.com")
|
||||
t.Setenv("FORGE_TOKEN", "env-token")
|
||||
|
||||
f, err := NewFromConfig("", "")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if f == nil {
|
||||
t.Fatal("expected forge client")
|
||||
}
|
||||
if got := f.BaseURL(); got != "https://forge.example.com" {
|
||||
t.Errorf("got baseURL=%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSaveConfig_Good(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
|
||||
if err := SaveConfig("https://file.example.com", "file-token"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cfgPath := filepath.Join(home, ".config", "forge", "config.json")
|
||||
data, err := coreio.Local.Read(cfgPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var cfg map[string]string
|
||||
if err := json.Unmarshal([]byte(data), &cfg); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if cfg["url"] != "https://file.example.com" {
|
||||
t.Errorf("got url=%q", cfg["url"])
|
||||
}
|
||||
if cfg["token"] != "file-token" {
|
||||
t.Errorf("got token=%q", cfg["token"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigPath_Good(t *testing.T) {
|
||||
home := t.TempDir()
|
||||
t.Setenv("HOME", home)
|
||||
|
||||
got, err := ConfigPath()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := filepath.Join(home, ".config", "forge", "config.json")
|
||||
if got != want {
|
||||
t.Fatalf("got path=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
58
contents.go
58
contents.go
|
|
@ -2,20 +2,13 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"net/url"
|
||||
"fmt"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// ContentService handles file read/write operations via the Forgejo API.
|
||||
// No Resource embedding — paths vary by operation.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Contents.GetFile(ctx, "core", "go-forge", "README.md")
|
||||
type ContentService struct {
|
||||
client *Client
|
||||
}
|
||||
|
|
@ -24,48 +17,9 @@ func newContentService(c *Client) *ContentService {
|
|||
return &ContentService{client: c}
|
||||
}
|
||||
|
||||
// ListContents returns the entries in a repository directory.
|
||||
// If ref is non-empty, the listing is resolved against that branch, tag, or commit.
|
||||
func (s *ContentService) ListContents(ctx context.Context, owner, repo, ref string) ([]types.ContentsResponse, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents", pathParams("owner", owner, "repo", repo))
|
||||
if ref != "" {
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, core.E("ContentService.ListContents", "forge: parse path", err)
|
||||
}
|
||||
q := u.Query()
|
||||
q.Set("ref", ref)
|
||||
u.RawQuery = q.Encode()
|
||||
path = u.String()
|
||||
}
|
||||
|
||||
var out []types.ContentsResponse
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// IterContents returns an iterator over the entries in a repository directory.
|
||||
// If ref is non-empty, the listing is resolved against that branch, tag, or commit.
|
||||
func (s *ContentService) IterContents(ctx context.Context, owner, repo, ref string) iter.Seq2[types.ContentsResponse, error] {
|
||||
return func(yield func(types.ContentsResponse, error) bool) {
|
||||
items, err := s.ListContents(ctx, owner, repo, ref)
|
||||
if err != nil {
|
||||
yield(*new(types.ContentsResponse), err)
|
||||
return
|
||||
}
|
||||
for _, item := range items {
|
||||
if !yield(item, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetFile returns metadata and content for a file in a repository.
|
||||
func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath string) (*types.ContentsResponse, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
|
||||
var out types.ContentsResponse
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -75,7 +29,7 @@ func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath stri
|
|||
|
||||
// CreateFile creates a new file in a repository.
|
||||
func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath string, opts *types.CreateFileOptions) (*types.FileResponse, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
|
||||
var out types.FileResponse
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -85,7 +39,7 @@ func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath s
|
|||
|
||||
// UpdateFile updates an existing file in a repository.
|
||||
func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath string, opts *types.UpdateFileOptions) (*types.FileResponse, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
|
||||
var out types.FileResponse
|
||||
if err := s.client.Put(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -95,12 +49,12 @@ func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath s
|
|||
|
||||
// DeleteFile deletes a file from a repository. Uses DELETE with a JSON body.
|
||||
func (s *ContentService) DeleteFile(ctx context.Context, owner, repo, filepath string, opts *types.DeleteFileOptions) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
|
||||
return s.client.DeleteWithBody(ctx, path, opts)
|
||||
}
|
||||
|
||||
// GetRawFile returns the raw file content as bytes.
|
||||
func (s *ContentService) GetRawFile(ctx context.Context, owner, repo, filepath string) ([]byte, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/raw/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/raw/%s", owner, repo, filepath)
|
||||
return s.client.GetRaw(ctx, path)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -10,70 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestContentService_ListContents_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/contents" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("ref"); got != "main" {
|
||||
t.Errorf("got ref=%q, want %q", got, "main")
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.ContentsResponse{
|
||||
{Name: "README.md", Path: "README.md", Type: "file"},
|
||||
{Name: "docs", Path: "docs", Type: "dir"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
items, err := f.Contents.ListContents(context.Background(), "core", "go-forge", "main")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(items) != 2 {
|
||||
t.Fatalf("got %d items, want 2", len(items))
|
||||
}
|
||||
if items[0].Name != "README.md" || items[1].Type != "dir" {
|
||||
t.Fatalf("unexpected results: %+v", items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContentService_IterContents_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/contents" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.ContentsResponse{
|
||||
{Name: "README.md", Path: "README.md", Type: "file"},
|
||||
{Name: "docs", Path: "docs", Type: "dir"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []string
|
||||
for item, err := range f.Contents.IterContents(context.Background(), "core", "go-forge", "") {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, item.Name)
|
||||
}
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("got %d items, want 2", len(got))
|
||||
}
|
||||
if got[0] != "README.md" || got[1] != "docs" {
|
||||
t.Fatalf("unexpected items: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestContentService_GetFile_Good(t *testing.T) {
|
||||
func TestContentService_Good_GetFile(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -112,7 +49,7 @@ func TestContentService_GetFile_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContentService_CreateFile_Good(t *testing.T) {
|
||||
func TestContentService_Good_CreateFile(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -161,7 +98,7 @@ func TestContentService_CreateFile_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContentService_UpdateFile_Good(t *testing.T) {
|
||||
func TestContentService_Good_UpdateFile(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
|
|
@ -201,7 +138,7 @@ func TestContentService_UpdateFile_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContentService_DeleteFile_Good(t *testing.T) {
|
||||
func TestContentService_Good_DeleteFile(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -231,7 +168,7 @@ func TestContentService_DeleteFile_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContentService_GetRawFile_Good(t *testing.T) {
|
||||
func TestContentService_Good_GetRawFile(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -255,7 +192,7 @@ func TestContentService_GetRawFile_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContentService_NotFound_Bad(t *testing.T) {
|
||||
func TestContentService_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "file not found"})
|
||||
|
|
@ -272,7 +209,7 @@ func TestContentService_NotFound_Bad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestContentService_GetRawNotFound_Bad(t *testing.T) {
|
||||
func TestContentService_Bad_GetRawNotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "file not found"})
|
||||
|
|
|
|||
3
doc.go
3
doc.go
|
|
@ -2,9 +2,8 @@
|
|||
//
|
||||
// Usage:
|
||||
//
|
||||
// ctx := context.Background()
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "your-token")
|
||||
// repos, err := f.Repos.ListOrgRepos(ctx, "core")
|
||||
// repos, err := f.Repos.List(ctx, forge.Params{"org": "core"}, forge.DefaultList)
|
||||
//
|
||||
// Types are generated from Forgejo's swagger.v1.json spec via cmd/forgegen/.
|
||||
// Run `go generate ./types/...` to regenerate after a Forgejo upgrade.
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| Kind | Name | Signature | Description | Test Coverage |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| type | APIError | `type APIError struct` | APIError represents an error response from the Forgejo API. | `TestAPIError_Good_Error`, `TestClient_Bad_ServerError`, `TestIsConflict_Bad_NotConflict` (+2 more) |
|
||||
| type | ActionsService | `type ActionsService struct` | ActionsService handles CI/CD actions operations across repositories and organisations — secrets, variables, workflow dispatches, and tasks. No Resource embedding — heterogeneous endpoints across repo and org levels. | `TestActionsService_Bad_NotFound`, `TestActionsService_Good_CreateRepoSecret`, `TestActionsService_Good_CreateRepoVariable` (+9 more) |
|
||||
| type | ActionsService | `type ActionsService struct` | ActionsService handles CI/CD actions operations across repositories and organisations — secrets, variables, and workflow dispatches. No Resource embedding — heterogeneous endpoints across repo and org levels. | `TestActionsService_Bad_NotFound`, `TestActionsService_Good_CreateRepoSecret`, `TestActionsService_Good_CreateRepoVariable` (+7 more) |
|
||||
| type | AdminService | `type AdminService struct` | AdminService handles site administration operations. Unlike other services, AdminService does not embed Resource[T,C,U] because admin endpoints are heterogeneous. | `TestAdminService_Bad_CreateUser_Forbidden`, `TestAdminService_Bad_DeleteUser_NotFound`, `TestAdminService_Good_AdoptRepo` (+9 more) |
|
||||
| type | BranchService | `type BranchService struct` | BranchService handles branch operations within a repository. | `TestBranchService_Good_CreateProtection`, `TestBranchService_Good_Get`, `TestBranchService_Good_List` |
|
||||
| type | Client | `type Client struct` | Client is a low-level HTTP client for the Forgejo API. | `TestClient_Bad_Conflict`, `TestClient_Bad_Forbidden`, `TestClient_Bad_NotFound` (+9 more) |
|
||||
|
|
@ -34,7 +34,7 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| type | Resource | `type Resource[T any, C any, U any] struct` | Resource provides generic CRUD operations for a Forgejo API resource. T is the resource type, C is the create options type, U is the update options type. | `TestResource_Bad_IterError`, `TestResource_Good_Create`, `TestResource_Good_Delete` (+6 more) |
|
||||
| type | TeamService | `type TeamService struct` | TeamService handles team operations. | `TestTeamService_Good_AddMember`, `TestTeamService_Good_Get`, `TestTeamService_Good_ListMembers` |
|
||||
| type | UserService | `type UserService struct` | UserService handles user operations. | `TestUserService_Good_Get`, `TestUserService_Good_GetCurrent`, `TestUserService_Good_ListFollowers` |
|
||||
| type | WebhookService | `type WebhookService struct` | WebhookService handles webhook (hook) operations for repositories, organisations, and the authenticated user. Embeds Resource for standard CRUD on /api/v1/repos/{owner}/{repo}/hooks/{id}. | `TestWebhookService_Bad_NotFound`, `TestWebhookService_Good_Create`, `TestWebhookService_Good_Get` (+8 more) |
|
||||
| type | WebhookService | `type WebhookService struct` | WebhookService handles webhook (hook) operations within a repository. Embeds Resource for standard CRUD on /api/v1/repos/{owner}/{repo}/hooks/{id}. | `TestWebhookService_Bad_NotFound`, `TestWebhookService_Good_Create`, `TestWebhookService_Good_Get` (+3 more) |
|
||||
| type | WikiService | `type WikiService struct` | WikiService handles wiki page operations for a repository. No Resource embedding — custom endpoints for wiki CRUD. | `TestWikiService_Bad_NotFound`, `TestWikiService_Good_CreatePage`, `TestWikiService_Good_DeletePage` (+3 more) |
|
||||
| function | IsConflict | `func IsConflict(err error) bool` | IsConflict returns true if the error is a 409 response. | `TestClient_Bad_Conflict`, `TestIsConflict_Bad_NotConflict`, `TestIsConflict_Good` (+1 more) |
|
||||
| function | IsForbidden | `func IsForbidden(err error) bool` | IsForbidden returns true if the error is a 403 response. | `TestAdminService_Bad_CreateUser_Forbidden`, `TestClient_Bad_Forbidden`, `TestIsForbidden_Bad_NotForbidden` |
|
||||
|
|
@ -56,7 +56,6 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | ActionsService.DeleteRepoSecret | `func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error` | DeleteRepoSecret removes a secret from a repository. | `TestActionsService_Good_DeleteRepoSecret` |
|
||||
| method | ActionsService.DeleteRepoVariable | `func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error` | DeleteRepoVariable removes an action variable from a repository. | `TestActionsService_Good_DeleteRepoVariable` |
|
||||
| method | ActionsService.DispatchWorkflow | `func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error` | DispatchWorkflow triggers a workflow run. | `TestActionsService_Good_DispatchWorkflow` |
|
||||
| method | ActionsService.IterRepoTasks | `func (s *ActionsService) IterRepoTasks(ctx context.Context, owner, repo string) iter.Seq2[types.ActionTask, error]` | IterRepoTasks returns an iterator over all action tasks for a repository. | `TestActionsService_Good_IterRepoTasks` |
|
||||
| method | ActionsService.IterOrgSecrets | `func (s *ActionsService) IterOrgSecrets(ctx context.Context, org string) iter.Seq2[types.Secret, error]` | IterOrgSecrets returns an iterator over all secrets for an organisation. | No direct tests. |
|
||||
| method | ActionsService.IterOrgVariables | `func (s *ActionsService) IterOrgVariables(ctx context.Context, org string) iter.Seq2[types.ActionVariable, error]` | IterOrgVariables returns an iterator over all action variables for an organisation. | No direct tests. |
|
||||
| method | ActionsService.IterRepoSecrets | `func (s *ActionsService) IterRepoSecrets(ctx context.Context, owner, repo string) iter.Seq2[types.Secret, error]` | IterRepoSecrets returns an iterator over all secrets for a repository. | No direct tests. |
|
||||
|
|
@ -64,7 +63,6 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | ActionsService.ListOrgSecrets | `func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string) ([]types.Secret, error)` | ListOrgSecrets returns all secrets for an organisation. | `TestActionsService_Good_ListOrgSecrets` |
|
||||
| method | ActionsService.ListOrgVariables | `func (s *ActionsService) ListOrgVariables(ctx context.Context, org string) ([]types.ActionVariable, error)` | ListOrgVariables returns all action variables for an organisation. | `TestActionsService_Good_ListOrgVariables` |
|
||||
| method | ActionsService.ListRepoSecrets | `func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string) ([]types.Secret, error)` | ListRepoSecrets returns all secrets for a repository. | `TestActionsService_Bad_NotFound`, `TestActionsService_Good_ListRepoSecrets` |
|
||||
| method | ActionsService.ListRepoTasks | `func (s *ActionsService) ListRepoTasks(ctx context.Context, owner, repo string, opts ListOptions) (*types.ActionTaskResponse, error)` | ListRepoTasks returns a single page of action tasks for a repository. | `TestActionsService_Good_ListRepoTasks` |
|
||||
| method | ActionsService.ListRepoVariables | `func (s *ActionsService) ListRepoVariables(ctx context.Context, owner, repo string) ([]types.ActionVariable, error)` | ListRepoVariables returns all action variables for a repository. | `TestActionsService_Good_ListRepoVariables` |
|
||||
| method | AdminService.AdoptRepo | `func (s *AdminService) AdoptRepo(ctx context.Context, owner, repo string) error` | AdoptRepo adopts an unadopted repository (admin only). | `TestAdminService_Good_AdoptRepo` |
|
||||
| method | AdminService.CreateUser | `func (s *AdminService) CreateUser(ctx context.Context, opts *types.CreateUserOption) (*types.User, error)` | CreateUser creates a new user (admin only). | `TestAdminService_Bad_CreateUser_Forbidden`, `TestAdminService_Good_CreateUser` |
|
||||
|
|
@ -72,15 +70,11 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | AdminService.EditUser | `func (s *AdminService) EditUser(ctx context.Context, username string, opts map[string]any) error` | EditUser edits an existing user (admin only). | `TestAdminService_Good_EditUser` |
|
||||
| method | AdminService.GenerateRunnerToken | `func (s *AdminService) GenerateRunnerToken(ctx context.Context) (string, error)` | GenerateRunnerToken generates an actions runner registration token. | `TestAdminService_Good_GenerateRunnerToken` |
|
||||
| method | AdminService.IterCron | `func (s *AdminService) IterCron(ctx context.Context) iter.Seq2[types.Cron, error]` | IterCron returns an iterator over all cron tasks (admin only). | No direct tests. |
|
||||
| method | AdminService.IterEmails | `func (s *AdminService) IterEmails(ctx context.Context) iter.Seq2[types.Email, error]` | IterEmails returns an iterator over all email addresses (admin only). | No direct tests. |
|
||||
| method | AdminService.IterOrgs | `func (s *AdminService) IterOrgs(ctx context.Context) iter.Seq2[types.Organization, error]` | IterOrgs returns an iterator over all organisations (admin only). | No direct tests. |
|
||||
| method | AdminService.IterSearchEmails | `func (s *AdminService) IterSearchEmails(ctx context.Context, q string) iter.Seq2[types.Email, error]` | IterSearchEmails returns an iterator over all email addresses matching a keyword (admin only). | No direct tests. |
|
||||
| method | AdminService.IterUsers | `func (s *AdminService) IterUsers(ctx context.Context) iter.Seq2[types.User, error]` | IterUsers returns an iterator over all users (admin only). | No direct tests. |
|
||||
| method | AdminService.ListCron | `func (s *AdminService) ListCron(ctx context.Context) ([]types.Cron, error)` | ListCron returns all cron tasks (admin only). | `TestAdminService_Good_ListCron` |
|
||||
| method | AdminService.ListEmails | `func (s *AdminService) ListEmails(ctx context.Context) ([]types.Email, error)` | ListEmails returns all email addresses (admin only). | `TestAdminService_ListEmails_Good` |
|
||||
| method | AdminService.ListOrgs | `func (s *AdminService) ListOrgs(ctx context.Context) ([]types.Organization, error)` | ListOrgs returns all organisations (admin only). | `TestAdminService_Good_ListOrgs` |
|
||||
| method | AdminService.ListUsers | `func (s *AdminService) ListUsers(ctx context.Context) ([]types.User, error)` | ListUsers returns all users (admin only). | `TestAdminService_Good_ListUsers` |
|
||||
| method | AdminService.SearchEmails | `func (s *AdminService) SearchEmails(ctx context.Context, q string) ([]types.Email, error)` | SearchEmails searches all email addresses by keyword (admin only). | `TestAdminService_SearchEmails_Good` |
|
||||
| method | AdminService.RenameUser | `func (s *AdminService) RenameUser(ctx context.Context, username, newName string) error` | RenameUser renames a user (admin only). | `TestAdminService_Good_RenameUser` |
|
||||
| method | AdminService.RunCron | `func (s *AdminService) RunCron(ctx context.Context, task string) error` | RunCron runs a cron task by name (admin only). | `TestAdminService_Good_RunCron` |
|
||||
| method | BranchService.CreateBranchProtection | `func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo string, opts *types.CreateBranchProtectionOption) (*types.BranchProtection, error)` | CreateBranchProtection creates a new branch protection rule. | `TestBranchService_Good_CreateProtection` |
|
||||
|
|
@ -93,14 +87,11 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | Client.DeleteWithBody | `func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) error` | DeleteWithBody performs a DELETE request with a JSON body. | No direct tests. |
|
||||
| method | Client.Get | `func (c *Client) Get(ctx context.Context, path string, out any) error` | Get performs a GET request. | `TestClient_Bad_Forbidden`, `TestClient_Bad_NotFound`, `TestClient_Bad_ServerError` (+3 more) |
|
||||
| method | Client.GetRaw | `func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error)` | GetRaw performs a GET request and returns the raw response body as bytes instead of JSON-decoding. Useful for endpoints that return raw file content. | No direct tests. |
|
||||
| method | Client.HasToken | `func (c *Client) HasToken() bool` | HasToken reports whether the client was configured with an API token. | `TestClient_HasToken_Bad`, `TestClient_HasToken_Good` |
|
||||
| method | Client.HTTPClient | `func (c *Client) HTTPClient() *http.Client` | HTTPClient returns the configured underlying HTTP client. | `TestClient_Good_WithHTTPClient` |
|
||||
| method | Client.Patch | `func (c *Client) Patch(ctx context.Context, path string, body, out any) error` | Patch performs a PATCH request. | No direct tests. |
|
||||
| method | Client.Post | `func (c *Client) Post(ctx context.Context, path string, body, out any) error` | Post performs a POST request. | `TestClient_Bad_Conflict`, `TestClient_Good_Post` |
|
||||
| method | Client.PostRaw | `func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, error)` | PostRaw performs a POST request with a JSON body and returns the raw response body as bytes instead of JSON-decoding. Useful for endpoints such as /markdown that return raw HTML text. | No direct tests. |
|
||||
| method | Client.Put | `func (c *Client) Put(ctx context.Context, path string, body, out any) error` | Put performs a PUT request. | No direct tests. |
|
||||
| method | Client.RateLimit | `func (c *Client) RateLimit() RateLimit` | RateLimit returns the last known rate limit information. | `TestClient_Good_RateLimit` |
|
||||
| method | Client.UserAgent | `func (c *Client) UserAgent() string` | UserAgent returns the configured User-Agent header value. | `TestClient_Good_Options` |
|
||||
| method | CommitService.CreateStatus | `func (s *CommitService) CreateStatus(ctx context.Context, owner, repo, sha string, opts *types.CreateStatusOption) (*types.CommitStatus, error)` | CreateStatus creates a new commit status for the given SHA. | `TestCommitService_Good_CreateStatus` |
|
||||
| method | CommitService.Get | `func (s *CommitService) Get(ctx context.Context, params Params) (*types.Commit, error)` | Get returns a single commit by SHA or ref. | `TestCommitService_Good_Get`, `TestCommitService_Good_List` |
|
||||
| method | CommitService.GetCombinedStatus | `func (s *CommitService) GetCombinedStatus(ctx context.Context, owner, repo, ref string) (*types.CombinedStatus, error)` | GetCombinedStatus returns the combined status for a given ref (branch, tag, or SHA). | `TestCommitService_Good_GetCombinedStatus` |
|
||||
|
|
@ -115,28 +106,17 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | ContentService.GetRawFile | `func (s *ContentService) GetRawFile(ctx context.Context, owner, repo, filepath string) ([]byte, error)` | GetRawFile returns the raw file content as bytes. | `TestContentService_Bad_GetRawNotFound`, `TestContentService_Good_GetRawFile` |
|
||||
| method | ContentService.UpdateFile | `func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath string, opts *types.UpdateFileOptions) (*types.FileResponse, error)` | UpdateFile updates an existing file in a repository. | `TestContentService_Good_UpdateFile` |
|
||||
| method | Forge.Client | `func (f *Forge) Client() *Client` | Client returns the underlying HTTP client. | `TestForge_Good_Client` |
|
||||
| method | Forge.HasToken | `func (f *Forge) HasToken() bool` | HasToken reports whether the Forge client was configured with an API token. | `TestForge_HasToken_Bad`, `TestForge_HasToken_Good` |
|
||||
| method | Forge.HTTPClient | `func (f *Forge) HTTPClient() *http.Client` | HTTPClient returns the configured underlying HTTP client. | `TestForge_HTTPClient_Good` |
|
||||
| method | Forge.RateLimit | `func (f *Forge) RateLimit() RateLimit` | RateLimit returns the last known rate limit information. | `TestForge_Good_RateLimit` |
|
||||
| method | Forge.UserAgent | `func (f *Forge) UserAgent() string` | UserAgent returns the configured User-Agent header value. | `TestForge_Good_UserAgent` |
|
||||
| method | IssueService.AddLabels | `func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index int64, labelIDs []int64) error` | AddLabels adds labels to an issue. | No direct tests. |
|
||||
| method | IssueService.AddReaction | `func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error` | AddReaction adds a reaction to an issue. | No direct tests. |
|
||||
| method | IssueService.CreateComment | `func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, index int64, body string) (*types.Comment, error)` | CreateComment creates a comment on an issue. | `TestIssueService_Good_CreateComment` |
|
||||
| method | IssueService.AddTime | `func (s *IssueService) AddTime(ctx context.Context, owner, repo string, index int64, opts *types.AddTimeOption) (*types.TrackedTime, error)` | AddTime adds tracked time to an issue. | `TestIssueService_AddTime_Good` |
|
||||
| method | IssueService.DeleteStopwatch | `func (s *IssueService) DeleteStopwatch(ctx context.Context, owner, repo string, index int64) error` | DeleteStopwatch deletes an issue's existing stopwatch. | `TestIssueService_DeleteStopwatch_Good` |
|
||||
| method | IssueService.DeleteReaction | `func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, index int64, reaction string) error` | DeleteReaction removes a reaction from an issue. | No direct tests. |
|
||||
| method | IssueService.IterComments | `func (s *IssueService) IterComments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Comment, error]` | IterComments returns an iterator over all comments on an issue. | No direct tests. |
|
||||
| method | IssueService.ListComments | `func (s *IssueService) ListComments(ctx context.Context, owner, repo string, index int64) ([]types.Comment, error)` | ListComments returns all comments on an issue. | No direct tests. |
|
||||
| method | IssueService.IterTimeline | `func (s *IssueService) IterTimeline(ctx context.Context, owner, repo string, index int64, since, before *time.Time) iter.Seq2[types.TimelineComment, error]` | IterTimeline returns an iterator over all comments and events on an issue. | `TestIssueService_IterTimeline_Good` |
|
||||
| method | IssueService.ListTimeline | `func (s *IssueService) ListTimeline(ctx context.Context, owner, repo string, index int64, since, before *time.Time) ([]types.TimelineComment, error)` | ListTimeline returns all comments and events on an issue. | `TestIssueService_ListTimeline_Good` |
|
||||
| method | IssueService.ListTimes | `func (s *IssueService) ListTimes(ctx context.Context, owner, repo string, index int64, user string, since, before *time.Time) ([]types.TrackedTime, error)` | ListTimes returns all tracked times on an issue. | `TestIssueService_ListTimes_Good` |
|
||||
| method | IssueService.IterTimes | `func (s *IssueService) IterTimes(ctx context.Context, owner, repo string, index int64, user string, since, before *time.Time) iter.Seq2[types.TrackedTime, error]` | IterTimes returns an iterator over all tracked times on an issue. | `TestIssueService_IterTimes_Good` |
|
||||
| method | IssueService.Pin | `func (s *IssueService) Pin(ctx context.Context, owner, repo string, index int64) error` | Pin pins an issue. | `TestIssueService_Good_Pin` |
|
||||
| method | IssueService.RemoveLabel | `func (s *IssueService) RemoveLabel(ctx context.Context, owner, repo string, index int64, labelID int64) error` | RemoveLabel removes a single label from an issue. | No direct tests. |
|
||||
| method | IssueService.SetDeadline | `func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, index int64, deadline string) error` | SetDeadline sets or updates the deadline on an issue. | No direct tests. |
|
||||
| method | IssueService.StartStopwatch | `func (s *IssueService) StartStopwatch(ctx context.Context, owner, repo string, index int64) error` | StartStopwatch starts the stopwatch on an issue. | No direct tests. |
|
||||
| method | IssueService.StopStopwatch | `func (s *IssueService) StopStopwatch(ctx context.Context, owner, repo string, index int64) error` | StopStopwatch stops the stopwatch on an issue. | No direct tests. |
|
||||
| method | IssueService.ResetTime | `func (s *IssueService) ResetTime(ctx context.Context, owner, repo string, index int64) error` | ResetTime removes all tracked time from an issue. | `TestIssueService_ResetTime_Good` |
|
||||
| method | IssueService.Unpin | `func (s *IssueService) Unpin(ctx context.Context, owner, repo string, index int64) error` | Unpin unpins an issue. | No direct tests. |
|
||||
| method | LabelService.CreateOrgLabel | `func (s *LabelService) CreateOrgLabel(ctx context.Context, org string, opts *types.CreateLabelOption) (*types.Label, error)` | CreateOrgLabel creates a new label in an organisation. | `TestLabelService_Good_CreateOrgLabel` |
|
||||
| method | LabelService.CreateRepoLabel | `func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string, opts *types.CreateLabelOption) (*types.Label, error)` | CreateRepoLabel creates a new label in a repository. | `TestLabelService_Good_CreateRepoLabel` |
|
||||
|
|
@ -149,27 +129,19 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | LabelService.ListRepoLabels | `func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) ([]types.Label, error)` | ListRepoLabels returns all labels for a repository. | `TestLabelService_Good_ListRepoLabels` |
|
||||
| method | MilestoneService.Create | `func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error)` | Create creates a new milestone. | No direct tests. |
|
||||
| method | MilestoneService.Get | `func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error)` | Get returns a single milestone by ID. | No direct tests. |
|
||||
| method | MilestoneService.ListAll | `func (s *MilestoneService) ListAll(ctx context.Context, params Params, filters ...MilestoneListOptions) ([]types.Milestone, error)` | ListAll returns all milestones for a repository. | No direct tests. |
|
||||
| method | MilestoneService.ListAll | `func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error)` | ListAll returns all milestones for a repository. | No direct tests. |
|
||||
| method | MiscService.GetGitignoreTemplate | `func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*types.GitignoreTemplateInfo, error)` | GetGitignoreTemplate returns a single gitignore template by name. | `TestMiscService_Good_GetGitignoreTemplate` |
|
||||
| method | MiscService.GetAPISettings | `func (s *MiscService) GetAPISettings(ctx context.Context) (*types.GeneralAPISettings, error)` | GetAPISettings returns the instance's global API settings. | `TestMiscService_GetAPISettings_Good` |
|
||||
| method | MiscService.GetAttachmentSettings | `func (s *MiscService) GetAttachmentSettings(ctx context.Context) (*types.GeneralAttachmentSettings, error)` | GetAttachmentSettings returns the instance's global attachment settings. | `TestMiscService_GetAttachmentSettings_Good` |
|
||||
| method | MiscService.GetLicense | `func (s *MiscService) GetLicense(ctx context.Context, name string) (*types.LicenseTemplateInfo, error)` | GetLicense returns a single licence template by name. | `TestMiscService_Bad_NotFound`, `TestMiscService_Good_GetLicense` |
|
||||
| method | MiscService.GetNodeInfo | `func (s *MiscService) GetNodeInfo(ctx context.Context) (*types.NodeInfo, error)` | GetNodeInfo returns the NodeInfo metadata for the Forgejo instance. | `TestMiscService_Good_GetNodeInfo` |
|
||||
| method | MiscService.GetRepositorySettings | `func (s *MiscService) GetRepositorySettings(ctx context.Context) (*types.GeneralRepoSettings, error)` | GetRepositorySettings returns the instance's global repository settings. | `TestMiscService_GetRepositorySettings_Good` |
|
||||
| method | MiscService.GetVersion | `func (s *MiscService) GetVersion(ctx context.Context) (*types.ServerVersion, error)` | GetVersion returns the server version. | `TestMiscService_Good_GetVersion` |
|
||||
| method | MiscService.GetUISettings | `func (s *MiscService) GetUISettings(ctx context.Context) (*types.GeneralUISettings, error)` | GetUISettings returns the instance's global UI settings. | `TestMiscService_GetUISettings_Good` |
|
||||
| method | MiscService.ListGitignoreTemplates | `func (s *MiscService) ListGitignoreTemplates(ctx context.Context) ([]string, error)` | ListGitignoreTemplates returns all available gitignore template names. | `TestMiscService_Good_ListGitignoreTemplates` |
|
||||
| method | MiscService.ListLicenses | `func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplateListEntry, error)` | ListLicenses returns all available licence templates. | `TestMiscService_Good_ListLicenses` |
|
||||
| method | MiscService.RenderMarkdown | `func (s *MiscService) RenderMarkdown(ctx context.Context, text, mode string) (string, error)` | RenderMarkdown renders markdown text to HTML. The response is raw HTML text, not JSON. | `TestMiscService_Good_RenderMarkdown` |
|
||||
| method | MiscService.RenderMarkup | `func (s *MiscService) RenderMarkup(ctx context.Context, text, mode string) (string, error)` | RenderMarkup renders markup text to HTML. The response is raw HTML text, not JSON. | `TestMiscService_Good_RenderMarkup` |
|
||||
| method | MiscService.RenderMarkdownRaw | `func (s *MiscService) RenderMarkdownRaw(ctx context.Context, text string) (string, error)` | RenderMarkdownRaw renders raw markdown text to HTML. The request body is sent as text/plain and the response is raw HTML text, not JSON. | `TestMiscService_RenderMarkdownRaw_Good` |
|
||||
| method | NotificationService.GetThread | `func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.NotificationThread, error)` | GetThread returns a single notification thread by ID. | `TestNotificationService_Bad_NotFound`, `TestNotificationService_Good_GetThread` |
|
||||
| method | NotificationService.Iter | `func (s *NotificationService) Iter(ctx context.Context) iter.Seq2[types.NotificationThread, error]` | Iter returns an iterator over all notifications for the authenticated user. | No direct tests. |
|
||||
| method | NotificationService.IterRepo | `func (s *NotificationService) IterRepo(ctx context.Context, owner, repo string) iter.Seq2[types.NotificationThread, error]` | IterRepo returns an iterator over all notifications for a specific repository. | No direct tests. |
|
||||
| method | NotificationService.List | `func (s *NotificationService) List(ctx context.Context) ([]types.NotificationThread, error)` | List returns all notifications for the authenticated user. | `TestNotificationService_Good_List` |
|
||||
| method | NotificationService.ListRepo | `func (s *NotificationService) ListRepo(ctx context.Context, owner, repo string) ([]types.NotificationThread, error)` | ListRepo returns all notifications for a specific repository. | `TestNotificationService_Good_ListRepo` |
|
||||
| method | NotificationService.MarkNotifications | `func (s *NotificationService) MarkNotifications(ctx context.Context, opts *NotificationMarkOptions) ([]types.NotificationThread, error)` | MarkNotifications marks authenticated-user notification threads as read, pinned, or unread. | `TestNotificationService_MarkNotifications_Good` |
|
||||
| method | NotificationService.MarkRepoNotifications | `func (s *NotificationService) MarkRepoNotifications(ctx context.Context, owner, repo string, opts *NotificationRepoMarkOptions) ([]types.NotificationThread, error)` | MarkRepoNotifications marks repository notification threads as read, unread, or pinned. | `TestNotificationService_MarkRepoNotifications_Good` |
|
||||
| method | NotificationService.MarkRead | `func (s *NotificationService) MarkRead(ctx context.Context) error` | MarkRead marks all notifications as read. | `TestNotificationService_Good_MarkRead` |
|
||||
| method | NotificationService.MarkThreadRead | `func (s *NotificationService) MarkThreadRead(ctx context.Context, id int64) error` | MarkThreadRead marks a single notification thread as read. | `TestNotificationService_Good_MarkThreadRead` |
|
||||
| method | OrgService.AddMember | `func (s *OrgService) AddMember(ctx context.Context, org, username string) error` | AddMember adds a user to an organisation. | No direct tests. |
|
||||
|
|
@ -187,35 +159,26 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | PackageService.List | `func (s *PackageService) List(ctx context.Context, owner string) ([]types.Package, error)` | List returns all packages for a given owner. | `TestPackageService_Good_List` |
|
||||
| method | PackageService.ListFiles | `func (s *PackageService) ListFiles(ctx context.Context, owner, pkgType, name, version string) ([]types.PackageFile, error)` | ListFiles returns all files for a specific package version. | `TestPackageService_Good_ListFiles` |
|
||||
| method | PullService.DismissReview | `func (s *PullService) DismissReview(ctx context.Context, owner, repo string, index, reviewID int64, msg string) error` | DismissReview dismisses a pull request review. | No direct tests. |
|
||||
| method | PullService.CancelReviewRequests | `func (s *PullService) CancelReviewRequests(ctx context.Context, owner, repo string, index int64, opts *types.PullReviewRequestOptions) error` | CancelReviewRequests cancels review requests for a pull request. | `TestPullService_CancelReviewRequests_Good` |
|
||||
| method | PullService.IterReviews | `func (s *PullService) IterReviews(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.PullReview, error]` | IterReviews returns an iterator over all reviews on a pull request. | No direct tests. |
|
||||
| method | PullService.ListReviews | `func (s *PullService) ListReviews(ctx context.Context, owner, repo string, index int64) ([]types.PullReview, error)` | ListReviews returns all reviews on a pull request. | No direct tests. |
|
||||
| method | PullService.Merge | `func (s *PullService) Merge(ctx context.Context, owner, repo string, index int64, method string) error` | Merge merges a pull request. Method is one of "merge", "rebase", "rebase-merge", "squash", "fast-forward-only", "manually-merged". | `TestPullService_Bad_Merge`, `TestPullService_Good_Merge` |
|
||||
| method | PullService.RequestReviewers | `func (s *PullService) RequestReviewers(ctx context.Context, owner, repo string, index int64, opts *types.PullReviewRequestOptions) ([]types.PullReview, error)` | RequestReviewers creates review requests for a pull request. | `TestPullService_RequestReviewers_Good` |
|
||||
| method | PullService.SubmitReview | `func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, index int64, review map[string]any) (*types.PullReview, error)` | SubmitReview creates a new review on a pull request. | No direct tests. |
|
||||
| method | PullService.UndismissReview | `func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error` | UndismissReview undismisses a pull request review. | No direct tests. |
|
||||
| method | PullService.Update | `func (s *PullService) Update(ctx context.Context, owner, repo string, index int64) error` | Update updates a pull request branch with the base branch. | No direct tests. |
|
||||
| method | ReleaseService.DeleteAsset | `func (s *ReleaseService) DeleteAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) error` | DeleteAsset deletes a single asset from a release. | No direct tests. |
|
||||
| method | ReleaseService.EditAsset | `func (s *ReleaseService) EditAsset(ctx context.Context, owner, repo string, releaseID, attachmentID int64, opts *types.EditAttachmentOptions) (*types.Attachment, error)` | EditAsset updates a release asset. | `TestReleaseService_EditAttachment_Good` |
|
||||
| method | ReleaseService.DeleteByTag | `func (s *ReleaseService) DeleteByTag(ctx context.Context, owner, repo, tag string) error` | DeleteByTag deletes a release by its tag name. | No direct tests. |
|
||||
| method | ReleaseService.GetAsset | `func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) (*types.Attachment, error)` | GetAsset returns a single asset for a release. | No direct tests. |
|
||||
| method | ReleaseService.GetByTag | `func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) (*types.Release, error)` | GetByTag returns a release by its tag name. | `TestReleaseService_Good_GetByTag` |
|
||||
| method | ReleaseService.GetLatest | `func (s *ReleaseService) GetLatest(ctx context.Context, owner, repo string) (*types.Release, error)` | GetLatest returns the most recent non-prerelease, non-draft release. | `TestReleaseService_GetLatest_Good` |
|
||||
| method | ReleaseService.IterAssets | `func (s *ReleaseService) IterAssets(ctx context.Context, owner, repo string, releaseID int64) iter.Seq2[types.Attachment, error]` | IterAssets returns an iterator over all assets for a release. | No direct tests. |
|
||||
| method | ReleaseService.ListAssets | `func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, releaseID int64) ([]types.Attachment, error)` | ListAssets returns all assets for a release. | No direct tests. |
|
||||
| method | RepoService.AcceptTransfer | `func (s *RepoService) AcceptTransfer(ctx context.Context, owner, repo string) error` | AcceptTransfer accepts a pending repository transfer. | No direct tests. |
|
||||
| method | RepoService.Fork | `func (s *RepoService) Fork(ctx context.Context, owner, repo, org string) (*types.Repository, error)` | Fork forks a repository. If org is non-empty, forks into that organisation. | `TestRepoService_Good_Fork` |
|
||||
| method | RepoService.GetArchive | `func (s *RepoService) GetArchive(ctx context.Context, owner, repo, archive string) ([]byte, error)` | GetArchive returns a repository archive as raw bytes. | `TestRepoService_GetArchive_Good` |
|
||||
| method | RepoService.GetSigningKey | `func (s *RepoService) GetSigningKey(ctx context.Context, owner, repo string) (string, error)` | GetSigningKey returns the repository signing key as ASCII-armoured text. | `TestRepoService_GetSigningKey_Good` |
|
||||
| method | RepoService.GetNewPinAllowed | `func (s *RepoService) GetNewPinAllowed(ctx context.Context, owner, repo string) (*types.NewIssuePinsAllowed, error)` | GetNewPinAllowed returns whether new issue pins are allowed for a repository. | `TestRepoService_GetNewPinAllowed_Good` |
|
||||
| method | RepoService.IterOrgRepos | `func (s *RepoService) IterOrgRepos(ctx context.Context, org string) iter.Seq2[types.Repository, error]` | IterOrgRepos returns an iterator over all repositories for an organisation. | No direct tests. |
|
||||
| method | RepoService.IterUserRepos | `func (s *RepoService) IterUserRepos(ctx context.Context) iter.Seq2[types.Repository, error]` | IterUserRepos returns an iterator over all repositories for the authenticated user. | No direct tests. |
|
||||
| method | RepoService.ListOrgRepos | `func (s *RepoService) ListOrgRepos(ctx context.Context, org string) ([]types.Repository, error)` | ListOrgRepos returns all repositories for an organisation. | `TestRepoService_Good_ListOrgRepos` |
|
||||
| method | RepoService.ListUserRepos | `func (s *RepoService) ListUserRepos(ctx context.Context) ([]types.Repository, error)` | ListUserRepos returns all repositories for the authenticated user. | No direct tests. |
|
||||
| method | RepoService.MirrorSync | `func (s *RepoService) MirrorSync(ctx context.Context, owner, repo string) error` | MirrorSync triggers a mirror sync. | No direct tests. |
|
||||
| method | RepoService.RejectTransfer | `func (s *RepoService) RejectTransfer(ctx context.Context, owner, repo string) error` | RejectTransfer rejects a pending repository transfer. | No direct tests. |
|
||||
| method | RepoService.DeleteAvatar | `func (s *RepoService) DeleteAvatar(ctx context.Context, owner, repo string) error` | DeleteAvatar deletes a repository avatar. | `TestRepoService_DeleteAvatar_Good` |
|
||||
| method | RepoService.UpdateAvatar | `func (s *RepoService) UpdateAvatar(ctx context.Context, owner, repo string, opts *types.UpdateRepoAvatarOption) error` | UpdateAvatar updates a repository avatar. | `TestRepoService_UpdateAvatar_Good` |
|
||||
| method | RepoService.Transfer | `func (s *RepoService) Transfer(ctx context.Context, owner, repo string, opts map[string]any) error` | Transfer initiates a repository transfer. | No direct tests. |
|
||||
| method | Resource.Create | `func (r *Resource[T, C, U]) Create(ctx context.Context, params Params, body *C) (*T, error)` | Create creates a new resource. | `TestResource_Good_Create` |
|
||||
| method | Resource.Delete | `func (r *Resource[T, C, U]) Delete(ctx context.Context, params Params) error` | Delete removes a resource. | `TestResource_Good_Delete` |
|
||||
|
|
@ -235,17 +198,7 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | TeamService.RemoveMember | `func (s *TeamService) RemoveMember(ctx context.Context, teamID int64, username string) error` | RemoveMember removes a user from a team. | No direct tests. |
|
||||
| method | TeamService.RemoveRepo | `func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error` | RemoveRepo removes a repository from a team. | No direct tests. |
|
||||
| method | UserService.Follow | `func (s *UserService) Follow(ctx context.Context, username string) error` | Follow follows a user as the authenticated user. | No direct tests. |
|
||||
| method | UserService.CheckFollowing | `func (s *UserService) CheckFollowing(ctx context.Context, username, target string) (bool, error)` | CheckFollowing reports whether one user is following another user. | `TestUserService_CheckFollowing_Good`, `TestUserService_CheckFollowing_Bad_NotFound` |
|
||||
| method | UserService.GetCurrent | `func (s *UserService) GetCurrent(ctx context.Context) (*types.User, error)` | GetCurrent returns the authenticated user. | `TestUserService_Good_GetCurrent` |
|
||||
| method | UserService.GetQuota | `func (s *UserService) GetQuota(ctx context.Context) (*types.QuotaInfo, error)` | GetQuota returns the authenticated user's quota information. | `TestUserService_GetQuota_Good` |
|
||||
| method | UserService.ListQuotaArtifacts | `func (s *UserService) ListQuotaArtifacts(ctx context.Context) ([]types.QuotaUsedArtifact, error)` | ListQuotaArtifacts returns all artifacts affecting the authenticated user's quota. | `TestUserService_ListQuotaArtifacts_Good` |
|
||||
| method | UserService.IterQuotaArtifacts | `func (s *UserService) IterQuotaArtifacts(ctx context.Context) iter.Seq2[types.QuotaUsedArtifact, error]` | IterQuotaArtifacts returns an iterator over all artifacts affecting the authenticated user's quota. | `TestUserService_IterQuotaArtifacts_Good` |
|
||||
| method | UserService.ListQuotaAttachments | `func (s *UserService) ListQuotaAttachments(ctx context.Context) ([]types.QuotaUsedAttachment, error)` | ListQuotaAttachments returns all attachments affecting the authenticated user's quota. | `TestUserService_ListQuotaAttachments_Good` |
|
||||
| method | UserService.IterQuotaAttachments | `func (s *UserService) IterQuotaAttachments(ctx context.Context) iter.Seq2[types.QuotaUsedAttachment, error]` | IterQuotaAttachments returns an iterator over all attachments affecting the authenticated user's quota. | `TestUserService_IterQuotaAttachments_Good` |
|
||||
| method | UserService.ListQuotaPackages | `func (s *UserService) ListQuotaPackages(ctx context.Context) ([]types.QuotaUsedPackage, error)` | ListQuotaPackages returns all packages affecting the authenticated user's quota. | `TestUserService_ListQuotaPackages_Good` |
|
||||
| method | UserService.IterQuotaPackages | `func (s *UserService) IterQuotaPackages(ctx context.Context) iter.Seq2[types.QuotaUsedPackage, error]` | IterQuotaPackages returns an iterator over all packages affecting the authenticated user's quota. | `TestUserService_IterQuotaPackages_Good` |
|
||||
| method | UserService.ListStopwatches | `func (s *UserService) ListStopwatches(ctx context.Context) ([]types.StopWatch, error)` | ListStopwatches returns all existing stopwatches for the authenticated user. | `TestUserService_ListStopwatches_Good` |
|
||||
| method | UserService.IterStopwatches | `func (s *UserService) IterStopwatches(ctx context.Context) iter.Seq2[types.StopWatch, error]` | IterStopwatches returns an iterator over all existing stopwatches for the authenticated user. | `TestUserService_IterStopwatches_Good` |
|
||||
| method | UserService.IterFollowers | `func (s *UserService) IterFollowers(ctx context.Context, username string) iter.Seq2[types.User, error]` | IterFollowers returns an iterator over all followers of a user. | No direct tests. |
|
||||
| method | UserService.IterFollowing | `func (s *UserService) IterFollowing(ctx context.Context, username string) iter.Seq2[types.User, error]` | IterFollowing returns an iterator over all users that a user is following. | No direct tests. |
|
||||
| method | UserService.IterStarred | `func (s *UserService) IterStarred(ctx context.Context, username string) iter.Seq2[types.Repository, error]` | IterStarred returns an iterator over all repositories starred by a user. | No direct tests. |
|
||||
|
|
@ -257,16 +210,6 @@ Coverage notes: rows list direct tests when a symbol is named in test names or r
|
|||
| method | UserService.Unstar | `func (s *UserService) Unstar(ctx context.Context, owner, repo string) error` | Unstar unstars a repository as the authenticated user. | No direct tests. |
|
||||
| method | WebhookService.IterOrgHooks | `func (s *WebhookService) IterOrgHooks(ctx context.Context, org string) iter.Seq2[types.Hook, error]` | IterOrgHooks returns an iterator over all webhooks for an organisation. | No direct tests. |
|
||||
| method | WebhookService.ListOrgHooks | `func (s *WebhookService) ListOrgHooks(ctx context.Context, org string) ([]types.Hook, error)` | ListOrgHooks returns all webhooks for an organisation. | `TestWebhookService_Good_ListOrgHooks` |
|
||||
| method | WebhookService.ListGitHooks | `func (s *WebhookService) ListGitHooks(ctx context.Context, owner, repo string) ([]types.GitHook, error)` | ListGitHooks returns all Git hooks for a repository. | `TestWebhookService_Good_ListGitHooks` |
|
||||
| method | WebhookService.GetGitHook | `func (s *WebhookService) GetGitHook(ctx context.Context, owner, repo, id string) (*types.GitHook, error)` | GetGitHook returns a single Git hook for a repository. | `TestWebhookService_Good_GetGitHook` |
|
||||
| method | WebhookService.EditGitHook | `func (s *WebhookService) EditGitHook(ctx context.Context, owner, repo, id string, opts *types.EditGitHookOption) (*types.GitHook, error)` | EditGitHook updates an existing Git hook in a repository. | `TestWebhookService_Good_EditGitHook` |
|
||||
| method | WebhookService.DeleteGitHook | `func (s *WebhookService) DeleteGitHook(ctx context.Context, owner, repo, id string) error` | DeleteGitHook deletes a Git hook from a repository. | `TestWebhookService_Good_DeleteGitHook` |
|
||||
| method | WebhookService.IterUserHooks | `func (s *WebhookService) IterUserHooks(ctx context.Context) iter.Seq2[types.Hook, error]` | IterUserHooks returns an iterator over all webhooks for the authenticated user. | No direct tests. |
|
||||
| method | WebhookService.ListUserHooks | `func (s *WebhookService) ListUserHooks(ctx context.Context) ([]types.Hook, error)` | ListUserHooks returns all webhooks for the authenticated user. | `TestWebhookService_Good_ListUserHooks` |
|
||||
| method | WebhookService.GetUserHook | `func (s *WebhookService) GetUserHook(ctx context.Context, id int64) (*types.Hook, error)` | GetUserHook returns a single webhook for the authenticated user. | `TestWebhookService_Good_GetUserHook` |
|
||||
| method | WebhookService.CreateUserHook | `func (s *WebhookService) CreateUserHook(ctx context.Context, opts *types.CreateHookOption) (*types.Hook, error)` | CreateUserHook creates a webhook for the authenticated user. | `TestWebhookService_Good_CreateUserHook` |
|
||||
| method | WebhookService.EditUserHook | `func (s *WebhookService) EditUserHook(ctx context.Context, id int64, opts *types.EditHookOption) (*types.Hook, error)` | EditUserHook updates an existing authenticated-user webhook. | `TestWebhookService_Good_EditUserHook` |
|
||||
| method | WebhookService.DeleteUserHook | `func (s *WebhookService) DeleteUserHook(ctx context.Context, id int64) error` | DeleteUserHook deletes an authenticated-user webhook. | `TestWebhookService_Good_DeleteUserHook` |
|
||||
| method | WebhookService.TestHook | `func (s *WebhookService) TestHook(ctx context.Context, owner, repo string, id int64) error` | TestHook triggers a test delivery for a webhook. | `TestWebhookService_Good_TestHook` |
|
||||
| method | WikiService.CreatePage | `func (s *WikiService) CreatePage(ctx context.Context, owner, repo string, opts *types.CreateWikiPageOptions) (*types.WikiPage, error)` | CreatePage creates a new wiki page. | `TestWikiService_Good_CreatePage` |
|
||||
| method | WikiService.DeletePage | `func (s *WikiService) DeletePage(ctx context.Context, owner, repo, pageName string) error` | DeletePage removes a wiki page. | `TestWikiService_Good_DeletePage` |
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ go-forge is organised in three layers, each building on the one below:
|
|||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Forge (top-level client) │
|
||||
│ Aggregates 20 service structs │
|
||||
│ Aggregates 18 service structs │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Service layer │
|
||||
│ RepoService, IssueService, PullService, ... │
|
||||
|
|
@ -49,7 +49,6 @@ func (c *Client) Delete(ctx context.Context, path string) error
|
|||
func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) error
|
||||
func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error)
|
||||
func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, error)
|
||||
func (c *Client) HTTPClient() *http.Client
|
||||
```
|
||||
|
||||
The `Raw` variants return the response body as `[]byte` instead of decoding JSON. This is used by endpoints that return non-JSON content (e.g. the markdown rendering endpoint returns raw HTML).
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ All tests use the standard `testing` package with `net/http/httptest` for HTTP s
|
|||
go test ./...
|
||||
|
||||
# Run a specific test by name
|
||||
go test -v -run TestClient_Get_Good ./...
|
||||
go test -v -run TestClient_Good_Get ./...
|
||||
|
||||
# Run tests with race detection
|
||||
go test -race ./...
|
||||
|
|
@ -59,7 +59,7 @@ core go cov --open # Open coverage report in browser
|
|||
|
||||
### Test naming convention
|
||||
|
||||
Tests follow `Test<TypeOrArea>_<MethodOrCase>_<Good|Bad|Ugly>`:
|
||||
Tests follow the `_Good`, `_Bad`, `_Ugly` suffix pattern:
|
||||
|
||||
- **`_Good`** — Happy-path tests confirming correct behaviour.
|
||||
- **`_Bad`** — Expected error conditions (e.g. 404, 500 responses).
|
||||
|
|
@ -67,11 +67,11 @@ Tests follow `Test<TypeOrArea>_<MethodOrCase>_<Good|Bad|Ugly>`:
|
|||
|
||||
Examples:
|
||||
```
|
||||
TestClient_Get_Good
|
||||
TestClient_ServerError_Bad
|
||||
TestClient_NotFound_Bad
|
||||
TestClient_ContextCancellation_Good
|
||||
TestResource_ListAll_Good
|
||||
TestClient_Good_Get
|
||||
TestClient_Bad_ServerError
|
||||
TestClient_Bad_NotFound
|
||||
TestClient_Good_ContextCancellation
|
||||
TestResource_Good_ListAll
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -173,7 +173,7 @@ To add coverage for a new Forgejo API domain:
|
|||
|
||||
```go
|
||||
func (s *TopicService) ListRepoTopics(ctx context.Context, owner, repo string) ([]types.Topic, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/topics", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/topics", owner, repo)
|
||||
return ListAll[types.Topic](ctx, s.client, path, nil)
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ description: Full-coverage Go client for the Forgejo API with generics-based CRU
|
|||
|
||||
# go-forge
|
||||
|
||||
`dappco.re/go/core/forge` is a Go client library for the [Forgejo](https://forgejo.org) REST API. It provides typed access to 20 API domains (repositories, issues, pull requests, organisations, milestones, ActivityPub, and more) through a single top-level `Forge` client. Types are generated directly from Forgejo's `swagger.v1.json` specification, keeping the library in lockstep with the server.
|
||||
`dappco.re/go/core/forge` is a Go client library for the [Forgejo](https://forgejo.org) REST API. It provides typed access to 18 API domains (repositories, issues, pull requests, organisations, and more) through a single top-level `Forge` client. Types are generated directly from Forgejo's `swagger.v1.json` specification, keeping the library in lockstep with the server.
|
||||
|
||||
**Module path:** `dappco.re/go/core/forge`
|
||||
**Go version:** 1.26+
|
||||
|
|
@ -75,7 +75,7 @@ Environment variables:
|
|||
go-forge/
|
||||
├── client.go HTTP client, auth, error handling, rate limits
|
||||
├── config.go Config resolution: flags > env > defaults
|
||||
├── forge.go Top-level Forge struct aggregating all 20 services
|
||||
├── forge.go Top-level Forge struct aggregating all 18 services
|
||||
├── resource.go Generic Resource[T, C, U] for CRUD operations
|
||||
├── pagination.go ListPage, ListAll, ListIter — paginated requests
|
||||
├── params.go Path variable resolution ({owner}/{repo} -> values)
|
||||
|
|
@ -92,13 +92,11 @@ go-forge/
|
|||
├── webhooks.go WebhookService — repo and org webhooks
|
||||
├── notifications.go NotificationService — notifications, threads
|
||||
├── packages.go PackageService — package registry
|
||||
├── actions.go ActionsService — CI/CD secrets, variables, dispatches, tasks
|
||||
├── actions.go ActionsService — CI/CD secrets, variables, dispatches
|
||||
├── contents.go ContentService — file read/write/delete
|
||||
├── wiki.go WikiService — wiki pages
|
||||
├── misc.go MiscService — markdown, licences, gitignore, version
|
||||
├── commits.go CommitService — statuses, notes
|
||||
├── milestones.go MilestoneService — repository milestones
|
||||
├── activitypub.go ActivityPubService — ActivityPub actors and inboxes
|
||||
├── misc.go MiscService — markdown, licences, gitignore, version
|
||||
├── types/ 229 generated Go types from swagger.v1.json
|
||||
│ ├── generate.go go:generate directive
|
||||
│ ├── repo.go Repository, CreateRepoOption, EditRepoOption, ...
|
||||
|
|
@ -116,7 +114,7 @@ go-forge/
|
|||
|
||||
## Services
|
||||
|
||||
The `Forge` struct exposes 20 service fields, each handling a different API domain:
|
||||
The `Forge` struct exposes 18 service fields, each handling a different API domain:
|
||||
|
||||
| Service | Struct | Embedding | Domain |
|
||||
|-----------------|---------------------|----------------------------------|--------------------------------------|
|
||||
|
|
@ -133,20 +131,18 @@ The `Forge` struct exposes 20 service fields, each handling a different API doma
|
|||
| `Webhooks` | `WebhookService` | `Resource[Hook, ...]` | Repo and org webhooks |
|
||||
| `Notifications` | `NotificationService` | (standalone) | Notifications, threads |
|
||||
| `Packages` | `PackageService` | (standalone) | Package registry |
|
||||
| `Actions` | `ActionsService` | (standalone) | CI/CD secrets, variables, dispatches, tasks |
|
||||
| `Actions` | `ActionsService` | (standalone) | CI/CD secrets, variables, dispatches |
|
||||
| `Contents` | `ContentService` | (standalone) | File read/write/delete |
|
||||
| `Wiki` | `WikiService` | (standalone) | Wiki pages |
|
||||
| `Misc` | `MiscService` | (standalone) | Markdown, licences, gitignore, version |
|
||||
| `Commits` | `CommitService` | (standalone) | Commit statuses, git notes |
|
||||
| `Milestones` | `MilestoneService` | (standalone) | Repository milestones |
|
||||
| `ActivityPub` | `ActivityPubService` | (standalone) | ActivityPub actors and inboxes |
|
||||
| `Misc` | `MiscService` | (standalone) | Markdown, licences, gitignore, version |
|
||||
|
||||
Services that embed `Resource[T, C, U]` inherit `List`, `ListAll`, `Iter`, `Get`, `Create`, `Update`, and `Delete` methods automatically. Standalone services have hand-written methods because their API endpoints are heterogeneous and do not fit a uniform CRUD pattern.
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
This module has a small dependency set: `dappco.re/go/core` and `github.com/goccy/go-json`, plus the Go standard library (`net/http`, `context`, `iter`, etc.) where appropriate.
|
||||
This module has **zero external dependencies**. It relies solely on the Go standard library (`net/http`, `encoding/json`, `context`, `iter`, etc.) and requires Go 1.26 or later.
|
||||
|
||||
```
|
||||
module dappco.re/go/core/forge
|
||||
|
|
|
|||
113
forge.go
113
forge.go
|
|
@ -1,14 +1,6 @@
|
|||
package forge
|
||||
|
||||
import "net/http"
|
||||
|
||||
// Forge is the top-level client for the Forgejo API.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ctx := context.Background()
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// repo, err := f.Repos.Get(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
type Forge struct {
|
||||
client *Client
|
||||
|
||||
|
|
@ -31,16 +23,9 @@ type Forge struct {
|
|||
Misc *MiscService
|
||||
Commits *CommitService
|
||||
Milestones *MilestoneService
|
||||
ActivityPub *ActivityPubService
|
||||
}
|
||||
|
||||
// NewForge creates a new Forge client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ctx := context.Background()
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// repos, err := f.Repos.ListOrgRepos(ctx, "core")
|
||||
func NewForge(url, token string, opts ...Option) *Forge {
|
||||
c := NewClient(url, token, opts...)
|
||||
f := &Forge{client: c}
|
||||
|
|
@ -63,102 +48,8 @@ func NewForge(url, token string, opts ...Option) *Forge {
|
|||
f.Misc = newMiscService(c)
|
||||
f.Commits = newCommitService(c)
|
||||
f.Milestones = newMilestoneService(c)
|
||||
f.ActivityPub = newActivityPubService(c)
|
||||
return f
|
||||
}
|
||||
|
||||
// Client returns the underlying Forge client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// client := f.Client()
|
||||
func (f *Forge) Client() *Client {
|
||||
if f == nil {
|
||||
return nil
|
||||
}
|
||||
return f.client
|
||||
}
|
||||
|
||||
// BaseURL returns the configured Forgejo base URL.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// baseURL := f.BaseURL()
|
||||
func (f *Forge) BaseURL() string {
|
||||
if f == nil || f.client == nil {
|
||||
return ""
|
||||
}
|
||||
return f.client.BaseURL()
|
||||
}
|
||||
|
||||
// RateLimit returns the last known rate limit information.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// rl := f.RateLimit()
|
||||
func (f *Forge) RateLimit() RateLimit {
|
||||
if f == nil || f.client == nil {
|
||||
return RateLimit{}
|
||||
}
|
||||
return f.client.RateLimit()
|
||||
}
|
||||
|
||||
// UserAgent returns the configured User-Agent header value.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// ua := f.UserAgent()
|
||||
func (f *Forge) UserAgent() string {
|
||||
if f == nil || f.client == nil {
|
||||
return ""
|
||||
}
|
||||
return f.client.UserAgent()
|
||||
}
|
||||
|
||||
// HTTPClient returns the configured underlying HTTP client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// hc := f.HTTPClient()
|
||||
func (f *Forge) HTTPClient() *http.Client {
|
||||
if f == nil || f.client == nil {
|
||||
return nil
|
||||
}
|
||||
return f.client.HTTPClient()
|
||||
}
|
||||
|
||||
// HasToken reports whether the Forge client was configured with an API token.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// if f.HasToken() {
|
||||
// _ = "authenticated"
|
||||
// }
|
||||
func (f *Forge) HasToken() bool {
|
||||
if f == nil || f.client == nil {
|
||||
return false
|
||||
}
|
||||
return f.client.HasToken()
|
||||
}
|
||||
|
||||
// String returns a safe summary of the Forge client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := f.String()
|
||||
func (f *Forge) String() string {
|
||||
if f == nil {
|
||||
return "forge.Forge{<nil>}"
|
||||
}
|
||||
if f.client == nil {
|
||||
return "forge.Forge{client=<nil>}"
|
||||
}
|
||||
return "forge.Forge{client=" + f.client.String() + "}"
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the Forge client.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := fmt.Sprintf("%#v", f)
|
||||
func (f *Forge) GoString() string { return f.String() }
|
||||
// Client returns the underlying HTTP client.
|
||||
func (f *Forge) Client() *Client { return f.client }
|
||||
|
|
|
|||
183
forge_test.go
183
forge_test.go
|
|
@ -1,10 +1,8 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -12,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestForge_NewForge_Good(t *testing.T) {
|
||||
func TestForge_Good_NewForge(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok")
|
||||
if f.Repos == nil {
|
||||
t.Fatal("Repos service is nil")
|
||||
|
|
@ -20,12 +18,9 @@ func TestForge_NewForge_Good(t *testing.T) {
|
|||
if f.Issues == nil {
|
||||
t.Fatal("Issues service is nil")
|
||||
}
|
||||
if f.ActivityPub == nil {
|
||||
t.Fatal("ActivityPub service is nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_Client_Good(t *testing.T) {
|
||||
func TestForge_Good_Client(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok")
|
||||
c := f.Client()
|
||||
if c == nil {
|
||||
|
|
@ -34,92 +29,9 @@ func TestForge_Client_Good(t *testing.T) {
|
|||
if c.baseURL != "https://forge.lthn.ai" {
|
||||
t.Errorf("got baseURL=%q", c.baseURL)
|
||||
}
|
||||
if got := c.BaseURL(); got != "https://forge.lthn.ai" {
|
||||
t.Errorf("got BaseURL()=%q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_BaseURL_Good(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok")
|
||||
if got := f.BaseURL(); got != "https://forge.lthn.ai" {
|
||||
t.Fatalf("got base URL %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_RateLimit_Good(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok")
|
||||
if got := f.RateLimit(); got != (RateLimit{}) {
|
||||
t.Fatalf("got rate limit %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_UserAgent_Good(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok", WithUserAgent("go-forge/1.0"))
|
||||
if got := f.UserAgent(); got != "go-forge/1.0" {
|
||||
t.Fatalf("got user agent %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_HTTPClient_Good(t *testing.T) {
|
||||
custom := &http.Client{}
|
||||
f := NewForge("https://forge.lthn.ai", "tok", WithHTTPClient(custom))
|
||||
if got := f.HTTPClient(); got != custom {
|
||||
t.Fatal("expected HTTPClient() to return the configured HTTP client")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_HasToken_Good(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok")
|
||||
if !f.HasToken() {
|
||||
t.Fatal("expected HasToken to report configured token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_HasToken_Bad(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "")
|
||||
if f.HasToken() {
|
||||
t.Fatal("expected HasToken to report missing token")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_NilSafeAccessors(t *testing.T) {
|
||||
var f *Forge
|
||||
if got := f.Client(); got != nil {
|
||||
t.Fatal("expected Client() to return nil")
|
||||
}
|
||||
if got := f.BaseURL(); got != "" {
|
||||
t.Fatalf("got BaseURL()=%q, want empty string", got)
|
||||
}
|
||||
if got := f.RateLimit(); got != (RateLimit{}) {
|
||||
t.Fatalf("got RateLimit()=%#v, want zero value", got)
|
||||
}
|
||||
if got := f.UserAgent(); got != "" {
|
||||
t.Fatalf("got UserAgent()=%q, want empty string", got)
|
||||
}
|
||||
if got := f.HTTPClient(); got != nil {
|
||||
t.Fatal("expected HTTPClient() to return nil")
|
||||
}
|
||||
if got := f.HasToken(); got {
|
||||
t.Fatal("expected HasToken() to report false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_String_Good(t *testing.T) {
|
||||
f := NewForge("https://forge.lthn.ai", "tok", WithUserAgent("go-forge/1.0"))
|
||||
got := fmt.Sprint(f)
|
||||
want := `forge.Forge{client=forge.Client{baseURL="https://forge.lthn.ai", token=set, userAgent="go-forge/1.0"}}`
|
||||
if got != want {
|
||||
t.Fatalf("got %q, want %q", got, want)
|
||||
}
|
||||
if got := f.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", f); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoService_ListOrgRepos_Good(t *testing.T) {
|
||||
func TestRepoService_Good_ListOrgRepos(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -144,7 +56,7 @@ func TestRepoService_ListOrgRepos_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Get_Good(t *testing.T) {
|
||||
func TestRepoService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
|
|
@ -165,7 +77,7 @@ func TestRepoService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Update_Good(t *testing.T) {
|
||||
func TestRepoService_Good_Update(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
|
|
@ -193,7 +105,7 @@ func TestRepoService_Update_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Delete_Good(t *testing.T) {
|
||||
func TestRepoService_Good_Delete(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -213,7 +125,7 @@ func TestRepoService_Delete_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Get_Bad(t *testing.T) {
|
||||
func TestRepoService_Bad_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
||||
|
|
@ -226,7 +138,7 @@ func TestRepoService_Get_Bad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRepoService_Fork_Good(t *testing.T) {
|
||||
func TestRepoService_Good_Fork(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -245,80 +157,3 @@ func TestRepoService_Fork_Good(t *testing.T) {
|
|||
t.Error("expected fork=true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoService_GetArchive_Good(t *testing.T) {
|
||||
want := []byte("zip-bytes")
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/archive/master.zip" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(want)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
got, err := f.Repos.GetArchive(context.Background(), "core", "go-forge", "master.zip")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoService_GetRawFile_Good(t *testing.T) {
|
||||
want := []byte("# go-forge\n\nA Go client for Forgejo.")
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/raw/README.md" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
_, _ = w.Write(want)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
got, err := f.Repos.GetRawFile(context.Background(), "core", "go-forge", "README.md")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(got, want) {
|
||||
t.Fatalf("got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRepoService_ListTags_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/tags" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Tag{{Name: "v1.0.0"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
tags, err := f.Repos.ListTags(context.Background(), "core", "go-forge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tags) != 1 || tags[0].Name != "v1.0.0" {
|
||||
t.Fatalf("unexpected result: %+v", tags)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
5
go.mod
5
go.mod
|
|
@ -3,9 +3,8 @@ module dappco.re/go/core/forge
|
|||
go 1.26.0
|
||||
|
||||
require (
|
||||
dappco.re/go/core v0.4.7
|
||||
dappco.re/go/core/io v0.2.0
|
||||
github.com/goccy/go-json v0.10.6
|
||||
dappco.re/go/core/log v0.1.0
|
||||
)
|
||||
|
||||
require dappco.re/go/core/log v0.0.4 // indirect
|
||||
require forge.lthn.ai/core/go-log v0.0.4 // indirect
|
||||
|
|
|
|||
6
go.sum
6
go.sum
|
|
@ -1,13 +1,11 @@
|
|||
dappco.re/go/core v0.4.7 h1:KmIA/2lo6rl1NMtLrKqCWfMlUqpDZYH3q0/d10dTtGA=
|
||||
dappco.re/go/core v0.4.7/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A=
|
||||
dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4=
|
||||
dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E=
|
||||
dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc=
|
||||
dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs=
|
||||
forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
|
||||
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU=
|
||||
github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
|
|
|
|||
121
helpers.go
121
helpers.go
|
|
@ -1,121 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
func trimTrailingSlashes(s string) string {
|
||||
for core.HasSuffix(s, "/") {
|
||||
s = core.TrimSuffix(s, "/")
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func int64String(v int64) string {
|
||||
return strconv.FormatInt(v, 10)
|
||||
}
|
||||
|
||||
func pathParams(values ...string) Params {
|
||||
params := make(Params, len(values)/2)
|
||||
for i := 0; i+1 < len(values); i += 2 {
|
||||
params[values[i]] = values[i+1]
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func optionString(typeName string, fields ...any) string {
|
||||
var b strings.Builder
|
||||
b.WriteString(typeName)
|
||||
b.WriteString("{")
|
||||
|
||||
wroteField := false
|
||||
for i := 0; i+1 < len(fields); i += 2 {
|
||||
name, _ := fields[i].(string)
|
||||
value := fields[i+1]
|
||||
if isZeroOptionValue(value) {
|
||||
continue
|
||||
}
|
||||
if wroteField {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
wroteField = true
|
||||
b.WriteString(name)
|
||||
b.WriteString("=")
|
||||
b.WriteString(formatOptionValue(value))
|
||||
}
|
||||
|
||||
b.WriteString("}")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func isZeroOptionValue(v any) bool {
|
||||
switch x := v.(type) {
|
||||
case nil:
|
||||
return true
|
||||
case string:
|
||||
return x == ""
|
||||
case bool:
|
||||
return !x
|
||||
case int:
|
||||
return x == 0
|
||||
case int64:
|
||||
return x == 0
|
||||
case []string:
|
||||
return len(x) == 0
|
||||
case *time.Time:
|
||||
return x == nil
|
||||
case *bool:
|
||||
return x == nil
|
||||
case time.Time:
|
||||
return x.IsZero()
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func formatOptionValue(v any) string {
|
||||
switch x := v.(type) {
|
||||
case string:
|
||||
return strconv.Quote(x)
|
||||
case bool:
|
||||
return strconv.FormatBool(x)
|
||||
case int:
|
||||
return strconv.Itoa(x)
|
||||
case int64:
|
||||
return strconv.FormatInt(x, 10)
|
||||
case []string:
|
||||
return fmt.Sprintf("%#v", x)
|
||||
case *time.Time:
|
||||
if x == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return strconv.Quote(x.Format(time.RFC3339))
|
||||
case *bool:
|
||||
if x == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return strconv.FormatBool(*x)
|
||||
case time.Time:
|
||||
return strconv.Quote(x.Format(time.RFC3339))
|
||||
default:
|
||||
return fmt.Sprintf("%#v", v)
|
||||
}
|
||||
}
|
||||
|
||||
func serviceString(typeName, fieldName string, value any) string {
|
||||
return typeName + "{" + fieldName + "=" + fmt.Sprint(value) + "}"
|
||||
}
|
||||
|
||||
func lastIndexByte(s string, b byte) int {
|
||||
for i := len(s) - 1; i >= 0; i-- {
|
||||
if s[i] == b {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
731
issues.go
731
issues.go
|
|
@ -2,156 +2,17 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
goio "io"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// IssueService handles issue operations within a repository.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Issues.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
type IssueService struct {
|
||||
Resource[types.Issue, types.CreateIssueOption, types.EditIssueOption]
|
||||
}
|
||||
|
||||
// IssueListOptions controls filtering for repository issue listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.IssueListOptions{State: "open", Labels: "bug"}
|
||||
type IssueListOptions struct {
|
||||
State string
|
||||
Labels string
|
||||
Query string
|
||||
Type string
|
||||
Milestones string
|
||||
Since *time.Time
|
||||
Before *time.Time
|
||||
CreatedBy string
|
||||
AssignedBy string
|
||||
MentionedBy string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the issue list filters.
|
||||
func (o IssueListOptions) String() string {
|
||||
return optionString("forge.IssueListOptions",
|
||||
"state", o.State,
|
||||
"labels", o.Labels,
|
||||
"q", o.Query,
|
||||
"type", o.Type,
|
||||
"milestones", o.Milestones,
|
||||
"since", o.Since,
|
||||
"before", o.Before,
|
||||
"created_by", o.CreatedBy,
|
||||
"assigned_by", o.AssignedBy,
|
||||
"mentioned_by", o.MentionedBy,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the issue list filters.
|
||||
func (o IssueListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o IssueListOptions) queryParams() map[string]string {
|
||||
query := make(map[string]string, 10)
|
||||
if o.State != "" {
|
||||
query["state"] = o.State
|
||||
}
|
||||
if o.Labels != "" {
|
||||
query["labels"] = o.Labels
|
||||
}
|
||||
if o.Query != "" {
|
||||
query["q"] = o.Query
|
||||
}
|
||||
if o.Type != "" {
|
||||
query["type"] = o.Type
|
||||
}
|
||||
if o.Milestones != "" {
|
||||
query["milestones"] = o.Milestones
|
||||
}
|
||||
if o.Since != nil {
|
||||
query["since"] = o.Since.Format(time.RFC3339)
|
||||
}
|
||||
if o.Before != nil {
|
||||
query["before"] = o.Before.Format(time.RFC3339)
|
||||
}
|
||||
if o.CreatedBy != "" {
|
||||
query["created_by"] = o.CreatedBy
|
||||
}
|
||||
if o.AssignedBy != "" {
|
||||
query["assigned_by"] = o.AssignedBy
|
||||
}
|
||||
if o.MentionedBy != "" {
|
||||
query["mentioned_by"] = o.MentionedBy
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// AttachmentUploadOptions controls metadata sent when uploading an attachment.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.AttachmentUploadOptions{Name: "screenshot.png"}
|
||||
type AttachmentUploadOptions struct {
|
||||
Name string
|
||||
UpdatedAt *time.Time
|
||||
}
|
||||
|
||||
// String returns a safe summary of the attachment upload metadata.
|
||||
func (o AttachmentUploadOptions) String() string {
|
||||
return optionString("forge.AttachmentUploadOptions",
|
||||
"name", o.Name,
|
||||
"updated_at", o.UpdatedAt,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the attachment upload metadata.
|
||||
func (o AttachmentUploadOptions) GoString() string { return o.String() }
|
||||
|
||||
// RepoCommentListOptions controls filtering for repository-wide issue comment listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.RepoCommentListOptions{Page: 1, Limit: 50}
|
||||
type RepoCommentListOptions struct {
|
||||
Since *time.Time
|
||||
Before *time.Time
|
||||
}
|
||||
|
||||
// String returns a safe summary of the repository comment filters.
|
||||
func (o RepoCommentListOptions) String() string {
|
||||
return optionString("forge.RepoCommentListOptions",
|
||||
"since", o.Since,
|
||||
"before", o.Before,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the repository comment filters.
|
||||
func (o RepoCommentListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o RepoCommentListOptions) queryParams() map[string]string {
|
||||
query := make(map[string]string, 2)
|
||||
if o.Since != nil {
|
||||
query["since"] = o.Since.Format(time.RFC3339)
|
||||
}
|
||||
if o.Before != nil {
|
||||
query["before"] = o.Before.Format(time.RFC3339)
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func newIssueService(c *Client) *IssueService {
|
||||
return &IssueService{
|
||||
Resource: *NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](
|
||||
|
|
@ -160,285 +21,79 @@ func newIssueService(c *Client) *IssueService {
|
|||
}
|
||||
}
|
||||
|
||||
// SearchIssuesOptions controls filtering for the global issue search endpoint.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.SearchIssuesOptions{State: "open"}
|
||||
type SearchIssuesOptions struct {
|
||||
State string
|
||||
Labels string
|
||||
Milestones string
|
||||
Query string
|
||||
PriorityRepoID int64
|
||||
Type string
|
||||
Since *time.Time
|
||||
Before *time.Time
|
||||
Assigned bool
|
||||
Created bool
|
||||
Mentioned bool
|
||||
ReviewRequested bool
|
||||
Reviewed bool
|
||||
Owner string
|
||||
Team string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the issue search filters.
|
||||
func (o SearchIssuesOptions) String() string {
|
||||
return optionString("forge.SearchIssuesOptions",
|
||||
"state", o.State,
|
||||
"labels", o.Labels,
|
||||
"milestones", o.Milestones,
|
||||
"q", o.Query,
|
||||
"priority_repo_id", o.PriorityRepoID,
|
||||
"type", o.Type,
|
||||
"since", o.Since,
|
||||
"before", o.Before,
|
||||
"assigned", o.Assigned,
|
||||
"created", o.Created,
|
||||
"mentioned", o.Mentioned,
|
||||
"review_requested", o.ReviewRequested,
|
||||
"reviewed", o.Reviewed,
|
||||
"owner", o.Owner,
|
||||
"team", o.Team,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the issue search filters.
|
||||
func (o SearchIssuesOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o SearchIssuesOptions) queryParams() map[string]string {
|
||||
query := make(map[string]string, 12)
|
||||
if o.State != "" {
|
||||
query["state"] = o.State
|
||||
}
|
||||
if o.Labels != "" {
|
||||
query["labels"] = o.Labels
|
||||
}
|
||||
if o.Milestones != "" {
|
||||
query["milestones"] = o.Milestones
|
||||
}
|
||||
if o.Query != "" {
|
||||
query["q"] = o.Query
|
||||
}
|
||||
if o.PriorityRepoID != 0 {
|
||||
query["priority_repo_id"] = strconv.FormatInt(o.PriorityRepoID, 10)
|
||||
}
|
||||
if o.Type != "" {
|
||||
query["type"] = o.Type
|
||||
}
|
||||
if o.Since != nil {
|
||||
query["since"] = o.Since.Format(time.RFC3339)
|
||||
}
|
||||
if o.Before != nil {
|
||||
query["before"] = o.Before.Format(time.RFC3339)
|
||||
}
|
||||
if o.Assigned {
|
||||
query["assigned"] = strconv.FormatBool(true)
|
||||
}
|
||||
if o.Created {
|
||||
query["created"] = strconv.FormatBool(true)
|
||||
}
|
||||
if o.Mentioned {
|
||||
query["mentioned"] = strconv.FormatBool(true)
|
||||
}
|
||||
if o.ReviewRequested {
|
||||
query["review_requested"] = strconv.FormatBool(true)
|
||||
}
|
||||
if o.Reviewed {
|
||||
query["reviewed"] = strconv.FormatBool(true)
|
||||
}
|
||||
if o.Owner != "" {
|
||||
query["owner"] = o.Owner
|
||||
}
|
||||
if o.Team != "" {
|
||||
query["team"] = o.Team
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// SearchIssuesPage returns a single page of issues matching the search filters.
|
||||
func (s *IssueService) SearchIssuesPage(ctx context.Context, opts SearchIssuesOptions, pageOpts ListOptions) (*PagedResult[types.Issue], error) {
|
||||
return ListPage[types.Issue](ctx, s.client, "/api/v1/repos/issues/search", opts.queryParams(), pageOpts)
|
||||
}
|
||||
|
||||
// SearchIssues returns all issues matching the search filters.
|
||||
func (s *IssueService) SearchIssues(ctx context.Context, opts SearchIssuesOptions) ([]types.Issue, error) {
|
||||
return ListAll[types.Issue](ctx, s.client, "/api/v1/repos/issues/search", opts.queryParams())
|
||||
}
|
||||
|
||||
// IterSearchIssues returns an iterator over issues matching the search filters.
|
||||
func (s *IssueService) IterSearchIssues(ctx context.Context, opts SearchIssuesOptions) iter.Seq2[types.Issue, error] {
|
||||
return ListIter[types.Issue](ctx, s.client, "/api/v1/repos/issues/search", opts.queryParams())
|
||||
}
|
||||
|
||||
// ListIssues returns all issues in a repository.
|
||||
func (s *IssueService) ListIssues(ctx context.Context, owner, repo string, filters ...IssueListOptions) ([]types.Issue, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo))
|
||||
return ListAll[types.Issue](ctx, s.client, path, issueListQuery(filters...))
|
||||
}
|
||||
|
||||
// IterIssues returns an iterator over all issues in a repository.
|
||||
func (s *IssueService) IterIssues(ctx context.Context, owner, repo string, filters ...IssueListOptions) iter.Seq2[types.Issue, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo))
|
||||
return ListIter[types.Issue](ctx, s.client, path, issueListQuery(filters...))
|
||||
}
|
||||
|
||||
// CreateIssue creates a new issue in a repository.
|
||||
func (s *IssueService) CreateIssue(ctx context.Context, owner, repo string, opts *types.CreateIssueOption) (*types.Issue, error) {
|
||||
var out types.Issue
|
||||
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/issues", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// Pin pins an issue.
|
||||
func (s *IssueService) Pin(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", owner, repo, index)
|
||||
return s.client.Post(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// MovePin moves a pinned issue to a new position.
|
||||
func (s *IssueService) MovePin(ctx context.Context, owner, repo string, index, position int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin/{position}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "position", int64String(position)))
|
||||
return s.client.Patch(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// ListPinnedIssues returns all pinned issues in a repository.
|
||||
func (s *IssueService) ListPinnedIssues(ctx context.Context, owner, repo string) ([]types.Issue, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/pinned", pathParams("owner", owner, "repo", repo))
|
||||
return ListAll[types.Issue](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterPinnedIssues returns an iterator over all pinned issues in a repository.
|
||||
func (s *IssueService) IterPinnedIssues(ctx context.Context, owner, repo string) iter.Seq2[types.Issue, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/pinned", pathParams("owner", owner, "repo", repo))
|
||||
return ListIter[types.Issue](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// Unpin unpins an issue.
|
||||
func (s *IssueService) Unpin(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", owner, repo, index)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// SetDeadline sets or updates the deadline on an issue.
|
||||
func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, index int64, deadline string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/deadline", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/deadline", owner, repo, index)
|
||||
body := map[string]string{"due_date": deadline}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// AddReaction adds a reaction to an issue.
|
||||
func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
body := types.EditReactionOption{Reaction: reaction}
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index)
|
||||
body := map[string]string{"content": reaction}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// ListReactions returns all reactions on an issue.
|
||||
func (s *IssueService) ListReactions(ctx context.Context, owner, repo string, index int64) ([]types.Reaction, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.Reaction](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterReactions returns an iterator over all reactions on an issue.
|
||||
func (s *IssueService) IterReactions(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Reaction, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.Reaction](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// DeleteReaction removes a reaction from an issue.
|
||||
func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, index int64, reaction string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
body := types.EditReactionOption{Reaction: reaction}
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index)
|
||||
body := map[string]string{"content": reaction}
|
||||
return s.client.DeleteWithBody(ctx, path, body)
|
||||
}
|
||||
|
||||
// StartStopwatch starts the stopwatch on an issue.
|
||||
func (s *IssueService) StartStopwatch(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/start", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index)
|
||||
return s.client.Post(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// StopStopwatch stops the stopwatch on an issue.
|
||||
func (s *IssueService) StopStopwatch(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/stop", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index)
|
||||
return s.client.Post(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// DeleteStopwatch deletes an issue's existing stopwatch.
|
||||
func (s *IssueService) DeleteStopwatch(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/delete", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListTimes returns all tracked times on an issue.
|
||||
func (s *IssueService) ListTimes(ctx context.Context, owner, repo string, index int64, user string, since, before *time.Time) ([]types.TrackedTime, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/times", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.TrackedTime](ctx, s.client, path, issueTimeQuery(user, since, before))
|
||||
}
|
||||
|
||||
// IterTimes returns an iterator over all tracked times on an issue.
|
||||
func (s *IssueService) IterTimes(ctx context.Context, owner, repo string, index int64, user string, since, before *time.Time) iter.Seq2[types.TrackedTime, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/times", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.TrackedTime](ctx, s.client, path, issueTimeQuery(user, since, before))
|
||||
}
|
||||
|
||||
// AddTime adds tracked time to an issue.
|
||||
func (s *IssueService) AddTime(ctx context.Context, owner, repo string, index int64, opts *types.AddTimeOption) (*types.TrackedTime, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/times", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
var out types.TrackedTime
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ResetTime removes all tracked time from an issue.
|
||||
func (s *IssueService) ResetTime(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/times", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// DeleteTime removes a specific tracked time entry from an issue.
|
||||
func (s *IssueService) DeleteTime(ctx context.Context, owner, repo string, index, timeID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/times/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(timeID)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// AddLabels adds labels to an issue.
|
||||
func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index int64, labelIDs []int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/labels", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner, repo, index)
|
||||
body := types.IssueLabelsOption{Labels: toAnySlice(labelIDs)}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// RemoveLabel removes a single label from an issue.
|
||||
func (s *IssueService) RemoveLabel(ctx context.Context, owner, repo string, index int64, labelID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/labels/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(labelID)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, labelID)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListComments returns all comments on an issue.
|
||||
func (s *IssueService) ListComments(ctx context.Context, owner, repo string, index int64) ([]types.Comment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
|
||||
return ListAll[types.Comment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterComments returns an iterator over all comments on an issue.
|
||||
func (s *IssueService) IterComments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Comment, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
|
||||
return ListIter[types.Comment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CreateComment creates a comment on an issue.
|
||||
func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, index int64, body string) (*types.Comment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
|
||||
opts := types.CreateIssueCommentOption{Body: body}
|
||||
var out types.Comment
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
|
|
@ -447,328 +102,6 @@ func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, in
|
|||
return &out, nil
|
||||
}
|
||||
|
||||
// EditComment updates an issue comment.
|
||||
func (s *IssueService) EditComment(ctx context.Context, owner, repo string, index, id int64, opts *types.EditIssueCommentOption) (*types.Comment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(id)))
|
||||
var out types.Comment
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteComment deletes an issue comment.
|
||||
func (s *IssueService) DeleteComment(ctx context.Context, owner, repo string, index, id int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(id)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListRepoComments returns all comments in a repository.
|
||||
func (s *IssueService) ListRepoComments(ctx context.Context, owner, repo string, filters ...RepoCommentListOptions) ([]types.Comment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments", pathParams("owner", owner, "repo", repo))
|
||||
return ListAll[types.Comment](ctx, s.client, path, repoCommentQuery(filters...))
|
||||
}
|
||||
|
||||
// IterRepoComments returns an iterator over all comments in a repository.
|
||||
func (s *IssueService) IterRepoComments(ctx context.Context, owner, repo string, filters ...RepoCommentListOptions) iter.Seq2[types.Comment, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments", pathParams("owner", owner, "repo", repo))
|
||||
return ListIter[types.Comment](ctx, s.client, path, repoCommentQuery(filters...))
|
||||
}
|
||||
|
||||
// GetRepoComment returns a single comment in a repository.
|
||||
func (s *IssueService) GetRepoComment(ctx context.Context, owner, repo string, id int64) (*types.Comment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
var out types.Comment
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// EditRepoComment updates a repository comment.
|
||||
func (s *IssueService) EditRepoComment(ctx context.Context, owner, repo string, id int64, opts *types.EditIssueCommentOption) (*types.Comment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
var out types.Comment
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteRepoComment deletes a repository comment.
|
||||
func (s *IssueService) DeleteRepoComment(ctx context.Context, owner, repo string, id int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListCommentReactions returns all reactions on an issue comment.
|
||||
func (s *IssueService) ListCommentReactions(ctx context.Context, owner, repo string, id int64) ([]types.Reaction, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return ListAll[types.Reaction](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterCommentReactions returns an iterator over all reactions on an issue comment.
|
||||
func (s *IssueService) IterCommentReactions(ctx context.Context, owner, repo string, id int64) iter.Seq2[types.Reaction, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return ListIter[types.Reaction](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// AddCommentReaction adds a reaction to an issue comment.
|
||||
func (s *IssueService) AddCommentReaction(ctx context.Context, owner, repo string, id int64, reaction string) (*types.Reaction, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
var out types.Reaction
|
||||
if err := s.client.Post(ctx, path, types.EditReactionOption{Reaction: reaction}, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteCommentReaction removes a reaction from an issue comment.
|
||||
func (s *IssueService) DeleteCommentReaction(ctx context.Context, owner, repo string, id int64, reaction string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/reactions", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return s.client.DeleteWithBody(ctx, path, types.EditReactionOption{Reaction: reaction})
|
||||
}
|
||||
|
||||
func issueListQuery(filters ...IssueListOptions) map[string]string {
|
||||
query := make(map[string]string, len(filters))
|
||||
for _, filter := range filters {
|
||||
for key, value := range filter.queryParams() {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func attachmentUploadQuery(opts *AttachmentUploadOptions) map[string]string {
|
||||
if opts == nil {
|
||||
return nil
|
||||
}
|
||||
query := make(map[string]string, 2)
|
||||
if opts.Name != "" {
|
||||
query["name"] = opts.Name
|
||||
}
|
||||
if opts.UpdatedAt != nil {
|
||||
query["updated_at"] = opts.UpdatedAt.Format(time.RFC3339)
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func (s *IssueService) createAttachment(ctx context.Context, path string, opts *AttachmentUploadOptions, filename string, content goio.Reader) (*types.Attachment, error) {
|
||||
var out types.Attachment
|
||||
if err := s.client.postMultipartJSON(ctx, path, attachmentUploadQuery(opts), nil, "attachment", filename, content, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateAttachment uploads a new attachment to an issue.
|
||||
func (s *IssueService) CreateAttachment(ctx context.Context, owner, repo string, index int64, opts *AttachmentUploadOptions, filename string, content goio.Reader) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/assets", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.createAttachment(ctx, path, opts, filename, content)
|
||||
}
|
||||
|
||||
// ListAttachments returns all attachments on an issue.
|
||||
func (s *IssueService) ListAttachments(ctx context.Context, owner, repo string, index int64) ([]types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/assets", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.Attachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterAttachments returns an iterator over all attachments on an issue.
|
||||
func (s *IssueService) IterAttachments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Attachment, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/assets", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.Attachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetAttachment returns a single attachment on an issue.
|
||||
func (s *IssueService) GetAttachment(ctx context.Context, owner, repo string, index, attachmentID int64) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/assets/{attachment_id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "attachment_id", int64String(attachmentID)))
|
||||
var out types.Attachment
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// EditAttachment updates an issue attachment.
|
||||
func (s *IssueService) EditAttachment(ctx context.Context, owner, repo string, index, attachmentID int64, opts *types.EditAttachmentOptions) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/assets/{attachment_id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "attachment_id", int64String(attachmentID)))
|
||||
var out types.Attachment
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteAttachment removes an issue attachment.
|
||||
func (s *IssueService) DeleteAttachment(ctx context.Context, owner, repo string, index, attachmentID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/assets/{attachment_id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "attachment_id", int64String(attachmentID)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListCommentAttachments returns all attachments on an issue comment.
|
||||
func (s *IssueService) ListCommentAttachments(ctx context.Context, owner, repo string, id int64) ([]types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return ListAll[types.Attachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterCommentAttachments returns an iterator over all attachments on an issue comment.
|
||||
func (s *IssueService) IterCommentAttachments(ctx context.Context, owner, repo string, id int64) iter.Seq2[types.Attachment, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return ListIter[types.Attachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetCommentAttachment returns a single attachment on an issue comment.
|
||||
func (s *IssueService) GetCommentAttachment(ctx context.Context, owner, repo string, id, attachmentID int64) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/assets/{attachment_id}", pathParams("owner", owner, "repo", repo, "id", int64String(id), "attachment_id", int64String(attachmentID)))
|
||||
var out types.Attachment
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateCommentAttachment uploads a new attachment to an issue comment.
|
||||
func (s *IssueService) CreateCommentAttachment(ctx context.Context, owner, repo string, id int64, opts *AttachmentUploadOptions, filename string, content goio.Reader) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return s.createAttachment(ctx, path, opts, filename, content)
|
||||
}
|
||||
|
||||
// EditCommentAttachment updates an issue comment attachment.
|
||||
func (s *IssueService) EditCommentAttachment(ctx context.Context, owner, repo string, id, attachmentID int64, opts *types.EditAttachmentOptions) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/assets/{attachment_id}", pathParams("owner", owner, "repo", repo, "id", int64String(id), "attachment_id", int64String(attachmentID)))
|
||||
var out types.Attachment
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteCommentAttachment removes an issue comment attachment.
|
||||
func (s *IssueService) DeleteCommentAttachment(ctx context.Context, owner, repo string, id, attachmentID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/comments/{id}/assets/{attachment_id}", pathParams("owner", owner, "repo", repo, "id", int64String(id), "attachment_id", int64String(attachmentID)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListTimeline returns all comments and events on an issue.
|
||||
func (s *IssueService) ListTimeline(ctx context.Context, owner, repo string, index int64, since, before *time.Time) ([]types.TimelineComment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/timeline", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
query := make(map[string]string, 2)
|
||||
if since != nil {
|
||||
query["since"] = since.Format(time.RFC3339)
|
||||
}
|
||||
if before != nil {
|
||||
query["before"] = before.Format(time.RFC3339)
|
||||
}
|
||||
if len(query) == 0 {
|
||||
query = nil
|
||||
}
|
||||
return ListAll[types.TimelineComment](ctx, s.client, path, query)
|
||||
}
|
||||
|
||||
// IterTimeline returns an iterator over all comments and events on an issue.
|
||||
func (s *IssueService) IterTimeline(ctx context.Context, owner, repo string, index int64, since, before *time.Time) iter.Seq2[types.TimelineComment, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/timeline", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
query := make(map[string]string, 2)
|
||||
if since != nil {
|
||||
query["since"] = since.Format(time.RFC3339)
|
||||
}
|
||||
if before != nil {
|
||||
query["before"] = before.Format(time.RFC3339)
|
||||
}
|
||||
if len(query) == 0 {
|
||||
query = nil
|
||||
}
|
||||
return ListIter[types.TimelineComment](ctx, s.client, path, query)
|
||||
}
|
||||
|
||||
// ListSubscriptions returns all users subscribed to an issue.
|
||||
func (s *IssueService) ListSubscriptions(ctx context.Context, owner, repo string, index int64) ([]types.User, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterSubscriptions returns an iterator over all users subscribed to an issue.
|
||||
func (s *IssueService) IterSubscriptions(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.User, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CheckSubscription returns the authenticated user's subscription state for an issue.
|
||||
func (s *IssueService) CheckSubscription(ctx context.Context, owner, repo string, index int64) (*types.WatchInfo, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/check", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
var out types.WatchInfo
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// SubscribeUser subscribes a user to an issue.
|
||||
func (s *IssueService) SubscribeUser(ctx context.Context, owner, repo string, index int64, user string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/{user}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "user", user))
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// UnsubscribeUser unsubscribes a user from an issue.
|
||||
func (s *IssueService) UnsubscribeUser(ctx context.Context, owner, repo string, index int64, user string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/subscriptions/{user}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "user", user))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListDependencies returns all issues that block the given issue.
|
||||
func (s *IssueService) ListDependencies(ctx context.Context, owner, repo string, index int64) ([]types.Issue, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/dependencies", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.Issue](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterDependencies returns an iterator over all issues that block the given issue.
|
||||
func (s *IssueService) IterDependencies(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Issue, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/dependencies", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.Issue](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// AddDependency makes another issue block the issue at the given path.
|
||||
func (s *IssueService) AddDependency(ctx context.Context, owner, repo string, index int64, dependency types.IssueMeta) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/dependencies", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.Post(ctx, path, dependency, nil)
|
||||
}
|
||||
|
||||
// RemoveDependency removes an issue dependency from the issue at the given path.
|
||||
func (s *IssueService) RemoveDependency(ctx context.Context, owner, repo string, index int64, dependency types.IssueMeta) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/dependencies", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.DeleteWithBody(ctx, path, dependency)
|
||||
}
|
||||
|
||||
// ListBlocks returns all issues blocked by the given issue.
|
||||
func (s *IssueService) ListBlocks(ctx context.Context, owner, repo string, index int64) ([]types.Issue, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/blocks", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.Issue](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterBlocks returns an iterator over all issues blocked by the given issue.
|
||||
func (s *IssueService) IterBlocks(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Issue, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/blocks", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.Issue](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// AddBlock makes the issue at the given path block another issue.
|
||||
func (s *IssueService) AddBlock(ctx context.Context, owner, repo string, index int64, blockedIssue types.IssueMeta) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/blocks", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.Post(ctx, path, blockedIssue, nil)
|
||||
}
|
||||
|
||||
// RemoveBlock removes an issue block from the issue at the given path.
|
||||
func (s *IssueService) RemoveBlock(ctx context.Context, owner, repo string, index int64, blockedIssue types.IssueMeta) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/blocks", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.DeleteWithBody(ctx, path, blockedIssue)
|
||||
}
|
||||
|
||||
// toAnySlice converts a slice of int64 to a slice of any for IssueLabelsOption.
|
||||
func toAnySlice(ids []int64) []any {
|
||||
out := make([]any, len(ids))
|
||||
|
|
@ -777,37 +110,3 @@ func toAnySlice(ids []int64) []any {
|
|||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func repoCommentQuery(filters ...RepoCommentListOptions) map[string]string {
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := make(map[string]string, 2)
|
||||
for _, filter := range filters {
|
||||
for key, value := range filter.queryParams() {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func issueTimeQuery(user string, since, before *time.Time) map[string]string {
|
||||
query := make(map[string]string, 3)
|
||||
if user != "" {
|
||||
query["user"] = user
|
||||
}
|
||||
if since != nil {
|
||||
query["since"] = since.Format(time.RFC3339)
|
||||
}
|
||||
if before != nil {
|
||||
query["before"] = before.Format(time.RFC3339)
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestIssueService_ListIssues_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/issues" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Issue{{ID: 1, Title: "bug"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
issues, err := f.Issues.ListIssues(context.Background(), "core", "go-forge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(issues) != 1 || issues[0].Title != "bug" {
|
||||
t.Fatalf("got %#v", issues)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIssueService_CreateIssue_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/issues" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.CreateIssueOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Title != "new issue" {
|
||||
t.Fatalf("unexpected body: %+v", body)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Issue{ID: 1, Index: 1, Title: body.Title})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
issue, err := f.Issues.CreateIssue(context.Background(), "core", "go-forge", &types.CreateIssueOption{Title: "new issue"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if issue.Title != "new issue" {
|
||||
t.Fatalf("got title=%q", issue.Title)
|
||||
}
|
||||
}
|
||||
1365
issues_test.go
1365
issues_test.go
File diff suppressed because it is too large
Load diff
85
labels.go
85
labels.go
|
|
@ -2,6 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
|
|
@ -9,11 +10,6 @@ import (
|
|||
|
||||
// LabelService handles repository labels, organisation labels, and issue labels.
|
||||
// No Resource embedding — paths are heterogeneous.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Labels.ListRepoLabels(ctx, "core", "go-forge")
|
||||
type LabelService struct {
|
||||
client *Client
|
||||
}
|
||||
|
|
@ -24,19 +20,19 @@ func newLabelService(c *Client) *LabelService {
|
|||
|
||||
// ListRepoLabels returns all labels for a repository.
|
||||
func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) ([]types.Label, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
|
||||
return ListAll[types.Label](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterRepoLabels returns an iterator over all labels for a repository.
|
||||
func (s *LabelService) IterRepoLabels(ctx context.Context, owner, repo string) iter.Seq2[types.Label, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
|
||||
return ListIter[types.Label](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetRepoLabel returns a single label by ID.
|
||||
func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id int64) (*types.Label, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
|
||||
var out types.Label
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -46,7 +42,7 @@ func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id
|
|||
|
||||
// CreateRepoLabel creates a new label in a repository.
|
||||
func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string, opts *types.CreateLabelOption) (*types.Label, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
|
||||
var out types.Label
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -56,7 +52,7 @@ func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string,
|
|||
|
||||
// EditRepoLabel updates an existing label in a repository.
|
||||
func (s *LabelService) EditRepoLabel(ctx context.Context, owner, repo string, id int64, opts *types.EditLabelOption) (*types.Label, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
|
||||
var out types.Label
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -66,89 +62,28 @@ func (s *LabelService) EditRepoLabel(ctx context.Context, owner, repo string, id
|
|||
|
||||
// DeleteRepoLabel deletes a label from a repository.
|
||||
func (s *LabelService) DeleteRepoLabel(ctx context.Context, owner, repo string, id int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListOrgLabels returns all labels for an organisation.
|
||||
func (s *LabelService) ListOrgLabels(ctx context.Context, org string) ([]types.Label, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/labels", org)
|
||||
return ListAll[types.Label](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterOrgLabels returns an iterator over all labels for an organisation.
|
||||
func (s *LabelService) IterOrgLabels(ctx context.Context, org string) iter.Seq2[types.Label, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/labels", org)
|
||||
return ListIter[types.Label](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CreateOrgLabel creates a new label in an organisation.
|
||||
func (s *LabelService) CreateOrgLabel(ctx context.Context, org string, opts *types.CreateLabelOption) (*types.Label, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/labels", org)
|
||||
var out types.Label
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetOrgLabel returns a single label for an organisation.
|
||||
func (s *LabelService) GetOrgLabel(ctx context.Context, org string, id int64) (*types.Label, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/labels/{id}", pathParams("org", org, "id", int64String(id)))
|
||||
var out types.Label
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// EditOrgLabel updates an existing label in an organisation.
|
||||
func (s *LabelService) EditOrgLabel(ctx context.Context, org string, id int64, opts *types.EditLabelOption) (*types.Label, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/labels/{id}", pathParams("org", org, "id", int64String(id)))
|
||||
var out types.Label
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteOrgLabel deletes a label from an organisation.
|
||||
func (s *LabelService) DeleteOrgLabel(ctx context.Context, org string, id int64) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/labels/{id}", pathParams("org", org, "id", int64String(id)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListLabelTemplates returns all available label template names.
|
||||
func (s *LabelService) ListLabelTemplates(ctx context.Context) ([]string, error) {
|
||||
var out []string
|
||||
if err := s.client.Get(ctx, "/api/v1/label/templates", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// IterLabelTemplates returns an iterator over all available label template names.
|
||||
func (s *LabelService) IterLabelTemplates(ctx context.Context) iter.Seq2[string, error] {
|
||||
return func(yield func(string, error) bool) {
|
||||
items, err := s.ListLabelTemplates(ctx)
|
||||
if err != nil {
|
||||
yield("", err)
|
||||
return
|
||||
}
|
||||
for _, item := range items {
|
||||
if !yield(item, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetLabelTemplate returns all labels for a label template.
|
||||
func (s *LabelService) GetLabelTemplate(ctx context.Context, name string) ([]types.LabelTemplate, error) {
|
||||
path := ResolvePath("/api/v1/label/templates/{name}", pathParams("name", name))
|
||||
var out []types.LabelTemplate
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestLabelService_ListRepoLabels_Good(t *testing.T) {
|
||||
func TestLabelService_Good_ListRepoLabels(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -42,7 +42,7 @@ func TestLabelService_ListRepoLabels_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabelService_CreateRepoLabel_Good(t *testing.T) {
|
||||
func TestLabelService_Good_CreateRepoLabel(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -84,7 +84,7 @@ func TestLabelService_CreateRepoLabel_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabelService_GetRepoLabel_Good(t *testing.T) {
|
||||
func TestLabelService_Good_GetRepoLabel(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -106,7 +106,7 @@ func TestLabelService_GetRepoLabel_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabelService_EditRepoLabel_Good(t *testing.T) {
|
||||
func TestLabelService_Good_EditRepoLabel(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
|
|
@ -135,7 +135,7 @@ func TestLabelService_EditRepoLabel_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabelService_DeleteRepoLabel_Good(t *testing.T) {
|
||||
func TestLabelService_Good_DeleteRepoLabel(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -154,7 +154,7 @@ func TestLabelService_DeleteRepoLabel_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabelService_ListOrgLabels_Good(t *testing.T) {
|
||||
func TestLabelService_Good_ListOrgLabels(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -182,7 +182,7 @@ func TestLabelService_ListOrgLabels_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabelService_CreateOrgLabel_Good(t *testing.T) {
|
||||
func TestLabelService_Good_CreateOrgLabel(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -214,7 +214,7 @@ func TestLabelService_CreateOrgLabel_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestLabelService_NotFound_Bad(t *testing.T) {
|
||||
func TestLabelService_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "label not found"})
|
||||
|
|
|
|||
|
|
@ -2,49 +2,12 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"fmt"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// MilestoneListOptions controls filtering for repository milestone listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.MilestoneListOptions{State: "open"}
|
||||
type MilestoneListOptions struct {
|
||||
State string
|
||||
Name string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the milestone filters.
|
||||
func (o MilestoneListOptions) String() string {
|
||||
return optionString("forge.MilestoneListOptions", "state", o.State, "name", o.Name)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the milestone filters.
|
||||
func (o MilestoneListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o MilestoneListOptions) queryParams() map[string]string {
|
||||
query := make(map[string]string, 2)
|
||||
if o.State != "" {
|
||||
query["state"] = o.State
|
||||
}
|
||||
if o.Name != "" {
|
||||
query["name"] = o.Name
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// MilestoneService handles repository milestones.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Milestones.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
type MilestoneService struct {
|
||||
client *Client
|
||||
}
|
||||
|
|
@ -53,27 +16,15 @@ func newMilestoneService(c *Client) *MilestoneService {
|
|||
return &MilestoneService{client: c}
|
||||
}
|
||||
|
||||
// List returns a single page of milestones for a repository.
|
||||
func (s *MilestoneService) List(ctx context.Context, params Params, opts ListOptions, filters ...MilestoneListOptions) (*PagedResult[types.Milestone], error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
|
||||
return ListPage[types.Milestone](ctx, s.client, path, milestoneQuery(filters...), opts)
|
||||
}
|
||||
|
||||
// Iter returns an iterator over all milestones for a repository.
|
||||
func (s *MilestoneService) Iter(ctx context.Context, params Params, filters ...MilestoneListOptions) iter.Seq2[types.Milestone, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
|
||||
return ListIter[types.Milestone](ctx, s.client, path, milestoneQuery(filters...))
|
||||
}
|
||||
|
||||
// ListAll returns all milestones for a repository.
|
||||
func (s *MilestoneService) ListAll(ctx context.Context, params Params, filters ...MilestoneListOptions) ([]types.Milestone, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
|
||||
return ListAll[types.Milestone](ctx, s.client, path, milestoneQuery(filters...))
|
||||
func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error) {
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones", params["owner"], params["repo"])
|
||||
return ListAll[types.Milestone](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// Get returns a single milestone by ID.
|
||||
func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d", owner, repo, id)
|
||||
var out types.Milestone
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -83,43 +34,10 @@ func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64
|
|||
|
||||
// Create creates a new milestone.
|
||||
func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones", owner, repo)
|
||||
var out types.Milestone
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// Edit updates an existing milestone.
|
||||
func (s *MilestoneService) Edit(ctx context.Context, owner, repo string, id int64, opts *types.EditMilestoneOption) (*types.Milestone, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
var out types.Milestone
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// Delete removes a milestone.
|
||||
func (s *MilestoneService) Delete(ctx context.Context, owner, repo string, id int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
func milestoneQuery(filters ...MilestoneListOptions) map[string]string {
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := make(map[string]string, 2)
|
||||
for _, filter := range filters {
|
||||
for key, value := range filter.queryParams() {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,273 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestMilestoneService_List_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("page"); got != "1" {
|
||||
t.Errorf("got page=%q, want %q", got, "1")
|
||||
}
|
||||
if got := r.URL.Query().Get("limit"); got != "1" {
|
||||
t.Errorf("got limit=%q, want %q", got, "1")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.Milestone{{ID: 2, Title: "v2.0"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
page, err := f.Milestones.List(context.Background(), Params{"owner": "core", "repo": "go-forge"}, ListOptions{Page: 1, Limit: 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if page.Page != 1 {
|
||||
t.Errorf("got page=%d, want 1", page.Page)
|
||||
}
|
||||
if page.TotalCount != 2 {
|
||||
t.Errorf("got total=%d, want 2", page.TotalCount)
|
||||
}
|
||||
if !page.HasMore {
|
||||
t.Error("expected HasMore=true")
|
||||
}
|
||||
if len(page.Items) != 1 || page.Items[0].Title != "v2.0" {
|
||||
t.Fatalf("unexpected items: %+v", page.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilestoneService_ListWithFilters_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("state"); got != "all" {
|
||||
t.Errorf("got state=%q, want %q", got, "all")
|
||||
}
|
||||
if got := r.URL.Query().Get("name"); got != "v1.0" {
|
||||
t.Errorf("got name=%q, want %q", got, "v1.0")
|
||||
}
|
||||
if got := r.URL.Query().Get("page"); got != "1" {
|
||||
t.Errorf("got page=%q, want %q", got, "1")
|
||||
}
|
||||
if got := r.URL.Query().Get("limit"); got != "1" {
|
||||
t.Errorf("got limit=%q, want %q", got, "1")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Milestone{{ID: 1, Title: "v1.0"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
page, err := f.Milestones.List(
|
||||
context.Background(),
|
||||
Params{"owner": "core", "repo": "go-forge"},
|
||||
ListOptions{Page: 1, Limit: 1},
|
||||
MilestoneListOptions{State: "all", Name: "v1.0"},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(page.Items) != 1 || page.Items[0].Title != "v1.0" {
|
||||
t.Fatalf("unexpected items: %+v", page.Items)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilestoneService_Iter_Good(t *testing.T) {
|
||||
requests := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
switch requests {
|
||||
case 1:
|
||||
if got := r.URL.Query().Get("page"); got != "1" {
|
||||
t.Errorf("got page=%q, want %q", got, "1")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.Milestone{{ID: 1, Title: "v1.0"}})
|
||||
case 2:
|
||||
if got := r.URL.Query().Get("page"); got != "2" {
|
||||
t.Errorf("got page=%q, want %q", got, "2")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.Milestone{{ID: 2, Title: "v2.0"}})
|
||||
default:
|
||||
t.Fatalf("unexpected request %d", requests)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []string
|
||||
for milestone, err := range f.Milestones.Iter(context.Background(), Params{"owner": "core", "repo": "go-forge"}) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, milestone.Title)
|
||||
}
|
||||
if !reflect.DeepEqual(got, []string{"v1.0", "v2.0"}) {
|
||||
t.Fatalf("got %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilestoneService_ListAll_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.Milestone{
|
||||
{ID: 1, Title: "v1.0"},
|
||||
{ID: 2, Title: "v2.0"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
milestones, err := f.Milestones.ListAll(context.Background(), Params{"owner": "core", "repo": "go-forge"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(milestones) != 2 {
|
||||
t.Errorf("got %d milestones, want 2", len(milestones))
|
||||
}
|
||||
if milestones[0].Title != "v1.0" {
|
||||
t.Errorf("got title=%q, want %q", milestones[0].Title, "v1.0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilestoneService_Get_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones/7" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Milestone{ID: 7, Title: "v1.0"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
milestone, err := f.Milestones.Get(context.Background(), "core", "go-forge", 7)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if milestone.ID != 7 {
|
||||
t.Errorf("got id=%d, want 7", milestone.ID)
|
||||
}
|
||||
if milestone.Title != "v1.0" {
|
||||
t.Errorf("got title=%q, want %q", milestone.Title, "v1.0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilestoneService_Create_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.CreateMilestoneOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Title != "v1.0" {
|
||||
t.Errorf("got title=%q, want %q", opts.Title, "v1.0")
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(types.Milestone{ID: 3, Title: opts.Title})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
milestone, err := f.Milestones.Create(context.Background(), "core", "go-forge", &types.CreateMilestoneOption{
|
||||
Title: "v1.0",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if milestone.ID != 3 {
|
||||
t.Errorf("got id=%d, want 3", milestone.ID)
|
||||
}
|
||||
if milestone.Title != "v1.0" {
|
||||
t.Errorf("got title=%q, want %q", milestone.Title, "v1.0")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilestoneService_Edit_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones/3" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.EditMilestoneOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Title != "v1.1" {
|
||||
t.Errorf("got title=%q, want %q", opts.Title, "v1.1")
|
||||
}
|
||||
|
||||
json.NewEncoder(w).Encode(types.Milestone{ID: 3, Title: opts.Title})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
milestone, err := f.Milestones.Edit(context.Background(), "core", "go-forge", 3, &types.EditMilestoneOption{
|
||||
Title: "v1.1",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if milestone.ID != 3 {
|
||||
t.Errorf("got id=%d, want 3", milestone.ID)
|
||||
}
|
||||
if milestone.Title != "v1.1" {
|
||||
t.Errorf("got title=%q, want %q", milestone.Title, "v1.1")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMilestoneService_Delete_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/milestones/3" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Milestones.Delete(context.Background(), "core", "go-forge", 3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
109
misc.go
109
misc.go
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"fmt"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
|
@ -11,11 +11,6 @@ import (
|
|||
// markdown rendering, licence templates, gitignore templates, and
|
||||
// server metadata.
|
||||
// No Resource embedding — heterogeneous read-only endpoints.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Misc.GetVersion(ctx)
|
||||
type MiscService struct {
|
||||
client *Client
|
||||
}
|
||||
|
|
@ -35,27 +30,6 @@ func (s *MiscService) RenderMarkdown(ctx context.Context, text, mode string) (st
|
|||
return string(data), nil
|
||||
}
|
||||
|
||||
// RenderMarkup renders markup text to HTML. The response is raw HTML text,
|
||||
// not JSON.
|
||||
func (s *MiscService) RenderMarkup(ctx context.Context, text, mode string) (string, error) {
|
||||
body := types.MarkupOption{Text: text, Mode: mode}
|
||||
data, err := s.client.PostRaw(ctx, "/api/v1/markup", body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// RenderMarkdownRaw renders raw markdown text to HTML. The request body is
|
||||
// sent as text/plain and the response is raw HTML text, not JSON.
|
||||
func (s *MiscService) RenderMarkdownRaw(ctx context.Context, text string) (string, error) {
|
||||
data, err := s.client.postRawText(ctx, "/api/v1/markdown/raw", text)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// ListLicenses returns all available licence templates.
|
||||
func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplateListEntry, error) {
|
||||
var out []types.LicensesTemplateListEntry
|
||||
|
|
@ -65,25 +39,9 @@ func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplat
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// IterLicenses returns an iterator over all available licence templates.
|
||||
func (s *MiscService) IterLicenses(ctx context.Context) iter.Seq2[types.LicensesTemplateListEntry, error] {
|
||||
return func(yield func(types.LicensesTemplateListEntry, error) bool) {
|
||||
items, err := s.ListLicenses(ctx)
|
||||
if err != nil {
|
||||
yield(*new(types.LicensesTemplateListEntry), err)
|
||||
return
|
||||
}
|
||||
for _, item := range items {
|
||||
if !yield(item, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetLicense returns a single licence template by name.
|
||||
func (s *MiscService) GetLicense(ctx context.Context, name string) (*types.LicenseTemplateInfo, error) {
|
||||
path := ResolvePath("/api/v1/licenses/{name}", pathParams("name", name))
|
||||
path := fmt.Sprintf("/api/v1/licenses/%s", name)
|
||||
var out types.LicenseTemplateInfo
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -100,25 +58,9 @@ func (s *MiscService) ListGitignoreTemplates(ctx context.Context) ([]string, err
|
|||
return out, nil
|
||||
}
|
||||
|
||||
// IterGitignoreTemplates returns an iterator over all available gitignore template names.
|
||||
func (s *MiscService) IterGitignoreTemplates(ctx context.Context) iter.Seq2[string, error] {
|
||||
return func(yield func(string, error) bool) {
|
||||
items, err := s.ListGitignoreTemplates(ctx)
|
||||
if err != nil {
|
||||
yield("", err)
|
||||
return
|
||||
}
|
||||
for _, item := range items {
|
||||
if !yield(item, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GetGitignoreTemplate returns a single gitignore template by name.
|
||||
func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*types.GitignoreTemplateInfo, error) {
|
||||
path := ResolvePath("/api/v1/gitignore/templates/{name}", pathParams("name", name))
|
||||
path := fmt.Sprintf("/api/v1/gitignore/templates/%s", name)
|
||||
var out types.GitignoreTemplateInfo
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -135,51 +77,6 @@ func (s *MiscService) GetNodeInfo(ctx context.Context) (*types.NodeInfo, error)
|
|||
return &out, nil
|
||||
}
|
||||
|
||||
// GetSigningKey returns the instance's default signing key.
|
||||
func (s *MiscService) GetSigningKey(ctx context.Context) (string, error) {
|
||||
data, err := s.client.GetRaw(ctx, "/api/v1/signing-key.gpg")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(data), nil
|
||||
}
|
||||
|
||||
// GetAPISettings returns the instance's global API settings.
|
||||
func (s *MiscService) GetAPISettings(ctx context.Context) (*types.GeneralAPISettings, error) {
|
||||
var out types.GeneralAPISettings
|
||||
if err := s.client.Get(ctx, "/api/v1/settings/api", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetAttachmentSettings returns the instance's global attachment settings.
|
||||
func (s *MiscService) GetAttachmentSettings(ctx context.Context) (*types.GeneralAttachmentSettings, error) {
|
||||
var out types.GeneralAttachmentSettings
|
||||
if err := s.client.Get(ctx, "/api/v1/settings/attachment", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetRepositorySettings returns the instance's global repository settings.
|
||||
func (s *MiscService) GetRepositorySettings(ctx context.Context) (*types.GeneralRepoSettings, error) {
|
||||
var out types.GeneralRepoSettings
|
||||
if err := s.client.Get(ctx, "/api/v1/settings/repository", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetUISettings returns the instance's global UI settings.
|
||||
func (s *MiscService) GetUISettings(ctx context.Context) (*types.GeneralUISettings, error) {
|
||||
var out types.GeneralUISettings
|
||||
if err := s.client.Get(ctx, "/api/v1/settings/ui", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetVersion returns the server version.
|
||||
func (s *MiscService) GetVersion(ctx context.Context) (*types.ServerVersion, error) {
|
||||
var out types.ServerVersion
|
||||
|
|
|
|||
290
misc_test.go
290
misc_test.go
|
|
@ -2,17 +2,15 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"io"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestMiscService_RenderMarkdown_Good(t *testing.T) {
|
||||
func TestMiscService_Good_RenderMarkdown(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -46,85 +44,7 @@ func TestMiscService_RenderMarkdown_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiscService_RenderMarkup_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/markup" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.MarkupOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Text != "**Hello**" {
|
||||
t.Errorf("got text=%q, want %q", opts.Text, "**Hello**")
|
||||
}
|
||||
if opts.Mode != "gfm" {
|
||||
t.Errorf("got mode=%q, want %q", opts.Mode, "gfm")
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte("<p><strong>Hello</strong></p>\n"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
html, err := f.Misc.RenderMarkup(context.Background(), "**Hello**", "gfm")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "<p><strong>Hello</strong></p>\n"
|
||||
if html != want {
|
||||
t.Errorf("got %q, want %q", html, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_RenderMarkdownRaw_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/markdown/raw" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.Header.Get("Content-Type"); !strings.HasPrefix(got, "text/plain") {
|
||||
t.Errorf("got content-type=%q, want text/plain", got)
|
||||
}
|
||||
if got := r.Header.Get("Accept"); got != "text/html" {
|
||||
t.Errorf("got accept=%q, want text/html", got)
|
||||
}
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(data) != "# Hello" {
|
||||
t.Errorf("got body=%q, want %q", string(data), "# Hello")
|
||||
}
|
||||
w.Header().Set("X-RateLimit-Limit", "80")
|
||||
w.Header().Set("X-RateLimit-Remaining", "79")
|
||||
w.Header().Set("X-RateLimit-Reset", "1700000003")
|
||||
w.Header().Set("Content-Type", "text/html")
|
||||
w.Write([]byte("<h1>Hello</h1>\n"))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
html, err := f.Misc.RenderMarkdownRaw(context.Background(), "# Hello")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := "<h1>Hello</h1>\n"
|
||||
if html != want {
|
||||
t.Errorf("got %q, want %q", html, want)
|
||||
}
|
||||
rl := f.Client().RateLimit()
|
||||
if rl.Limit != 80 || rl.Remaining != 79 || rl.Reset != 1700000003 {
|
||||
t.Fatalf("unexpected rate limit: %+v", rl)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetVersion_Good(t *testing.T) {
|
||||
func TestMiscService_Good_GetVersion(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -148,117 +68,7 @@ func TestMiscService_GetVersion_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetAPISettings_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/settings/api" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.GeneralAPISettings{
|
||||
DefaultGitTreesPerPage: 25,
|
||||
DefaultMaxBlobSize: 4096,
|
||||
DefaultPagingNum: 1,
|
||||
MaxResponseItems: 500,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
settings, err := f.Misc.GetAPISettings(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if settings.DefaultPagingNum != 1 || settings.MaxResponseItems != 500 {
|
||||
t.Fatalf("unexpected api settings: %+v", settings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetAttachmentSettings_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/settings/attachment" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.GeneralAttachmentSettings{
|
||||
AllowedTypes: "image/*",
|
||||
Enabled: true,
|
||||
MaxFiles: 10,
|
||||
MaxSize: 1048576,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
settings, err := f.Misc.GetAttachmentSettings(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !settings.Enabled || settings.MaxFiles != 10 {
|
||||
t.Fatalf("unexpected attachment settings: %+v", settings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetRepositorySettings_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/settings/repository" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.GeneralRepoSettings{
|
||||
ForksDisabled: true,
|
||||
HTTPGitDisabled: true,
|
||||
LFSDisabled: true,
|
||||
MigrationsDisabled: true,
|
||||
MirrorsDisabled: false,
|
||||
StarsDisabled: true,
|
||||
TimeTrackingDisabled: false,
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
settings, err := f.Misc.GetRepositorySettings(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !settings.ForksDisabled || !settings.HTTPGitDisabled {
|
||||
t.Fatalf("unexpected repository settings: %+v", settings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetUISettings_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/settings/ui" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.GeneralUISettings{
|
||||
AllowedReactions: []string{"+1", "-1"},
|
||||
CustomEmojis: []string{":forgejo:"},
|
||||
DefaultTheme: "forgejo-auto",
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
settings, err := f.Misc.GetUISettings(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if settings.DefaultTheme != "forgejo-auto" || len(settings.AllowedReactions) != 2 {
|
||||
t.Fatalf("unexpected ui settings: %+v", settings)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_ListLicenses_Good(t *testing.T) {
|
||||
func TestMiscService_Good_ListLicenses(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -289,38 +99,7 @@ func TestMiscService_ListLicenses_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiscService_IterLicenses_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/licenses" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.LicensesTemplateListEntry{
|
||||
{Key: "mit", Name: "MIT License"},
|
||||
{Key: "gpl-3.0", Name: "GNU General Public License v3.0"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var names []string
|
||||
for item, err := range f.Misc.IterLicenses(context.Background()) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
names = append(names, item.Name)
|
||||
}
|
||||
if len(names) != 2 {
|
||||
t.Fatalf("got %d licences, want 2", len(names))
|
||||
}
|
||||
if names[0] != "MIT License" || names[1] != "GNU General Public License v3.0" {
|
||||
t.Fatalf("unexpected licences: %+v", names)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetLicense_Good(t *testing.T) {
|
||||
func TestMiscService_Good_GetLicense(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -349,7 +128,7 @@ func TestMiscService_GetLicense_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiscService_ListGitignoreTemplates_Good(t *testing.T) {
|
||||
func TestMiscService_Good_ListGitignoreTemplates(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -374,35 +153,7 @@ func TestMiscService_ListGitignoreTemplates_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiscService_IterGitignoreTemplates_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/gitignore/templates" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]string{"Go", "Python", "Node"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var names []string
|
||||
for item, err := range f.Misc.IterGitignoreTemplates(context.Background()) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
names = append(names, item)
|
||||
}
|
||||
if len(names) != 3 {
|
||||
t.Fatalf("got %d templates, want 3", len(names))
|
||||
}
|
||||
if names[0] != "Go" {
|
||||
t.Errorf("got [0]=%q, want %q", names[0], "Go")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetGitignoreTemplate_Good(t *testing.T) {
|
||||
func TestMiscService_Good_GetGitignoreTemplate(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -427,7 +178,7 @@ func TestMiscService_GetGitignoreTemplate_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetNodeInfo_Good(t *testing.T) {
|
||||
func TestMiscService_Good_GetNodeInfo(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -458,30 +209,7 @@ func TestMiscService_GetNodeInfo_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestMiscService_GetSigningKey_Good(t *testing.T) {
|
||||
want := "-----BEGIN PGP PUBLIC KEY BLOCK-----\n..."
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/signing-key.gpg" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
_, _ = w.Write([]byte(want))
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
key, err := f.Misc.GetSigningKey(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if key != want {
|
||||
t.Fatalf("got %q, want %q", key, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMiscService_NotFound_Bad(t *testing.T) {
|
||||
func TestMiscService_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
||||
|
|
|
|||
278
notifications.go
278
notifications.go
|
|
@ -2,216 +2,42 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// NotificationListOptions controls filtering for notification listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.NotificationListOptions{All: true, StatusTypes: []string{"unread"}}
|
||||
type NotificationListOptions struct {
|
||||
All bool
|
||||
StatusTypes []string
|
||||
SubjectTypes []string
|
||||
Since *time.Time
|
||||
Before *time.Time
|
||||
}
|
||||
|
||||
// String returns a safe summary of the notification filters.
|
||||
func (o NotificationListOptions) String() string {
|
||||
return optionString("forge.NotificationListOptions",
|
||||
"all", o.All,
|
||||
"status_types", o.StatusTypes,
|
||||
"subject_types", o.SubjectTypes,
|
||||
"since", o.Since,
|
||||
"before", o.Before,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the notification filters.
|
||||
func (o NotificationListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o NotificationListOptions) addQuery(values url.Values) {
|
||||
if o.All {
|
||||
values.Set("all", "true")
|
||||
}
|
||||
for _, status := range o.StatusTypes {
|
||||
if status != "" {
|
||||
values.Add("status-types", status)
|
||||
}
|
||||
}
|
||||
for _, subjectType := range o.SubjectTypes {
|
||||
if subjectType != "" {
|
||||
values.Add("subject-type", subjectType)
|
||||
}
|
||||
}
|
||||
if o.Since != nil {
|
||||
values.Set("since", o.Since.Format(time.RFC3339))
|
||||
}
|
||||
if o.Before != nil {
|
||||
values.Set("before", o.Before.Format(time.RFC3339))
|
||||
}
|
||||
}
|
||||
|
||||
// NotificationService handles notification operations via the Forgejo API.
|
||||
// No Resource embedding — varied endpoint shapes.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Notifications.List(ctx)
|
||||
type NotificationService struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// NotificationRepoMarkOptions controls how repository notifications are marked.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.NotificationRepoMarkOptions{All: true, ToStatus: "read"}
|
||||
type NotificationRepoMarkOptions struct {
|
||||
All bool
|
||||
StatusTypes []string
|
||||
ToStatus string
|
||||
LastReadAt *time.Time
|
||||
}
|
||||
|
||||
// String returns a safe summary of the repository notification mark options.
|
||||
func (o NotificationRepoMarkOptions) String() string {
|
||||
return optionString("forge.NotificationRepoMarkOptions",
|
||||
"all", o.All,
|
||||
"status_types", o.StatusTypes,
|
||||
"to_status", o.ToStatus,
|
||||
"last_read_at", o.LastReadAt,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the repository notification mark options.
|
||||
func (o NotificationRepoMarkOptions) GoString() string { return o.String() }
|
||||
|
||||
// NotificationMarkOptions controls how authenticated-user notifications are marked.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.NotificationMarkOptions{All: true, ToStatus: "read"}
|
||||
type NotificationMarkOptions struct {
|
||||
All bool
|
||||
StatusTypes []string
|
||||
ToStatus string
|
||||
LastReadAt *time.Time
|
||||
}
|
||||
|
||||
// String returns a safe summary of the authenticated-user notification mark options.
|
||||
func (o NotificationMarkOptions) String() string {
|
||||
return optionString("forge.NotificationMarkOptions",
|
||||
"all", o.All,
|
||||
"status_types", o.StatusTypes,
|
||||
"to_status", o.ToStatus,
|
||||
"last_read_at", o.LastReadAt,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the authenticated-user notification mark options.
|
||||
func (o NotificationMarkOptions) GoString() string { return o.String() }
|
||||
|
||||
func newNotificationService(c *Client) *NotificationService {
|
||||
return &NotificationService{client: c}
|
||||
}
|
||||
|
||||
func notificationMarkQueryString(all bool, statusTypes []string, toStatus string, lastReadAt *time.Time) string {
|
||||
values := url.Values{}
|
||||
if all {
|
||||
values.Set("all", "true")
|
||||
}
|
||||
for _, status := range statusTypes {
|
||||
if status != "" {
|
||||
values.Add("status-types", status)
|
||||
}
|
||||
}
|
||||
if toStatus != "" {
|
||||
values.Set("to-status", toStatus)
|
||||
}
|
||||
if lastReadAt != nil {
|
||||
values.Set("last_read_at", lastReadAt.Format(time.RFC3339))
|
||||
}
|
||||
return values.Encode()
|
||||
}
|
||||
|
||||
func (o NotificationRepoMarkOptions) queryString() string {
|
||||
return notificationMarkQueryString(o.All, o.StatusTypes, o.ToStatus, o.LastReadAt)
|
||||
}
|
||||
|
||||
func (o NotificationMarkOptions) queryString() string {
|
||||
return notificationMarkQueryString(o.All, o.StatusTypes, o.ToStatus, o.LastReadAt)
|
||||
}
|
||||
|
||||
// List returns all notifications for the authenticated user.
|
||||
func (s *NotificationService) List(ctx context.Context, filters ...NotificationListOptions) ([]types.NotificationThread, error) {
|
||||
return s.listAll(ctx, "/api/v1/notifications", filters...)
|
||||
func (s *NotificationService) List(ctx context.Context) ([]types.NotificationThread, error) {
|
||||
return ListAll[types.NotificationThread](ctx, s.client, "/api/v1/notifications", nil)
|
||||
}
|
||||
|
||||
// Iter returns an iterator over all notifications for the authenticated user.
|
||||
func (s *NotificationService) Iter(ctx context.Context, filters ...NotificationListOptions) iter.Seq2[types.NotificationThread, error] {
|
||||
return s.listIter(ctx, "/api/v1/notifications", filters...)
|
||||
}
|
||||
|
||||
// NewAvailable returns the count of unread notifications for the authenticated user.
|
||||
func (s *NotificationService) NewAvailable(ctx context.Context) (*types.NotificationCount, error) {
|
||||
var out types.NotificationCount
|
||||
if err := s.client.Get(ctx, "/api/v1/notifications/new", &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
func (s *NotificationService) Iter(ctx context.Context) iter.Seq2[types.NotificationThread, error] {
|
||||
return ListIter[types.NotificationThread](ctx, s.client, "/api/v1/notifications", nil)
|
||||
}
|
||||
|
||||
// ListRepo returns all notifications for a specific repository.
|
||||
func (s *NotificationService) ListRepo(ctx context.Context, owner, repo string, filters ...NotificationListOptions) ([]types.NotificationThread, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/notifications", pathParams("owner", owner, "repo", repo))
|
||||
return s.listAll(ctx, path, filters...)
|
||||
func (s *NotificationService) ListRepo(ctx context.Context, owner, repo string) ([]types.NotificationThread, error) {
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo)
|
||||
return ListAll[types.NotificationThread](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterRepo returns an iterator over all notifications for a specific repository.
|
||||
func (s *NotificationService) IterRepo(ctx context.Context, owner, repo string, filters ...NotificationListOptions) iter.Seq2[types.NotificationThread, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/notifications", pathParams("owner", owner, "repo", repo))
|
||||
return s.listIter(ctx, path, filters...)
|
||||
}
|
||||
|
||||
// MarkNotifications marks authenticated-user notification threads as read, pinned, or unread.
|
||||
func (s *NotificationService) MarkNotifications(ctx context.Context, opts *NotificationMarkOptions) ([]types.NotificationThread, error) {
|
||||
path := "/api/v1/notifications"
|
||||
if opts != nil {
|
||||
if query := opts.queryString(); query != "" {
|
||||
path += "?" + query
|
||||
}
|
||||
}
|
||||
var out []types.NotificationThread
|
||||
if err := s.client.Put(ctx, path, nil, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// MarkRepoNotifications marks repository notification threads as read, unread, or pinned.
|
||||
func (s *NotificationService) MarkRepoNotifications(ctx context.Context, owner, repo string, opts *NotificationRepoMarkOptions) ([]types.NotificationThread, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/notifications", pathParams("owner", owner, "repo", repo))
|
||||
if opts != nil {
|
||||
if query := opts.queryString(); query != "" {
|
||||
path += "?" + query
|
||||
}
|
||||
}
|
||||
var out []types.NotificationThread
|
||||
if err := s.client.Put(ctx, path, nil, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
func (s *NotificationService) IterRepo(ctx context.Context, owner, repo string) iter.Seq2[types.NotificationThread, error] {
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo)
|
||||
return ListIter[types.NotificationThread](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// MarkRead marks all notifications as read.
|
||||
|
|
@ -221,7 +47,7 @@ func (s *NotificationService) MarkRead(ctx context.Context) error {
|
|||
|
||||
// GetThread returns a single notification thread by ID.
|
||||
func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.NotificationThread, error) {
|
||||
path := ResolvePath("/api/v1/notifications/threads/{id}", pathParams("id", int64String(id)))
|
||||
path := fmt.Sprintf("/api/v1/notifications/threads/%d", id)
|
||||
var out types.NotificationThread
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -231,84 +57,6 @@ func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.N
|
|||
|
||||
// MarkThreadRead marks a single notification thread as read.
|
||||
func (s *NotificationService) MarkThreadRead(ctx context.Context, id int64) error {
|
||||
path := ResolvePath("/api/v1/notifications/threads/{id}", pathParams("id", int64String(id)))
|
||||
path := fmt.Sprintf("/api/v1/notifications/threads/%d", id)
|
||||
return s.client.Patch(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
func (s *NotificationService) listAll(ctx context.Context, path string, filters ...NotificationListOptions) ([]types.NotificationThread, error) {
|
||||
var all []types.NotificationThread
|
||||
page := 1
|
||||
|
||||
for {
|
||||
result, err := s.listPage(ctx, path, ListOptions{Page: page, Limit: defaultPageLimit}, filters...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all = append(all, result.Items...)
|
||||
if !result.HasMore {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (s *NotificationService) listIter(ctx context.Context, path string, filters ...NotificationListOptions) iter.Seq2[types.NotificationThread, error] {
|
||||
return func(yield func(types.NotificationThread, error) bool) {
|
||||
page := 1
|
||||
for {
|
||||
result, err := s.listPage(ctx, path, ListOptions{Page: page, Limit: defaultPageLimit}, filters...)
|
||||
if err != nil {
|
||||
yield(*new(types.NotificationThread), err)
|
||||
return
|
||||
}
|
||||
for _, item := range result.Items {
|
||||
if !yield(item, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !result.HasMore {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NotificationService) listPage(ctx context.Context, path string, opts ListOptions, filters ...NotificationListOptions) (*PagedResult[types.NotificationThread], error) {
|
||||
if opts.Page < 1 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.Limit < 1 {
|
||||
opts.Limit = defaultPageLimit
|
||||
}
|
||||
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, core.E("NotificationService.listPage", "forge: parse path", err)
|
||||
}
|
||||
|
||||
values := u.Query()
|
||||
values.Set("page", strconv.Itoa(opts.Page))
|
||||
values.Set("limit", strconv.Itoa(opts.Limit))
|
||||
for _, filter := range filters {
|
||||
filter.addQuery(values)
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
var items []types.NotificationThread
|
||||
resp, err := s.client.doJSON(ctx, http.MethodGet, u.String(), nil, &items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCount, _ := strconv.Atoi(resp.Header.Get("X-Total-Count"))
|
||||
return &PagedResult[types.NotificationThread]{
|
||||
Items: items,
|
||||
TotalCount: totalCount,
|
||||
Page: opts.Page,
|
||||
HasMore: (totalCount > 0 && (opts.Page-1)*opts.Limit+len(items) < totalCount) ||
|
||||
(totalCount == 0 && len(items) >= opts.Limit),
|
||||
}, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,16 +2,15 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestNotificationService_List_Good(t *testing.T) {
|
||||
func TestNotificationService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -46,55 +45,7 @@ func TestNotificationService_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_List_Filters(t *testing.T) {
|
||||
since := time.Date(2026, time.April, 1, 12, 0, 0, 0, time.UTC)
|
||||
before := time.Date(2026, time.April, 2, 12, 0, 0, 0, time.UTC)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/notifications" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("all"); got != "true" {
|
||||
t.Errorf("got all=%q, want true", got)
|
||||
}
|
||||
if got := r.URL.Query()["status-types"]; len(got) != 2 || got[0] != "unread" || got[1] != "pinned" {
|
||||
t.Errorf("got status-types=%v, want [unread pinned]", got)
|
||||
}
|
||||
if got := r.URL.Query()["subject-type"]; len(got) != 2 || got[0] != "issue" || got[1] != "pull" {
|
||||
t.Errorf("got subject-type=%v, want [issue pull]", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("since"); got != since.Format(time.RFC3339) {
|
||||
t.Errorf("got since=%q, want %q", got, since.Format(time.RFC3339))
|
||||
}
|
||||
if got := r.URL.Query().Get("before"); got != before.Format(time.RFC3339) {
|
||||
t.Errorf("got before=%q, want %q", got, before.Format(time.RFC3339))
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.NotificationThread{
|
||||
{ID: 11, Unread: true, Subject: &types.NotificationSubject{Title: "Filtered"}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
threads, err := f.Notifications.List(context.Background(), NotificationListOptions{
|
||||
All: true,
|
||||
StatusTypes: []string{"unread", "pinned"},
|
||||
SubjectTypes: []string{"issue", "pull"},
|
||||
Since: &since,
|
||||
Before: &before,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(threads) != 1 || threads[0].ID != 11 {
|
||||
t.Fatalf("got threads=%+v", threads)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_ListRepo_Good(t *testing.T) {
|
||||
func TestNotificationService_Good_ListRepo(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -122,68 +73,7 @@ func TestNotificationService_ListRepo_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_ListRepo_Filters(t *testing.T) {
|
||||
since := time.Date(2026, time.April, 1, 12, 0, 0, 0, time.UTC)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/notifications" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query()["status-types"]; len(got) != 1 || got[0] != "read" {
|
||||
t.Errorf("got status-types=%v, want [read]", got)
|
||||
}
|
||||
if got := r.URL.Query()["subject-type"]; len(got) != 1 || got[0] != "repository" {
|
||||
t.Errorf("got subject-type=%v, want [repository]", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("since"); got != since.Format(time.RFC3339) {
|
||||
t.Errorf("got since=%q, want %q", got, since.Format(time.RFC3339))
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.NotificationThread{
|
||||
{ID: 12, Unread: false, Subject: &types.NotificationSubject{Title: "Repo filtered"}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
threads, err := f.Notifications.ListRepo(context.Background(), "core", "go-forge", NotificationListOptions{
|
||||
StatusTypes: []string{"read"},
|
||||
SubjectTypes: []string{"repository"},
|
||||
Since: &since,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(threads) != 1 || threads[0].ID != 12 {
|
||||
t.Fatalf("got threads=%+v", threads)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_NewAvailable_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/notifications/new" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.NotificationCount{New: 3})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
count, err := f.Notifications.NewAvailable(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if count.New != 3 {
|
||||
t.Fatalf("got new=%d, want 3", count.New)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_GetThread_Good(t *testing.T) {
|
||||
func TestNotificationService_Good_GetThread(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -217,57 +107,7 @@ func TestNotificationService_GetThread_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_MarkNotifications_Good(t *testing.T) {
|
||||
lastReadAt := time.Date(2026, time.April, 2, 15, 4, 5, 0, time.UTC)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/notifications" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("all"); got != "true" {
|
||||
t.Errorf("got all=%q, want true", got)
|
||||
}
|
||||
if got := r.URL.Query()["status-types"]; len(got) != 2 || got[0] != "unread" || got[1] != "pinned" {
|
||||
t.Errorf("got status-types=%v, want [unread pinned]", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("to-status"); got != "read" {
|
||||
t.Errorf("got to-status=%q, want read", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("last_read_at"); got != lastReadAt.Format(time.RFC3339) {
|
||||
t.Errorf("got last_read_at=%q, want %q", got, lastReadAt.Format(time.RFC3339))
|
||||
}
|
||||
w.WriteHeader(http.StatusResetContent)
|
||||
json.NewEncoder(w).Encode([]types.NotificationThread{
|
||||
{ID: 21, Unread: false, Subject: &types.NotificationSubject{Title: "Release notes"}},
|
||||
{ID: 22, Unread: false, Subject: &types.NotificationSubject{Title: "Issue triaged"}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
threads, err := f.Notifications.MarkNotifications(context.Background(), &NotificationMarkOptions{
|
||||
All: true,
|
||||
StatusTypes: []string{"unread", "pinned"},
|
||||
ToStatus: "read",
|
||||
LastReadAt: &lastReadAt,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(threads) != 2 {
|
||||
t.Fatalf("got %d threads, want 2", len(threads))
|
||||
}
|
||||
if threads[0].ID != 21 || threads[1].ID != 22 {
|
||||
t.Fatalf("got ids=%d,%d want 21,22", threads[0].ID, threads[1].ID)
|
||||
}
|
||||
if threads[0].Subject.Title != "Release notes" {
|
||||
t.Errorf("got title=%q, want %q", threads[0].Subject.Title, "Release notes")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_MarkRead_Good(t *testing.T) {
|
||||
func TestNotificationService_Good_MarkRead(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
|
|
@ -286,7 +126,7 @@ func TestNotificationService_MarkRead_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_MarkThreadRead_Good(t *testing.T) {
|
||||
func TestNotificationService_Good_MarkThreadRead(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
|
|
@ -305,57 +145,7 @@ func TestNotificationService_MarkThreadRead_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_MarkRepoNotifications_Good(t *testing.T) {
|
||||
lastReadAt := time.Date(2026, time.April, 2, 15, 4, 5, 0, time.UTC)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/notifications" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query()["status-types"]; len(got) != 2 || got[0] != "unread" || got[1] != "pinned" {
|
||||
t.Errorf("got status-types=%v, want [unread pinned]", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("all"); got != "true" {
|
||||
t.Errorf("got all=%q, want true", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("to-status"); got != "read" {
|
||||
t.Errorf("got to-status=%q, want read", got)
|
||||
}
|
||||
if got := r.URL.Query().Get("last_read_at"); got != lastReadAt.Format(time.RFC3339) {
|
||||
t.Errorf("got last_read_at=%q, want %q", got, lastReadAt.Format(time.RFC3339))
|
||||
}
|
||||
w.WriteHeader(http.StatusResetContent)
|
||||
json.NewEncoder(w).Encode([]types.NotificationThread{
|
||||
{ID: 7, Unread: false, Subject: &types.NotificationSubject{Title: "Pinned release"}},
|
||||
{ID: 8, Unread: false, Subject: &types.NotificationSubject{Title: "New docs"}},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
threads, err := f.Notifications.MarkRepoNotifications(context.Background(), "core", "go-forge", &NotificationRepoMarkOptions{
|
||||
All: true,
|
||||
StatusTypes: []string{"unread", "pinned"},
|
||||
ToStatus: "read",
|
||||
LastReadAt: &lastReadAt,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(threads) != 2 {
|
||||
t.Fatalf("got %d threads, want 2", len(threads))
|
||||
}
|
||||
if threads[0].ID != 7 || threads[1].ID != 8 {
|
||||
t.Fatalf("got ids=%d,%d want 7,8", threads[0].ID, threads[1].ID)
|
||||
}
|
||||
if threads[0].Subject.Title != "Pinned release" {
|
||||
t.Errorf("got title=%q, want %q", threads[0].Subject.Title, "Pinned release")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotificationService_NotFound_Bad(t *testing.T) {
|
||||
func TestNotificationService_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "thread not found"})
|
||||
|
|
|
|||
281
orgs.go
281
orgs.go
|
|
@ -2,49 +2,17 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// OrgService handles organisation operations.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Orgs.ListMembers(ctx, "core")
|
||||
type OrgService struct {
|
||||
Resource[types.Organization, types.CreateOrgOption, types.EditOrgOption]
|
||||
}
|
||||
|
||||
// OrgActivityFeedListOptions controls filtering for organisation activity feeds.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.OrgActivityFeedListOptions{Date: &day}
|
||||
type OrgActivityFeedListOptions struct {
|
||||
Date *time.Time
|
||||
}
|
||||
|
||||
// String returns a safe summary of the organisation activity feed filters.
|
||||
func (o OrgActivityFeedListOptions) String() string {
|
||||
return optionString("forge.OrgActivityFeedListOptions", "date", o.Date)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the organisation activity feed filters.
|
||||
func (o OrgActivityFeedListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o OrgActivityFeedListOptions) queryParams() map[string]string {
|
||||
if o.Date == nil {
|
||||
return nil
|
||||
}
|
||||
return map[string]string{
|
||||
"date": o.Date.Format("2006-01-02"),
|
||||
}
|
||||
}
|
||||
|
||||
func newOrgService(c *Client) *OrgService {
|
||||
return &OrgService{
|
||||
Resource: *NewResource[types.Organization, types.CreateOrgOption, types.EditOrgOption](
|
||||
|
|
@ -53,257 +21,39 @@ func newOrgService(c *Client) *OrgService {
|
|||
}
|
||||
}
|
||||
|
||||
// ListOrgs returns all organisations.
|
||||
func (s *OrgService) ListOrgs(ctx context.Context) ([]types.Organization, error) {
|
||||
return ListAll[types.Organization](ctx, s.client, "/api/v1/orgs", nil)
|
||||
}
|
||||
|
||||
// IterOrgs returns an iterator over all organisations.
|
||||
func (s *OrgService) IterOrgs(ctx context.Context) iter.Seq2[types.Organization, error] {
|
||||
return ListIter[types.Organization](ctx, s.client, "/api/v1/orgs", nil)
|
||||
}
|
||||
|
||||
// CreateOrg creates a new organisation.
|
||||
func (s *OrgService) CreateOrg(ctx context.Context, opts *types.CreateOrgOption) (*types.Organization, error) {
|
||||
var out types.Organization
|
||||
if err := s.client.Post(ctx, "/api/v1/orgs", opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ListMembers returns all members of an organisation.
|
||||
func (s *OrgService) ListMembers(ctx context.Context, org string) ([]types.User, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/members", org)
|
||||
return ListAll[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterMembers returns an iterator over all members of an organisation.
|
||||
func (s *OrgService) IterMembers(ctx context.Context, org string) iter.Seq2[types.User, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/members", org)
|
||||
return ListIter[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// AddMember adds a user to an organisation.
|
||||
func (s *OrgService) AddMember(ctx context.Context, org, username string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/members/%s", org, username)
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// RemoveMember removes a user from an organisation.
|
||||
func (s *OrgService) RemoveMember(ctx context.Context, org, username string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/members/%s", org, username)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// IsMember reports whether a user is a member of an organisation.
|
||||
func (s *OrgService) IsMember(ctx context.Context, org, username string) (bool, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username))
|
||||
resp, err := s.client.doJSON(ctx, http.MethodGet, path, nil, nil)
|
||||
if err != nil {
|
||||
if IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return resp.StatusCode == http.StatusNoContent, nil
|
||||
}
|
||||
|
||||
// ListBlockedUsers returns all users blocked by an organisation.
|
||||
func (s *OrgService) ListBlockedUsers(ctx context.Context, org string) ([]types.BlockedUser, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/list_blocked", pathParams("org", org))
|
||||
return ListAll[types.BlockedUser](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterBlockedUsers returns an iterator over all users blocked by an organisation.
|
||||
func (s *OrgService) IterBlockedUsers(ctx context.Context, org string) iter.Seq2[types.BlockedUser, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/list_blocked", pathParams("org", org))
|
||||
return ListIter[types.BlockedUser](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IsBlocked reports whether a user is blocked by an organisation.
|
||||
func (s *OrgService) IsBlocked(ctx context.Context, org, username string) (bool, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/block/{username}", pathParams("org", org, "username", username))
|
||||
resp, err := s.client.doJSON(ctx, "GET", path, nil, nil)
|
||||
if err != nil {
|
||||
if IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return resp.StatusCode == http.StatusNoContent, nil
|
||||
}
|
||||
|
||||
// ListPublicMembers returns all public members of an organisation.
|
||||
func (s *OrgService) ListPublicMembers(ctx context.Context, org string) ([]types.User, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/public_members", pathParams("org", org))
|
||||
return ListAll[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterPublicMembers returns an iterator over all public members of an organisation.
|
||||
func (s *OrgService) IterPublicMembers(ctx context.Context, org string) iter.Seq2[types.User, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/public_members", pathParams("org", org))
|
||||
return ListIter[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IsPublicMember reports whether a user is a public member of an organisation.
|
||||
func (s *OrgService) IsPublicMember(ctx context.Context, org, username string) (bool, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/public_members/{username}", pathParams("org", org, "username", username))
|
||||
resp, err := s.client.doJSON(ctx, "GET", path, nil, nil)
|
||||
if err != nil {
|
||||
if IsNotFound(err) {
|
||||
return false, nil
|
||||
}
|
||||
return false, err
|
||||
}
|
||||
return resp.StatusCode == http.StatusNoContent, nil
|
||||
}
|
||||
|
||||
// PublicizeMember makes a user's membership public within an organisation.
|
||||
func (s *OrgService) PublicizeMember(ctx context.Context, org, username string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/public_members/{username}", pathParams("org", org, "username", username))
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// ConcealMember hides a user's public membership within an organisation.
|
||||
func (s *OrgService) ConcealMember(ctx context.Context, org, username string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/public_members/{username}", pathParams("org", org, "username", username))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// Block blocks a user within an organisation.
|
||||
func (s *OrgService) Block(ctx context.Context, org, username string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/block/{username}", pathParams("org", org, "username", username))
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// Unblock unblocks a user within an organisation.
|
||||
func (s *OrgService) Unblock(ctx context.Context, org, username string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/unblock/{username}", pathParams("org", org, "username", username))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// GetQuota returns the quota information for an organisation.
|
||||
func (s *OrgService) GetQuota(ctx context.Context, org string) (*types.QuotaInfo, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota", pathParams("org", org))
|
||||
var out types.QuotaInfo
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CheckQuota reports whether an organisation is over quota for the current subject.
|
||||
func (s *OrgService) CheckQuota(ctx context.Context, org string) (bool, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota/check", pathParams("org", org))
|
||||
var out bool
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return false, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// ListQuotaArtifacts returns all artefacts counting towards an organisation's quota.
|
||||
func (s *OrgService) ListQuotaArtifacts(ctx context.Context, org string) ([]types.QuotaUsedArtifact, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota/artifacts", pathParams("org", org))
|
||||
return ListAll[types.QuotaUsedArtifact](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterQuotaArtifacts returns an iterator over all artefacts counting towards an organisation's quota.
|
||||
func (s *OrgService) IterQuotaArtifacts(ctx context.Context, org string) iter.Seq2[types.QuotaUsedArtifact, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota/artifacts", pathParams("org", org))
|
||||
return ListIter[types.QuotaUsedArtifact](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// ListQuotaAttachments returns all attachments counting towards an organisation's quota.
|
||||
func (s *OrgService) ListQuotaAttachments(ctx context.Context, org string) ([]types.QuotaUsedAttachment, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota/attachments", pathParams("org", org))
|
||||
return ListAll[types.QuotaUsedAttachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterQuotaAttachments returns an iterator over all attachments counting towards an organisation's quota.
|
||||
func (s *OrgService) IterQuotaAttachments(ctx context.Context, org string) iter.Seq2[types.QuotaUsedAttachment, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota/attachments", pathParams("org", org))
|
||||
return ListIter[types.QuotaUsedAttachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// ListQuotaPackages returns all packages counting towards an organisation's quota.
|
||||
func (s *OrgService) ListQuotaPackages(ctx context.Context, org string) ([]types.QuotaUsedPackage, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota/packages", pathParams("org", org))
|
||||
return ListAll[types.QuotaUsedPackage](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterQuotaPackages returns an iterator over all packages counting towards an organisation's quota.
|
||||
func (s *OrgService) IterQuotaPackages(ctx context.Context, org string) iter.Seq2[types.QuotaUsedPackage, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/quota/packages", pathParams("org", org))
|
||||
return ListIter[types.QuotaUsedPackage](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetRunnerRegistrationToken returns an organisation actions runner registration token.
|
||||
func (s *OrgService) GetRunnerRegistrationToken(ctx context.Context, org string) (string, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/actions/runners/registration-token", pathParams("org", org))
|
||||
resp, err := s.client.doJSON(ctx, http.MethodGet, path, nil, nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return resp.Header.Get("token"), nil
|
||||
}
|
||||
|
||||
// UpdateAvatar updates an organisation avatar.
|
||||
func (s *OrgService) UpdateAvatar(ctx context.Context, org string, opts *types.UpdateUserAvatarOption) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/avatar", pathParams("org", org))
|
||||
return s.client.Post(ctx, path, opts, nil)
|
||||
}
|
||||
|
||||
// DeleteAvatar deletes an organisation avatar.
|
||||
func (s *OrgService) DeleteAvatar(ctx context.Context, org string) error {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/avatar", pathParams("org", org))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// SearchTeams searches for teams within an organisation.
|
||||
func (s *OrgService) SearchTeams(ctx context.Context, org, q string) ([]types.Team, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/teams/search", pathParams("org", org))
|
||||
return ListAll[types.Team](ctx, s.client, path, map[string]string{"q": q})
|
||||
}
|
||||
|
||||
// IterSearchTeams returns an iterator over teams within an organisation.
|
||||
func (s *OrgService) IterSearchTeams(ctx context.Context, org, q string) iter.Seq2[types.Team, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/teams/search", pathParams("org", org))
|
||||
return ListIter[types.Team](ctx, s.client, path, map[string]string{"q": q})
|
||||
}
|
||||
|
||||
// GetUserPermissions returns a user's permissions in an organisation.
|
||||
func (s *OrgService) GetUserPermissions(ctx context.Context, username, org string) (*types.OrganizationPermissions, error) {
|
||||
path := ResolvePath("/api/v1/users/{username}/orgs/{org}/permissions", pathParams("username", username, "org", org))
|
||||
var out types.OrganizationPermissions
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ListActivityFeeds returns the organisation's activity feed entries.
|
||||
func (s *OrgService) ListActivityFeeds(ctx context.Context, org string, filters ...OrgActivityFeedListOptions) ([]types.Activity, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/activities/feeds", pathParams("org", org))
|
||||
return ListAll[types.Activity](ctx, s.client, path, orgActivityFeedQuery(filters...))
|
||||
}
|
||||
|
||||
// IterActivityFeeds returns an iterator over the organisation's activity feed entries.
|
||||
func (s *OrgService) IterActivityFeeds(ctx context.Context, org string, filters ...OrgActivityFeedListOptions) iter.Seq2[types.Activity, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/activities/feeds", pathParams("org", org))
|
||||
return ListIter[types.Activity](ctx, s.client, path, orgActivityFeedQuery(filters...))
|
||||
}
|
||||
|
||||
// ListUserOrgs returns all organisations for a user.
|
||||
func (s *OrgService) ListUserOrgs(ctx context.Context, username string) ([]types.Organization, error) {
|
||||
path := ResolvePath("/api/v1/users/{username}/orgs", pathParams("username", username))
|
||||
path := fmt.Sprintf("/api/v1/users/%s/orgs", username)
|
||||
return ListAll[types.Organization](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterUserOrgs returns an iterator over all organisations for a user.
|
||||
func (s *OrgService) IterUserOrgs(ctx context.Context, username string) iter.Seq2[types.Organization, error] {
|
||||
path := ResolvePath("/api/v1/users/{username}/orgs", pathParams("username", username))
|
||||
path := fmt.Sprintf("/api/v1/users/%s/orgs", username)
|
||||
return ListIter[types.Organization](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
|
|
@ -316,20 +66,3 @@ func (s *OrgService) ListMyOrgs(ctx context.Context) ([]types.Organization, erro
|
|||
func (s *OrgService) IterMyOrgs(ctx context.Context) iter.Seq2[types.Organization, error] {
|
||||
return ListIter[types.Organization](ctx, s.client, "/api/v1/user/orgs", nil)
|
||||
}
|
||||
|
||||
func orgActivityFeedQuery(filters ...OrgActivityFeedListOptions) map[string]string {
|
||||
if len(filters) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
query := make(map[string]string, 1)
|
||||
for _, filter := range filters {
|
||||
if filter.Date != nil {
|
||||
query["date"] = filter.Date.Format("2006-01-02")
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,63 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestOrgService_ListOrgs_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Organization{{ID: 1, Name: "core"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
orgs, err := f.Orgs.ListOrgs(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(orgs) != 1 || orgs[0].Name != "core" {
|
||||
t.Fatalf("got %#v", orgs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_CreateOrg_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.CreateOrgOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.UserName != "core" {
|
||||
t.Fatalf("unexpected body: %+v", body)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Organization{ID: 1, Name: body.UserName})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
org, err := f.Orgs.CreateOrg(context.Background(), &types.CreateOrgOption{UserName: "core"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if org.Name != "core" {
|
||||
t.Fatalf("got name=%q", org.Name)
|
||||
}
|
||||
}
|
||||
271
orgs_test.go
271
orgs_test.go
|
|
@ -2,16 +2,15 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestOrgService_List_Good(t *testing.T) {
|
||||
func TestOrgService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -37,7 +36,7 @@ func TestOrgService_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOrgService_Get_Good(t *testing.T) {
|
||||
func TestOrgService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -59,7 +58,7 @@ func TestOrgService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestOrgService_ListMembers_Good(t *testing.T) {
|
||||
func TestOrgService_Good_ListMembers(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -87,265 +86,3 @@ func TestOrgService_ListMembers_Good(t *testing.T) {
|
|||
t.Errorf("got username=%q, want %q", members[0].UserName, "alice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_IsMember_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/members/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
member, err := f.Orgs.IsMember(context.Background(), "core", "alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !member {
|
||||
t.Fatal("got member=false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_ListPublicMembers_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/public_members" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.User{
|
||||
{ID: 1, UserName: "alice"},
|
||||
{ID: 2, UserName: "bob"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
members, err := f.Orgs.ListPublicMembers(context.Background(), "core")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(members) != 2 {
|
||||
t.Errorf("got %d members, want 2", len(members))
|
||||
}
|
||||
if members[0].UserName != "alice" {
|
||||
t.Errorf("got username=%q, want %q", members[0].UserName, "alice")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_ListBlockedUsers_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/list_blocked" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.BlockedUser{
|
||||
{BlockID: 1},
|
||||
{BlockID: 2},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
blocked, err := f.Orgs.ListBlockedUsers(context.Background(), "core")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(blocked) != 2 {
|
||||
t.Fatalf("got %d blocked users, want 2", len(blocked))
|
||||
}
|
||||
if blocked[0].BlockID != 1 {
|
||||
t.Errorf("got block_id=%d, want %d", blocked[0].BlockID, 1)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_PublicizeMember_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/public_members/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Orgs.PublicizeMember(context.Background(), "core", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_ConcealMember_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/public_members/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Orgs.ConcealMember(context.Background(), "core", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_Block_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/block/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Orgs.Block(context.Background(), "core", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_Unblock_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/unblock/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Orgs.Unblock(context.Background(), "core", "alice"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_ListActivityFeeds_Good(t *testing.T) {
|
||||
date := time.Date(2026, time.April, 2, 15, 4, 5, 0, time.UTC)
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/activities/feeds" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
if got := r.URL.Query().Get("date"); got != "2026-04-02" {
|
||||
t.Errorf("wrong date: %s", got)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Activity{{
|
||||
ID: 9,
|
||||
OpType: "create_org",
|
||||
Content: "created organisation",
|
||||
}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
activities, err := f.Orgs.ListActivityFeeds(context.Background(), "core", OrgActivityFeedListOptions{Date: &date})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(activities) != 1 || activities[0].ID != 9 || activities[0].OpType != "create_org" {
|
||||
t.Fatalf("got %#v", activities)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_IterActivityFeeds_Good(t *testing.T) {
|
||||
var requests int
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/activities/feeds" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Activity{{
|
||||
ID: 11,
|
||||
OpType: "update_org",
|
||||
Content: "updated organisation",
|
||||
}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []int64
|
||||
for activity, err := range f.Orgs.IterActivityFeeds(context.Background(), "core") {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, activity.ID)
|
||||
}
|
||||
if requests != 1 {
|
||||
t.Fatalf("expected 1 request, got %d", requests)
|
||||
}
|
||||
if len(got) != 1 || got[0] != 11 {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_IsBlocked_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/block/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
blocked, err := f.Orgs.IsBlocked(context.Background(), "core", "alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !blocked {
|
||||
t.Fatal("got blocked=false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestOrgService_IsPublicMember_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/public_members/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
public, err := f.Orgs.IsPublicMember(context.Background(), "core", "alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !public {
|
||||
t.Fatal("got public=false, want true")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
18
packages.go
18
packages.go
|
|
@ -2,6 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
|
|
@ -9,11 +10,6 @@ import (
|
|||
|
||||
// PackageService handles package registry operations via the Forgejo API.
|
||||
// No Resource embedding — paths vary by operation.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Packages.List(ctx, "core")
|
||||
type PackageService struct {
|
||||
client *Client
|
||||
}
|
||||
|
|
@ -24,19 +20,19 @@ func newPackageService(c *Client) *PackageService {
|
|||
|
||||
// List returns all packages for a given owner.
|
||||
func (s *PackageService) List(ctx context.Context, owner string) ([]types.Package, error) {
|
||||
path := ResolvePath("/api/v1/packages/{owner}", pathParams("owner", owner))
|
||||
path := fmt.Sprintf("/api/v1/packages/%s", owner)
|
||||
return ListAll[types.Package](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// Iter returns an iterator over all packages for a given owner.
|
||||
func (s *PackageService) Iter(ctx context.Context, owner string) iter.Seq2[types.Package, error] {
|
||||
path := ResolvePath("/api/v1/packages/{owner}", pathParams("owner", owner))
|
||||
path := fmt.Sprintf("/api/v1/packages/%s", owner)
|
||||
return ListIter[types.Package](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// Get returns a single package by owner, type, name, and version.
|
||||
func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version string) (*types.Package, error) {
|
||||
path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}", pathParams("owner", owner, "type", pkgType, "name", name, "version", version))
|
||||
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version)
|
||||
var out types.Package
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -46,18 +42,18 @@ func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version
|
|||
|
||||
// Delete removes a package by owner, type, name, and version.
|
||||
func (s *PackageService) Delete(ctx context.Context, owner, pkgType, name, version string) error {
|
||||
path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}", pathParams("owner", owner, "type", pkgType, "name", name, "version", version))
|
||||
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListFiles returns all files for a specific package version.
|
||||
func (s *PackageService) ListFiles(ctx context.Context, owner, pkgType, name, version string) ([]types.PackageFile, error) {
|
||||
path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}/files", pathParams("owner", owner, "type", pkgType, "name", name, "version", version))
|
||||
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version)
|
||||
return ListAll[types.PackageFile](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterFiles returns an iterator over all files for a specific package version.
|
||||
func (s *PackageService) IterFiles(ctx context.Context, owner, pkgType, name, version string) iter.Seq2[types.PackageFile, error] {
|
||||
path := ResolvePath("/api/v1/packages/{owner}/{type}/{name}/{version}/files", pathParams("owner", owner, "type", pkgType, "name", name, "version", version))
|
||||
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version)
|
||||
return ListIter[types.PackageFile](ctx, s.client, path, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestPackageService_List_Good(t *testing.T) {
|
||||
func TestPackageService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -42,7 +42,7 @@ func TestPackageService_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPackageService_Get_Good(t *testing.T) {
|
||||
func TestPackageService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -75,7 +75,7 @@ func TestPackageService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPackageService_Delete_Good(t *testing.T) {
|
||||
func TestPackageService_Good_Delete(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -94,7 +94,7 @@ func TestPackageService_Delete_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPackageService_ListFiles_Good(t *testing.T) {
|
||||
func TestPackageService_Good_ListFiles(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -125,7 +125,7 @@ func TestPackageService_ListFiles_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPackageService_NotFound_Bad(t *testing.T) {
|
||||
func TestPackageService_Bad_NotFound(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "package not found"})
|
||||
|
|
|
|||
100
pagination.go
100
pagination.go
|
|
@ -7,58 +7,19 @@ import (
|
|||
"net/url"
|
||||
"strconv"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
coreerr "dappco.re/go/core/log"
|
||||
)
|
||||
|
||||
const defaultPageLimit = 50
|
||||
|
||||
// ListOptions controls pagination.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.ListOptions{Page: 1, Limit: 50}
|
||||
// _ = opts
|
||||
type ListOptions struct {
|
||||
Page int // 1-based page number
|
||||
Limit int // items per page (default 50)
|
||||
}
|
||||
|
||||
// String returns a safe summary of the pagination options.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = forge.DefaultList.String()
|
||||
func (o ListOptions) String() string {
|
||||
return core.Concat(
|
||||
"forge.ListOptions{page=",
|
||||
strconv.Itoa(o.Page),
|
||||
", limit=",
|
||||
strconv.Itoa(o.Limit),
|
||||
"}",
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the pagination options.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = fmt.Sprintf("%#v", forge.DefaultList)
|
||||
func (o ListOptions) GoString() string { return o.String() }
|
||||
|
||||
// DefaultList provides sensible default pagination.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// page, err := forge.ListPage[types.Repository](ctx, client, path, nil, forge.DefaultList)
|
||||
// _ = page
|
||||
var DefaultList = ListOptions{Page: 1, Limit: defaultPageLimit}
|
||||
// DefaultList returns sensible default pagination.
|
||||
var DefaultList = ListOptions{Page: 1, Limit: 50}
|
||||
|
||||
// PagedResult holds a single page of results with metadata.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// page, err := forge.ListPage[types.Repository](ctx, client, path, nil, forge.DefaultList)
|
||||
// _ = page
|
||||
type PagedResult[T any] struct {
|
||||
Items []T
|
||||
TotalCount int
|
||||
|
|
@ -66,55 +27,19 @@ type PagedResult[T any] struct {
|
|||
HasMore bool
|
||||
}
|
||||
|
||||
// String returns a safe summary of a page of results.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// page, _ := forge.ListPage[types.Repository](...)
|
||||
// _ = page.String()
|
||||
func (r PagedResult[T]) String() string {
|
||||
items := 0
|
||||
if r.Items != nil {
|
||||
items = len(r.Items)
|
||||
}
|
||||
return core.Concat(
|
||||
"forge.PagedResult{items=",
|
||||
strconv.Itoa(items),
|
||||
", totalCount=",
|
||||
strconv.Itoa(r.TotalCount),
|
||||
", page=",
|
||||
strconv.Itoa(r.Page),
|
||||
", hasMore=",
|
||||
strconv.FormatBool(r.HasMore),
|
||||
"}",
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of a page of results.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = fmt.Sprintf("%#v", page)
|
||||
func (r PagedResult[T]) GoString() string { return r.String() }
|
||||
|
||||
// ListPage fetches a single page of results.
|
||||
// Extra query params can be passed via the query map.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// page, err := forge.ListPage[types.Repository](ctx, client, "/api/v1/user/repos", nil, forge.DefaultList)
|
||||
// _ = page
|
||||
func ListPage[T any](ctx context.Context, c *Client, path string, query map[string]string, opts ListOptions) (*PagedResult[T], error) {
|
||||
if opts.Page < 1 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.Limit < 1 {
|
||||
opts.Limit = defaultPageLimit
|
||||
opts.Limit = 50
|
||||
}
|
||||
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, core.E("ListPage", "forge: parse path", err)
|
||||
return nil, coreerr.E("ListPage", "forge: parse path", err)
|
||||
}
|
||||
|
||||
q := u.Query()
|
||||
|
|
@ -145,17 +70,12 @@ func ListPage[T any](ctx context.Context, c *Client, path string, query map[stri
|
|||
}
|
||||
|
||||
// ListAll fetches all pages of results.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// items, err := forge.ListAll[types.Repository](ctx, client, "/api/v1/user/repos", nil)
|
||||
// _ = items
|
||||
func ListAll[T any](ctx context.Context, c *Client, path string, query map[string]string) ([]T, error) {
|
||||
var all []T
|
||||
page := 1
|
||||
|
||||
for {
|
||||
result, err := ListPage[T](ctx, c, path, query, ListOptions{Page: page, Limit: defaultPageLimit})
|
||||
result, err := ListPage[T](ctx, c, path, query, ListOptions{Page: page, Limit: 50})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -170,17 +90,11 @@ func ListAll[T any](ctx context.Context, c *Client, path string, query map[strin
|
|||
}
|
||||
|
||||
// ListIter returns an iterator over all resources across all pages.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// for item, err := range forge.ListIter[types.Repository](ctx, client, "/api/v1/user/repos", nil) {
|
||||
// _, _ = item, err
|
||||
// }
|
||||
func ListIter[T any](ctx context.Context, c *Client, path string, query map[string]string) iter.Seq2[T, error] {
|
||||
return func(yield func(T, error) bool) {
|
||||
page := 1
|
||||
for {
|
||||
result, err := ListPage[T](ctx, c, path, query, ListOptions{Page: page, Limit: defaultPageLimit})
|
||||
result, err := ListPage[T](ctx, c, path, query, ListOptions{Page: page, Limit: 50})
|
||||
if err != nil {
|
||||
yield(*new(T), err)
|
||||
return
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestPagination_SinglePage_Good(t *testing.T) {
|
||||
func TestPagination_Good_SinglePage(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]map[string]int{{"id": 1}, {"id": 2}})
|
||||
|
|
@ -25,7 +25,7 @@ func TestPagination_SinglePage_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPagination_MultiPage_Good(t *testing.T) {
|
||||
func TestPagination_Good_MultiPage(t *testing.T) {
|
||||
page := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
page++
|
||||
|
|
@ -48,7 +48,7 @@ func TestPagination_MultiPage_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPagination_EmptyResult_Good(t *testing.T) {
|
||||
func TestPagination_Good_EmptyResult(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Total-Count", "0")
|
||||
json.NewEncoder(w).Encode([]map[string]int{})
|
||||
|
|
@ -65,7 +65,7 @@ func TestPagination_EmptyResult_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPagination_Iter_Good(t *testing.T) {
|
||||
func TestPagination_Good_Iter(t *testing.T) {
|
||||
page := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
page++
|
||||
|
|
@ -95,7 +95,7 @@ func TestPagination_Iter_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestListPage_QueryParams_Good(t *testing.T) {
|
||||
func TestListPage_Good_QueryParams(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
p := r.URL.Query().Get("page")
|
||||
l := r.URL.Query().Get("limit")
|
||||
|
|
@ -116,7 +116,7 @@ func TestListPage_QueryParams_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPagination_ServerError_Bad(t *testing.T) {
|
||||
func TestPagination_Bad_ServerError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(500)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "fail"})
|
||||
|
|
|
|||
53
params.go
53
params.go
|
|
@ -2,68 +2,17 @@ package forge
|
|||
|
||||
import (
|
||||
"net/url"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
)
|
||||
|
||||
// Params maps path variable names to values.
|
||||
// Example: Params{"owner": "core", "repo": "go-forge"}
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// params := forge.Params{"owner": "core", "repo": "go-forge"}
|
||||
// _ = params
|
||||
type Params map[string]string
|
||||
|
||||
// String returns a safe summary of the path parameters.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = forge.Params{"owner": "core"}.String()
|
||||
func (p Params) String() string {
|
||||
if p == nil {
|
||||
return "forge.Params{<nil>}"
|
||||
}
|
||||
|
||||
keys := make([]string, 0, len(p))
|
||||
for k := range p {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
var b strings.Builder
|
||||
b.WriteString("forge.Params{")
|
||||
for i, k := range keys {
|
||||
if i > 0 {
|
||||
b.WriteString(", ")
|
||||
}
|
||||
b.WriteString(k)
|
||||
b.WriteString("=")
|
||||
b.WriteString(strconv.Quote(p[k]))
|
||||
}
|
||||
b.WriteString("}")
|
||||
return b.String()
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the path parameters.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// _ = fmt.Sprintf("%#v", forge.Params{"owner": "core"})
|
||||
func (p Params) GoString() string { return p.String() }
|
||||
|
||||
// ResolvePath substitutes {placeholders} in path with values from params.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// path := forge.ResolvePath("/api/v1/repos/{owner}/{repo}", forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
// _ = path
|
||||
func ResolvePath(path string, params Params) string {
|
||||
for k, v := range params {
|
||||
path = core.Replace(path, "{"+k+"}", url.PathEscape(v))
|
||||
path = strings.ReplaceAll(path, "{"+k+"}", url.PathEscape(v))
|
||||
}
|
||||
return path
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import "testing"
|
||||
|
||||
func TestResolvePath_Simple_Good(t *testing.T) {
|
||||
func TestResolvePath_Good_Simple(t *testing.T) {
|
||||
got := ResolvePath("/api/v1/repos/{owner}/{repo}", Params{"owner": "core", "repo": "go-forge"})
|
||||
want := "/api/v1/repos/core/go-forge"
|
||||
if got != want {
|
||||
|
|
@ -10,14 +10,14 @@ func TestResolvePath_Simple_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolvePath_NoParams_Good(t *testing.T) {
|
||||
func TestResolvePath_Good_NoParams(t *testing.T) {
|
||||
got := ResolvePath("/api/v1/user", nil)
|
||||
if got != "/api/v1/user" {
|
||||
t.Errorf("got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolvePath_WithID_Good(t *testing.T) {
|
||||
func TestResolvePath_Good_WithID(t *testing.T) {
|
||||
got := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}", Params{
|
||||
"owner": "core", "repo": "go-forge", "index": "42",
|
||||
})
|
||||
|
|
@ -27,7 +27,7 @@ func TestResolvePath_WithID_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResolvePath_URLEncoding_Good(t *testing.T) {
|
||||
func TestResolvePath_Good_URLEncoding(t *testing.T) {
|
||||
got := ResolvePath("/api/v1/repos/{owner}/{repo}", Params{"owner": "my org", "repo": "my repo"})
|
||||
want := "/api/v1/repos/my%20org/my%20repo"
|
||||
if got != want {
|
||||
|
|
|
|||
303
pulls.go
303
pulls.go
|
|
@ -2,71 +2,17 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"net/url"
|
||||
"strconv"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// PullService handles pull request operations within a repository.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Pulls.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
type PullService struct {
|
||||
Resource[types.PullRequest, types.CreatePullRequestOption, types.EditPullRequestOption]
|
||||
}
|
||||
|
||||
// PullListOptions controls filtering for repository pull request listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.PullListOptions{State: "open", Labels: []int64{1, 2}}
|
||||
type PullListOptions struct {
|
||||
State string
|
||||
Sort string
|
||||
Milestone int64
|
||||
Labels []int64
|
||||
Poster string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the pull request list filters.
|
||||
func (o PullListOptions) String() string {
|
||||
return optionString("forge.PullListOptions",
|
||||
"state", o.State,
|
||||
"sort", o.Sort,
|
||||
"milestone", o.Milestone,
|
||||
"labels", o.Labels,
|
||||
"poster", o.Poster,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the pull request list filters.
|
||||
func (o PullListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o PullListOptions) addQuery(values url.Values) {
|
||||
if o.State != "" {
|
||||
values.Set("state", o.State)
|
||||
}
|
||||
if o.Sort != "" {
|
||||
values.Set("sort", o.Sort)
|
||||
}
|
||||
if o.Milestone != 0 {
|
||||
values.Set("milestone", strconv.FormatInt(o.Milestone, 10))
|
||||
}
|
||||
for _, label := range o.Labels {
|
||||
if label != 0 {
|
||||
values.Add("labels", strconv.FormatInt(label, 10))
|
||||
}
|
||||
}
|
||||
if o.Poster != "" {
|
||||
values.Set("poster", o.Poster)
|
||||
}
|
||||
}
|
||||
|
||||
func newPullService(c *Client) *PullService {
|
||||
return &PullService{
|
||||
Resource: *NewResource[types.PullRequest, types.CreatePullRequestOption, types.EditPullRequestOption](
|
||||
|
|
@ -75,132 +21,34 @@ func newPullService(c *Client) *PullService {
|
|||
}
|
||||
}
|
||||
|
||||
// ListPullRequests returns all pull requests in a repository.
|
||||
func (s *PullService) ListPullRequests(ctx context.Context, owner, repo string, filters ...PullListOptions) ([]types.PullRequest, error) {
|
||||
return s.listAll(ctx, owner, repo, filters...)
|
||||
}
|
||||
|
||||
// IterPullRequests returns an iterator over all pull requests in a repository.
|
||||
func (s *PullService) IterPullRequests(ctx context.Context, owner, repo string, filters ...PullListOptions) iter.Seq2[types.PullRequest, error] {
|
||||
return s.listIter(ctx, owner, repo, filters...)
|
||||
}
|
||||
|
||||
// CreatePullRequest creates a pull request in a repository.
|
||||
func (s *PullService) CreatePullRequest(ctx context.Context, owner, repo string, opts *types.CreatePullRequestOption) (*types.PullRequest, error) {
|
||||
var out types.PullRequest
|
||||
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/pulls", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// Merge merges a pull request. Method is one of "merge", "rebase", "rebase-merge", "squash", "fast-forward-only", "manually-merged".
|
||||
func (s *PullService) Merge(ctx context.Context, owner, repo string, index int64, method string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/merge", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index)
|
||||
body := map[string]string{"Do": method}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// CancelScheduledAutoMerge cancels the scheduled auto merge for a pull request.
|
||||
func (s *PullService) CancelScheduledAutoMerge(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/merge", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// Update updates a pull request branch with the base branch.
|
||||
func (s *PullService) Update(ctx context.Context, owner, repo string, index int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/update", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/update", owner, repo, index)
|
||||
return s.client.Post(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// GetDiffOrPatch returns a pull request diff or patch as raw bytes.
|
||||
func (s *PullService) GetDiffOrPatch(ctx context.Context, owner, repo string, index int64, diffType string) ([]byte, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}.{diffType}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "diffType", diffType))
|
||||
return s.client.GetRaw(ctx, path)
|
||||
}
|
||||
|
||||
// ListCommits returns all commits for a pull request.
|
||||
func (s *PullService) ListCommits(ctx context.Context, owner, repo string, index int64) ([]types.Commit, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/commits", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.Commit](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterCommits returns an iterator over all commits for a pull request.
|
||||
func (s *PullService) IterCommits(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Commit, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/commits", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.Commit](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// ListReviews returns all reviews on a pull request.
|
||||
func (s *PullService) ListReviews(ctx context.Context, owner, repo string, index int64) ([]types.PullReview, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index)
|
||||
return ListAll[types.PullReview](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterReviews returns an iterator over all reviews on a pull request.
|
||||
func (s *PullService) IterReviews(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.PullReview, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index)
|
||||
return ListIter[types.PullReview](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// ListFiles returns all changed files on a pull request.
|
||||
func (s *PullService) ListFiles(ctx context.Context, owner, repo string, index int64) ([]types.ChangedFile, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/files", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListAll[types.ChangedFile](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterFiles returns an iterator over all changed files on a pull request.
|
||||
func (s *PullService) IterFiles(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.ChangedFile, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/files", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return ListIter[types.ChangedFile](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetByBaseHead returns a pull request for a given base and head branch pair.
|
||||
func (s *PullService) GetByBaseHead(ctx context.Context, owner, repo, base, head string) (*types.PullRequest, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{base}/{head}", pathParams(
|
||||
"owner", owner,
|
||||
"repo", repo,
|
||||
"base", base,
|
||||
"head", head,
|
||||
))
|
||||
var out types.PullRequest
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ListReviewers returns all users who can be requested to review a pull request.
|
||||
func (s *PullService) ListReviewers(ctx context.Context, owner, repo string) ([]types.User, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/reviewers", pathParams("owner", owner, "repo", repo))
|
||||
return ListAll[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterReviewers returns an iterator over all users who can be requested to review a pull request.
|
||||
func (s *PullService) IterReviewers(ctx context.Context, owner, repo string) iter.Seq2[types.User, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/reviewers", pathParams("owner", owner, "repo", repo))
|
||||
return ListIter[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// RequestReviewers creates review requests for a pull request.
|
||||
func (s *PullService) RequestReviewers(ctx context.Context, owner, repo string, index int64, opts *types.PullReviewRequestOptions) ([]types.PullReview, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/requested_reviewers", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
var out []types.PullReview
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// CancelReviewRequests cancels review requests for a pull request.
|
||||
func (s *PullService) CancelReviewRequests(ctx context.Context, owner, repo string, index int64, opts *types.PullReviewRequestOptions) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/requested_reviewers", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
return s.client.DeleteWithBody(ctx, path, opts)
|
||||
}
|
||||
|
||||
// SubmitReview creates a new review on a pull request.
|
||||
func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, index int64, review *types.SubmitPullReviewOptions) (*types.PullReview, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index)))
|
||||
func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, index int64, review map[string]any) (*types.PullReview, error) {
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index)
|
||||
var out types.PullReview
|
||||
if err := s.client.Post(ctx, path, review, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -208,148 +56,15 @@ func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, inde
|
|||
return &out, nil
|
||||
}
|
||||
|
||||
// GetReview returns a single pull request review.
|
||||
func (s *PullService) GetReview(ctx context.Context, owner, repo string, index, reviewID int64) (*types.PullReview, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID)))
|
||||
var out types.PullReview
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteReview deletes a pull request review.
|
||||
func (s *PullService) DeleteReview(ctx context.Context, owner, repo string, index, reviewID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
func (s *PullService) listPage(ctx context.Context, owner, repo string, opts ListOptions, filters ...PullListOptions) (*PagedResult[types.PullRequest], error) {
|
||||
if opts.Page < 1 {
|
||||
opts.Page = 1
|
||||
}
|
||||
if opts.Limit < 1 {
|
||||
opts.Limit = defaultPageLimit
|
||||
}
|
||||
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls", pathParams("owner", owner, "repo", repo))
|
||||
u, err := url.Parse(path)
|
||||
if err != nil {
|
||||
return nil, core.E("PullService.listPage", "forge: parse path", err)
|
||||
}
|
||||
|
||||
values := u.Query()
|
||||
values.Set("page", strconv.Itoa(opts.Page))
|
||||
values.Set("limit", strconv.Itoa(opts.Limit))
|
||||
for _, filter := range filters {
|
||||
filter.addQuery(values)
|
||||
}
|
||||
u.RawQuery = values.Encode()
|
||||
|
||||
var items []types.PullRequest
|
||||
resp, err := s.client.doJSON(ctx, "GET", u.String(), nil, &items)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
totalCount, _ := strconv.Atoi(resp.Header.Get("X-Total-Count"))
|
||||
return &PagedResult[types.PullRequest]{
|
||||
Items: items,
|
||||
TotalCount: totalCount,
|
||||
Page: opts.Page,
|
||||
HasMore: (totalCount > 0 && (opts.Page-1)*opts.Limit+len(items) < totalCount) ||
|
||||
(totalCount == 0 && len(items) >= opts.Limit),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *PullService) listAll(ctx context.Context, owner, repo string, filters ...PullListOptions) ([]types.PullRequest, error) {
|
||||
var all []types.PullRequest
|
||||
page := 1
|
||||
|
||||
for {
|
||||
result, err := s.listPage(ctx, owner, repo, ListOptions{Page: page, Limit: defaultPageLimit}, filters...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
all = append(all, result.Items...)
|
||||
if !result.HasMore {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (s *PullService) listIter(ctx context.Context, owner, repo string, filters ...PullListOptions) iter.Seq2[types.PullRequest, error] {
|
||||
return func(yield func(types.PullRequest, error) bool) {
|
||||
page := 1
|
||||
for {
|
||||
result, err := s.listPage(ctx, owner, repo, ListOptions{Page: page, Limit: defaultPageLimit}, filters...)
|
||||
if err != nil {
|
||||
yield(*new(types.PullRequest), err)
|
||||
return
|
||||
}
|
||||
for _, item := range result.Items {
|
||||
if !yield(item, nil) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if !result.HasMore {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ListReviewComments returns all comments on a pull request review.
|
||||
func (s *PullService) ListReviewComments(ctx context.Context, owner, repo string, index, reviewID int64) ([]types.PullReviewComment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID)))
|
||||
return ListAll[types.PullReviewComment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterReviewComments returns an iterator over all comments on a pull request review.
|
||||
func (s *PullService) IterReviewComments(ctx context.Context, owner, repo string, index, reviewID int64) iter.Seq2[types.PullReviewComment, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID)))
|
||||
return ListIter[types.PullReviewComment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetReviewComment returns a single comment on a pull request review.
|
||||
func (s *PullService) GetReviewComment(ctx context.Context, owner, repo string, index, reviewID, commentID int64) (*types.PullReviewComment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID), "comment", int64String(commentID)))
|
||||
var out types.PullReviewComment
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateReviewComment creates a new comment on a pull request review.
|
||||
func (s *PullService) CreateReviewComment(ctx context.Context, owner, repo string, index, reviewID int64, opts *types.CreatePullReviewCommentOptions) (*types.PullReviewComment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID)))
|
||||
var out types.PullReviewComment
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// DeleteReviewComment deletes a comment on a pull request review.
|
||||
func (s *PullService) DeleteReviewComment(ctx context.Context, owner, repo string, index, reviewID, commentID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/comments/{comment}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID), "comment", int64String(commentID)))
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// DismissReview dismisses a pull request review.
|
||||
func (s *PullService) DismissReview(ctx context.Context, owner, repo string, index, reviewID int64, msg string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/dismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, reviewID)
|
||||
body := map[string]string{"message": msg}
|
||||
return s.client.Post(ctx, path, body, nil)
|
||||
}
|
||||
|
||||
// UndismissReview undismisses a pull request review.
|
||||
func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{id}/undismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "id", int64String(reviewID)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, reviewID)
|
||||
return s.client.Post(ctx, path, nil, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestPullService_ListPullRequests_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.PullRequest{{ID: 1, Title: "add feature"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
prs, err := f.Pulls.ListPullRequests(context.Background(), "core", "go-forge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(prs) != 1 || prs[0].Title != "add feature" {
|
||||
t.Fatalf("got %#v", prs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_CreatePullRequest_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.CreatePullRequestOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.Title != "add feature" {
|
||||
t.Fatalf("unexpected body: %+v", body)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.PullRequest{ID: 1, Title: body.Title, Index: 1})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
pr, err := f.Pulls.CreatePullRequest(context.Background(), "core", "go-forge", &types.CreatePullRequestOption{
|
||||
Title: "add feature",
|
||||
Base: "main",
|
||||
Head: "feature",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pr.Title != "add feature" {
|
||||
t.Fatalf("got title=%q", pr.Title)
|
||||
}
|
||||
}
|
||||
321
pulls_test.go
321
pulls_test.go
|
|
@ -2,16 +2,15 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestPullService_List_Good(t *testing.T) {
|
||||
func TestPullService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -42,54 +41,7 @@ func TestPullService_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPullService_ListFiltered_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
want := map[string]string{
|
||||
"state": "open",
|
||||
"sort": "priority",
|
||||
"milestone": "7",
|
||||
"poster": "alice",
|
||||
"page": "1",
|
||||
"limit": "50",
|
||||
}
|
||||
for key, wantValue := range want {
|
||||
if got := r.URL.Query().Get(key); got != wantValue {
|
||||
t.Errorf("got %s=%q, want %q", key, got, wantValue)
|
||||
}
|
||||
}
|
||||
if got := r.URL.Query()["labels"]; !reflect.DeepEqual(got, []string{"1", "2"}) {
|
||||
t.Errorf("got labels=%v, want %v", got, []string{"1", "2"})
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.PullRequest{{ID: 1, Title: "add feature"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
prs, err := f.Pulls.ListPullRequests(context.Background(), "core", "go-forge", PullListOptions{
|
||||
State: "open",
|
||||
Sort: "priority",
|
||||
Milestone: 7,
|
||||
Labels: []int64{1, 2},
|
||||
Poster: "alice",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(prs) != 1 || prs[0].Title != "add feature" {
|
||||
t.Fatalf("got %#v", prs)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_Get_Good(t *testing.T) {
|
||||
func TestPullService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -111,7 +63,7 @@ func TestPullService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPullService_Create_Good(t *testing.T) {
|
||||
func TestPullService_Good_Create(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -142,248 +94,7 @@ func TestPullService_Create_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPullService_ListReviewers_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/reviewers" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.User{
|
||||
{UserName: "alice"},
|
||||
{UserName: "bob"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
reviewers, err := f.Pulls.ListReviewers(context.Background(), "core", "go-forge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(reviewers) != 2 || reviewers[0].UserName != "alice" || reviewers[1].UserName != "bob" {
|
||||
t.Fatalf("got %#v", reviewers)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_ListFiles_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls/7/files" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.ChangedFile{
|
||||
{Filename: "README.md", Status: "modified", Additions: 2, Deletions: 1},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
files, err := f.Pulls.ListFiles(context.Background(), "core", "go-forge", 7)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(files) != 1 {
|
||||
t.Fatalf("got %d files, want 1", len(files))
|
||||
}
|
||||
if files[0].Filename != "README.md" || files[0].Status != "modified" {
|
||||
t.Fatalf("got %#v", files[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_GetByBaseHead_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls/main/feature" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.PullRequest{Index: 7, Title: "Add feature"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
pr, err := f.Pulls.GetByBaseHead(context.Background(), "core", "go-forge", "main", "feature")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if pr.Index != 7 || pr.Title != "Add feature" {
|
||||
t.Fatalf("got %+v", pr)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_IterFiles_Good(t *testing.T) {
|
||||
requests := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls/7/files" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
switch requests {
|
||||
case 1:
|
||||
if got := r.URL.Query().Get("page"); got != "1" {
|
||||
t.Errorf("got page=%q, want %q", got, "1")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.ChangedFile{{Filename: "README.md", Status: "modified"}})
|
||||
case 2:
|
||||
if got := r.URL.Query().Get("page"); got != "2" {
|
||||
t.Errorf("got page=%q, want %q", got, "2")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.ChangedFile{{Filename: "docs/guide.md", Status: "added"}})
|
||||
default:
|
||||
t.Fatalf("unexpected request %d", requests)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []string
|
||||
for file, err := range f.Pulls.IterFiles(context.Background(), "core", "go-forge", 7) {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, file.Filename)
|
||||
}
|
||||
if len(got) != 2 || got[0] != "README.md" || got[1] != "docs/guide.md" {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_IterReviewers_Good(t *testing.T) {
|
||||
requests := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
requests++
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/reviewers" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
switch requests {
|
||||
case 1:
|
||||
if got := r.URL.Query().Get("page"); got != "1" {
|
||||
t.Errorf("got page=%q, want %q", got, "1")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.User{{UserName: "alice"}})
|
||||
case 2:
|
||||
if got := r.URL.Query().Get("page"); got != "2" {
|
||||
t.Errorf("got page=%q, want %q", got, "2")
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "2")
|
||||
json.NewEncoder(w).Encode([]types.User{{UserName: "bob"}})
|
||||
default:
|
||||
t.Fatalf("unexpected request %d", requests)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
var got []string
|
||||
for reviewer, err := range f.Pulls.IterReviewers(context.Background(), "core", "go-forge") {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got = append(got, reviewer.UserName)
|
||||
}
|
||||
if len(got) != 2 || got[0] != "alice" || got[1] != "bob" {
|
||||
t.Fatalf("got %#v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_RequestReviewers_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls/7/requested_reviewers" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var body types.PullReviewRequestOptions
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
if len(body.Reviewers) != 2 || body.Reviewers[0] != "alice" || body.Reviewers[1] != "bob" {
|
||||
t.Fatalf("got reviewers %#v", body.Reviewers)
|
||||
}
|
||||
if len(body.TeamReviewers) != 1 || body.TeamReviewers[0] != "platform" {
|
||||
t.Fatalf("got team reviewers %#v", body.TeamReviewers)
|
||||
}
|
||||
json.NewEncoder(w).Encode([]types.PullReview{
|
||||
{ID: 101, Body: "requested"},
|
||||
})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
reviews, err := f.Pulls.RequestReviewers(context.Background(), "core", "go-forge", 7, &types.PullReviewRequestOptions{
|
||||
Reviewers: []string{"alice", "bob"},
|
||||
TeamReviewers: []string{"platform"},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(reviews) != 1 || reviews[0].ID != 101 || reviews[0].Body != "requested" {
|
||||
t.Fatalf("got %#v", reviews)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_CancelReviewRequests_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls/7/requested_reviewers" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var body types.PullReviewRequestOptions
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode body: %v", err)
|
||||
}
|
||||
if len(body.Reviewers) != 1 || body.Reviewers[0] != "alice" {
|
||||
t.Fatalf("got reviewers %#v", body.Reviewers)
|
||||
}
|
||||
if len(body.TeamReviewers) != 1 || body.TeamReviewers[0] != "platform" {
|
||||
t.Fatalf("got team reviewers %#v", body.TeamReviewers)
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Pulls.CancelReviewRequests(context.Background(), "core", "go-forge", 7, &types.PullReviewRequestOptions{
|
||||
Reviewers: []string{"alice"},
|
||||
TeamReviewers: []string{"platform"},
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_Merge_Good(t *testing.T) {
|
||||
func TestPullService_Good_Merge(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -407,7 +118,7 @@ func TestPullService_Merge_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestPullService_Merge_Bad(t *testing.T) {
|
||||
func TestPullService_Bad_Merge(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusConflict)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "already merged"})
|
||||
|
|
@ -419,23 +130,3 @@ func TestPullService_Merge_Bad(t *testing.T) {
|
|||
t.Fatalf("expected conflict, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullService_CancelScheduledAutoMerge_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/pulls/7/merge" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
if err := f.Pulls.CancelScheduledAutoMerge(context.Background(), "core", "go-forge", 7); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
178
releases.go
178
releases.go
|
|
@ -2,96 +2,17 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
goio "io"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// ReleaseService handles release operations within a repository.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Releases.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
type ReleaseService struct {
|
||||
Resource[types.Release, types.CreateReleaseOption, types.EditReleaseOption]
|
||||
}
|
||||
|
||||
// ReleaseListOptions controls filtering for repository release listings.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.ReleaseListOptions{Draft: true, Query: "1.0"}
|
||||
type ReleaseListOptions struct {
|
||||
Draft bool
|
||||
PreRelease bool
|
||||
Query string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the release list filters.
|
||||
func (o ReleaseListOptions) String() string {
|
||||
return optionString("forge.ReleaseListOptions",
|
||||
"draft", o.Draft,
|
||||
"pre-release", o.PreRelease,
|
||||
"q", o.Query,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the release list filters.
|
||||
func (o ReleaseListOptions) GoString() string { return o.String() }
|
||||
|
||||
func (o ReleaseListOptions) queryParams() map[string]string {
|
||||
query := make(map[string]string, 3)
|
||||
if o.Draft {
|
||||
query["draft"] = strconv.FormatBool(true)
|
||||
}
|
||||
if o.PreRelease {
|
||||
query["pre-release"] = strconv.FormatBool(true)
|
||||
}
|
||||
if o.Query != "" {
|
||||
query["q"] = o.Query
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
// ReleaseAttachmentUploadOptions controls metadata sent when uploading a release attachment.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := forge.ReleaseAttachmentUploadOptions{Name: "release.zip"}
|
||||
type ReleaseAttachmentUploadOptions struct {
|
||||
Name string
|
||||
ExternalURL string
|
||||
}
|
||||
|
||||
// String returns a safe summary of the release attachment upload metadata.
|
||||
func (o ReleaseAttachmentUploadOptions) String() string {
|
||||
return optionString("forge.ReleaseAttachmentUploadOptions",
|
||||
"name", o.Name,
|
||||
"external_url", o.ExternalURL,
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the release attachment upload metadata.
|
||||
func (o ReleaseAttachmentUploadOptions) GoString() string { return o.String() }
|
||||
|
||||
func releaseAttachmentUploadQuery(opts *ReleaseAttachmentUploadOptions) map[string]string {
|
||||
if opts == nil || opts.Name == "" {
|
||||
return nil
|
||||
}
|
||||
query := make(map[string]string, 1)
|
||||
if opts.Name != "" {
|
||||
query["name"] = opts.Name
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
||||
func newReleaseService(c *Client) *ReleaseService {
|
||||
return &ReleaseService{
|
||||
Resource: *NewResource[types.Release, types.CreateReleaseOption, types.EditReleaseOption](
|
||||
|
|
@ -100,40 +21,9 @@ func newReleaseService(c *Client) *ReleaseService {
|
|||
}
|
||||
}
|
||||
|
||||
// ListReleases returns all releases in a repository.
|
||||
func (s *ReleaseService) ListReleases(ctx context.Context, owner, repo string, filters ...ReleaseListOptions) ([]types.Release, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases", pathParams("owner", owner, "repo", repo))
|
||||
return ListAll[types.Release](ctx, s.client, path, releaseListQuery(filters...))
|
||||
}
|
||||
|
||||
// IterReleases returns an iterator over all releases in a repository.
|
||||
func (s *ReleaseService) IterReleases(ctx context.Context, owner, repo string, filters ...ReleaseListOptions) iter.Seq2[types.Release, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases", pathParams("owner", owner, "repo", repo))
|
||||
return ListIter[types.Release](ctx, s.client, path, releaseListQuery(filters...))
|
||||
}
|
||||
|
||||
// CreateRelease creates a release in a repository.
|
||||
func (s *ReleaseService) CreateRelease(ctx context.Context, owner, repo string, opts *types.CreateReleaseOption) (*types.Release, error) {
|
||||
var out types.Release
|
||||
if err := s.client.Post(ctx, ResolvePath("/api/v1/repos/{owner}/{repo}/releases", pathParams("owner", owner, "repo", repo)), opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetByTag returns a release by its tag name.
|
||||
func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) (*types.Release, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/tags/{tag}", pathParams("owner", owner, "repo", repo, "tag", tag))
|
||||
var out types.Release
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// GetLatest returns the most recent non-prerelease, non-draft release.
|
||||
func (s *ReleaseService) GetLatest(ctx context.Context, owner, repo string) (*types.Release, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/latest", pathParams("owner", owner, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner, repo, tag)
|
||||
var out types.Release
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -143,66 +33,25 @@ func (s *ReleaseService) GetLatest(ctx context.Context, owner, repo string) (*ty
|
|||
|
||||
// DeleteByTag deletes a release by its tag name.
|
||||
func (s *ReleaseService) DeleteByTag(ctx context.Context, owner, repo, tag string) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/tags/{tag}", pathParams("owner", owner, "repo", repo, "tag", tag))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner, repo, tag)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListAssets returns all assets for a release.
|
||||
func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, releaseID int64) ([]types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(releaseID)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner, repo, releaseID)
|
||||
return ListAll[types.Attachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// CreateAttachment uploads a new attachment to a release.
|
||||
//
|
||||
// If opts.ExternalURL is set, the upload uses the external_url form field and
|
||||
// ignores filename/content.
|
||||
func (s *ReleaseService) CreateAttachment(ctx context.Context, owner, repo string, releaseID int64, opts *ReleaseAttachmentUploadOptions, filename string, content goio.Reader) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(releaseID)))
|
||||
fields := make(map[string]string, 1)
|
||||
fieldName := "attachment"
|
||||
if opts != nil && opts.ExternalURL != "" {
|
||||
fields["external_url"] = opts.ExternalURL
|
||||
fieldName = ""
|
||||
filename = ""
|
||||
content = nil
|
||||
}
|
||||
var out types.Attachment
|
||||
if err := s.client.postMultipartJSON(ctx, path, releaseAttachmentUploadQuery(opts), fields, fieldName, filename, content, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// EditAttachment updates a release attachment.
|
||||
func (s *ReleaseService) EditAttachment(ctx context.Context, owner, repo string, releaseID, attachmentID int64, opts *types.EditAttachmentOptions) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{id}/assets/{attachment_id}", pathParams("owner", owner, "repo", repo, "id", int64String(releaseID), "attachment_id", int64String(attachmentID)))
|
||||
var out types.Attachment
|
||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// CreateAsset uploads a new asset to a release.
|
||||
func (s *ReleaseService) CreateAsset(ctx context.Context, owner, repo string, releaseID int64, opts *ReleaseAttachmentUploadOptions, filename string, content goio.Reader) (*types.Attachment, error) {
|
||||
return s.CreateAttachment(ctx, owner, repo, releaseID, opts, filename, content)
|
||||
}
|
||||
|
||||
// EditAsset updates a release asset.
|
||||
func (s *ReleaseService) EditAsset(ctx context.Context, owner, repo string, releaseID, attachmentID int64, opts *types.EditAttachmentOptions) (*types.Attachment, error) {
|
||||
return s.EditAttachment(ctx, owner, repo, releaseID, attachmentID, opts)
|
||||
}
|
||||
|
||||
// IterAssets returns an iterator over all assets for a release.
|
||||
func (s *ReleaseService) IterAssets(ctx context.Context, owner, repo string, releaseID int64) iter.Seq2[types.Attachment, error] {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{id}/assets", pathParams("owner", owner, "repo", repo, "id", int64String(releaseID)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner, repo, releaseID)
|
||||
return ListIter[types.Attachment](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// GetAsset returns a single asset for a release.
|
||||
func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) (*types.Attachment, error) {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets/{assetID}", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID), "assetID", int64String(assetID)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID)
|
||||
var out types.Attachment
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
|
|
@ -212,19 +61,6 @@ func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, relea
|
|||
|
||||
// DeleteAsset deletes a single asset from a release.
|
||||
func (s *ReleaseService) DeleteAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) error {
|
||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets/{assetID}", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID), "assetID", int64String(assetID)))
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
func releaseListQuery(filters ...ReleaseListOptions) map[string]string {
|
||||
query := make(map[string]string, len(filters))
|
||||
for _, filter := range filters {
|
||||
for key, value := range filter.queryParams() {
|
||||
query[key] = value
|
||||
}
|
||||
}
|
||||
if len(query) == 0 {
|
||||
return nil
|
||||
}
|
||||
return query
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,66 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestReleaseService_ListReleases_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/releases" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Release{{ID: 1, TagName: "v1.0.0"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
releases, err := f.Releases.ListReleases(context.Background(), "core", "go-forge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(releases) != 1 || releases[0].TagName != "v1.0.0" {
|
||||
t.Fatalf("got %#v", releases)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_CreateRelease_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/releases" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var body types.CreateReleaseOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if body.TagName != "v1.0.0" {
|
||||
t.Fatalf("unexpected body: %+v", body)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Release{ID: 1, TagName: body.TagName})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
release, err := f.Releases.CreateRelease(context.Background(), "core", "go-forge", &types.CreateReleaseOption{
|
||||
TagName: "v1.0.0",
|
||||
Title: "Release 1.0",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if release.TagName != "v1.0.0" {
|
||||
t.Fatalf("got tag=%q", release.TagName)
|
||||
}
|
||||
}
|
||||
240
releases_test.go
240
releases_test.go
|
|
@ -1,64 +1,16 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"io"
|
||||
"mime"
|
||||
"mime/multipart"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func readMultipartReleaseAttachment(t *testing.T, r *http.Request) (map[string]string, string, string) {
|
||||
t.Helper()
|
||||
|
||||
mediaType, params, err := mime.ParseMediaType(r.Header.Get("Content-Type"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mediaType != "multipart/form-data" {
|
||||
t.Fatalf("got content-type=%q", mediaType)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
fields := make(map[string]string)
|
||||
reader := multipart.NewReader(bytes.NewReader(body), params["boundary"])
|
||||
var fileName string
|
||||
var fileContent string
|
||||
for {
|
||||
part, err := reader.NextPart()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
data, err := io.ReadAll(part)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if part.FormName() == "attachment" {
|
||||
fileName = part.FileName()
|
||||
fileContent = string(data)
|
||||
continue
|
||||
}
|
||||
fields[part.FormName()] = string(data)
|
||||
}
|
||||
|
||||
return fields, fileName, fileContent
|
||||
}
|
||||
|
||||
func TestReleaseService_List_Good(t *testing.T) {
|
||||
func TestReleaseService_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -84,48 +36,7 @@ func TestReleaseService_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_ListFiltered_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/releases" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
want := map[string]string{
|
||||
"draft": "true",
|
||||
"pre-release": "true",
|
||||
"q": "1.0",
|
||||
"page": "1",
|
||||
"limit": "50",
|
||||
}
|
||||
for key, wantValue := range want {
|
||||
if got := r.URL.Query().Get(key); got != wantValue {
|
||||
t.Errorf("got %s=%q, want %q", key, got, wantValue)
|
||||
}
|
||||
}
|
||||
w.Header().Set("X-Total-Count", "1")
|
||||
json.NewEncoder(w).Encode([]types.Release{{ID: 1, TagName: "v1.0.0", Title: "Release 1.0"}})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
releases, err := f.Releases.ListReleases(context.Background(), "core", "go-forge", ReleaseListOptions{
|
||||
Draft: true,
|
||||
PreRelease: true,
|
||||
Query: "1.0",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(releases) != 1 || releases[0].TagName != "v1.0.0" {
|
||||
t.Fatalf("got %#v", releases)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_Get_Good(t *testing.T) {
|
||||
func TestReleaseService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -150,7 +61,7 @@ func TestReleaseService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_GetByTag_Good(t *testing.T) {
|
||||
func TestReleaseService_Good_GetByTag(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -174,146 +85,3 @@ func TestReleaseService_GetByTag_Good(t *testing.T) {
|
|||
t.Errorf("got id=%d, want 1", release.ID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_GetLatest_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/releases/latest" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Release{ID: 3, TagName: "v2.1.0", Title: "Latest Release"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
release, err := f.Releases.GetLatest(context.Background(), "core", "go-forge")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if release.TagName != "v2.1.0" {
|
||||
t.Errorf("got tag=%q, want %q", release.TagName, "v2.1.0")
|
||||
}
|
||||
if release.Title != "Latest Release" {
|
||||
t.Errorf("got title=%q, want %q", release.Title, "Latest Release")
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_CreateAttachment_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/releases/1/assets" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if got := r.URL.Query().Get("name"); got != "linux-amd64" {
|
||||
t.Fatalf("got name=%q", got)
|
||||
}
|
||||
fields, filename, content := readMultipartReleaseAttachment(t, r)
|
||||
if !reflect.DeepEqual(fields, map[string]string{}) {
|
||||
t.Fatalf("got fields=%#v", fields)
|
||||
}
|
||||
if filename != "release.tar.gz" {
|
||||
t.Fatalf("got filename=%q", filename)
|
||||
}
|
||||
if content != "release bytes" {
|
||||
t.Fatalf("got content=%q", content)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(types.Attachment{ID: 9, Name: filename})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
attachment, err := f.Releases.CreateAttachment(
|
||||
context.Background(),
|
||||
"core",
|
||||
"go-forge",
|
||||
1,
|
||||
&ReleaseAttachmentUploadOptions{Name: "linux-amd64"},
|
||||
"release.tar.gz",
|
||||
bytes.NewBufferString("release bytes"),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if attachment.Name != "release.tar.gz" {
|
||||
t.Fatalf("got name=%q", attachment.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_CreateAttachmentExternalURL_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/releases/1/assets" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
if got := r.URL.Query().Get("name"); got != "docs" {
|
||||
t.Fatalf("got name=%q", got)
|
||||
}
|
||||
fields, filename, content := readMultipartReleaseAttachment(t, r)
|
||||
if !reflect.DeepEqual(fields, map[string]string{"external_url": "https://example.com/release.tar.gz"}) {
|
||||
t.Fatalf("got fields=%#v", fields)
|
||||
}
|
||||
if filename != "" || content != "" {
|
||||
t.Fatalf("unexpected file upload: filename=%q content=%q", filename, content)
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
json.NewEncoder(w).Encode(types.Attachment{ID: 10, Name: "docs"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
attachment, err := f.Releases.CreateAttachment(
|
||||
context.Background(),
|
||||
"core",
|
||||
"go-forge",
|
||||
1,
|
||||
&ReleaseAttachmentUploadOptions{Name: "docs", ExternalURL: "https://example.com/release.tar.gz"},
|
||||
"",
|
||||
nil,
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if attachment.Name != "docs" {
|
||||
t.Fatalf("got name=%q", attachment.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReleaseService_EditAttachment_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge/releases/1/assets/4" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
var body types.EditAttachmentOptions
|
||||
json.NewDecoder(r.Body).Decode(&body)
|
||||
if body.Name != "release-notes.pdf" {
|
||||
t.Fatalf("got body=%#v", body)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Attachment{ID: 4, Name: body.Name})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
attachment, err := f.Releases.EditAttachment(context.Background(), "core", "go-forge", 1, 4, &types.EditAttachmentOptions{Name: "release-notes.pdf"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if attachment.Name != "release-notes.pdf" {
|
||||
t.Fatalf("got name=%q", attachment.Name)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2277
repos_test.go
2277
repos_test.go
File diff suppressed because it is too large
Load diff
73
resource.go
73
resource.go
|
|
@ -3,64 +3,27 @@ package forge
|
|||
import (
|
||||
"context"
|
||||
"iter"
|
||||
"strconv"
|
||||
|
||||
core "dappco.re/go/core"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Resource provides generic CRUD operations for a Forgejo API resource.
|
||||
// T is the resource type, C is the create options type, U is the update options type.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// r := forge.NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](client, "/api/v1/repos/{owner}/{repo}/issues/{index}")
|
||||
// _ = r
|
||||
type Resource[T any, C any, U any] struct {
|
||||
client *Client
|
||||
path string // item path: /api/v1/repos/{owner}/{repo}/issues/{index}
|
||||
collection string // collection path: /api/v1/repos/{owner}/{repo}/issues
|
||||
}
|
||||
|
||||
// String returns a safe summary of the resource configuration.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := res.String()
|
||||
func (r *Resource[T, C, U]) String() string {
|
||||
if r == nil {
|
||||
return "forge.Resource{<nil>}"
|
||||
}
|
||||
return core.Concat(
|
||||
"forge.Resource{path=",
|
||||
strconv.Quote(r.path),
|
||||
", collection=",
|
||||
strconv.Quote(r.collection),
|
||||
"}",
|
||||
)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the resource configuration.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := fmt.Sprintf("%#v", res)
|
||||
func (r *Resource[T, C, U]) GoString() string { return r.String() }
|
||||
|
||||
// NewResource creates a new Resource for the given path pattern.
|
||||
// The path should be the item path (e.g., /repos/{owner}/{repo}/issues/{index}).
|
||||
// The collection path is derived by stripping the last /{placeholder} segment.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// r := forge.NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](client, "/api/v1/repos/{owner}/{repo}/issues/{index}")
|
||||
// _ = r
|
||||
func NewResource[T any, C any, U any](c *Client, path string) *Resource[T, C, U] {
|
||||
collection := path
|
||||
// Strip last segment if it's a pure placeholder like /{index}
|
||||
// Don't strip if mixed like /repos or /{org}/repos
|
||||
if i := lastIndexByte(path, '/'); i >= 0 {
|
||||
if i := strings.LastIndex(path, "/"); i >= 0 {
|
||||
lastSeg := path[i+1:]
|
||||
if core.HasPrefix(lastSeg, "{") && core.HasSuffix(lastSeg, "}") {
|
||||
if strings.HasPrefix(lastSeg, "{") && strings.HasSuffix(lastSeg, "}") {
|
||||
collection = path[:i]
|
||||
}
|
||||
}
|
||||
|
|
@ -68,39 +31,21 @@ func NewResource[T any, C any, U any](c *Client, path string) *Resource[T, C, U]
|
|||
}
|
||||
|
||||
// List returns a single page of resources.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// page, err := res.List(ctx, forge.Params{"owner": "core"}, forge.DefaultList)
|
||||
func (r *Resource[T, C, U]) List(ctx context.Context, params Params, opts ListOptions) (*PagedResult[T], error) {
|
||||
return ListPage[T](ctx, r.client, ResolvePath(r.collection, params), nil, opts)
|
||||
}
|
||||
|
||||
// ListAll returns all resources across all pages.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// items, err := res.ListAll(ctx, forge.Params{"owner": "core"})
|
||||
func (r *Resource[T, C, U]) ListAll(ctx context.Context, params Params) ([]T, error) {
|
||||
return ListAll[T](ctx, r.client, ResolvePath(r.collection, params), nil)
|
||||
}
|
||||
|
||||
// Iter returns an iterator over all resources across all pages.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// for item, err := range res.Iter(ctx, forge.Params{"owner": "core"}) {
|
||||
// _, _ = item, err
|
||||
// }
|
||||
func (r *Resource[T, C, U]) Iter(ctx context.Context, params Params) iter.Seq2[T, error] {
|
||||
return ListIter[T](ctx, r.client, ResolvePath(r.collection, params), nil)
|
||||
}
|
||||
|
||||
// Get returns a single resource by appending id to the path.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// item, err := res.Get(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
func (r *Resource[T, C, U]) Get(ctx context.Context, params Params) (*T, error) {
|
||||
var out T
|
||||
if err := r.client.Get(ctx, ResolvePath(r.path, params), &out); err != nil {
|
||||
|
|
@ -110,10 +55,6 @@ func (r *Resource[T, C, U]) Get(ctx context.Context, params Params) (*T, error)
|
|||
}
|
||||
|
||||
// Create creates a new resource.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// item, err := res.Create(ctx, forge.Params{"owner": "core"}, body)
|
||||
func (r *Resource[T, C, U]) Create(ctx context.Context, params Params, body *C) (*T, error) {
|
||||
var out T
|
||||
if err := r.client.Post(ctx, ResolvePath(r.collection, params), body, &out); err != nil {
|
||||
|
|
@ -123,10 +64,6 @@ func (r *Resource[T, C, U]) Create(ctx context.Context, params Params, body *C)
|
|||
}
|
||||
|
||||
// Update modifies an existing resource.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// item, err := res.Update(ctx, forge.Params{"owner": "core", "repo": "go-forge"}, body)
|
||||
func (r *Resource[T, C, U]) Update(ctx context.Context, params Params, body *U) (*T, error) {
|
||||
var out T
|
||||
if err := r.client.Patch(ctx, ResolvePath(r.path, params), body, &out); err != nil {
|
||||
|
|
@ -136,10 +73,6 @@ func (r *Resource[T, C, U]) Update(ctx context.Context, params Params, body *U)
|
|||
}
|
||||
|
||||
// Delete removes a resource.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// err := res.Delete(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
func (r *Resource[T, C, U]) Delete(ctx context.Context, params Params) error {
|
||||
return r.client.Delete(ctx, ResolvePath(r.path, params))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResource_String_Good(t *testing.T) {
|
||||
res := NewResource[int, struct{}, struct{}](NewClient("https://forge.lthn.ai", "tok"), "/api/v1/repos/{owner}/{repo}")
|
||||
got := fmt.Sprint(res)
|
||||
want := `forge.Resource{path="/api/v1/repos/{owner}/{repo}", collection="/api/v1/repos/{owner}"}`
|
||||
if got != want {
|
||||
t.Fatalf("got %q, want %q", got, want)
|
||||
}
|
||||
if got := res.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", res); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -22,7 +22,7 @@ type testUpdate struct {
|
|||
Name *string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func TestResource_List_Good(t *testing.T) {
|
||||
func TestResource_Good_List(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/orgs/core/repos" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
|
|
@ -44,7 +44,7 @@ func TestResource_List_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_Get_Good(t *testing.T) {
|
||||
func TestResource_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/api/v1/repos/core/go-forge" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
|
|
@ -65,7 +65,7 @@ func TestResource_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_Create_Good(t *testing.T) {
|
||||
func TestResource_Good_Create(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
|
|
@ -94,7 +94,7 @@ func TestResource_Create_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_Update_Good(t *testing.T) {
|
||||
func TestResource_Good_Update(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPatch {
|
||||
t.Errorf("expected PATCH, got %s", r.Method)
|
||||
|
|
@ -116,7 +116,7 @@ func TestResource_Update_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_Delete_Good(t *testing.T) {
|
||||
func TestResource_Good_Delete(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodDelete {
|
||||
t.Errorf("expected DELETE, got %s", r.Method)
|
||||
|
|
@ -134,7 +134,7 @@ func TestResource_Delete_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_ListAll_Good(t *testing.T) {
|
||||
func TestResource_Good_ListAll(t *testing.T) {
|
||||
page := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
page++
|
||||
|
|
@ -159,7 +159,7 @@ func TestResource_ListAll_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_Iter_Good(t *testing.T) {
|
||||
func TestResource_Good_Iter(t *testing.T) {
|
||||
page := 0
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
page++
|
||||
|
|
@ -190,7 +190,7 @@ func TestResource_Iter_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_IterError_Bad(t *testing.T) {
|
||||
func TestResource_Bad_IterError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
json.NewEncoder(w).Encode(map[string]string{"message": "server error"})
|
||||
|
|
@ -212,7 +212,7 @@ func TestResource_IterError_Bad(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResource_IterBreakEarly_Good(t *testing.T) {
|
||||
func TestResource_Good_IterBreakEarly(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("X-Total-Count", "100")
|
||||
json.NewEncoder(w).Encode([]testItem{{1, "a"}, {2, "b"}, {3, "c"}})
|
||||
|
|
|
|||
|
|
@ -1,421 +0,0 @@
|
|||
package forge
|
||||
|
||||
// String returns a safe summary of the actions service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ActionsService{}
|
||||
// _ = s.String()
|
||||
func (s *ActionsService) String() string {
|
||||
if s == nil {
|
||||
return "forge.ActionsService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.ActionsService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the actions service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ActionsService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *ActionsService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the ActivityPub service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ActivityPubService{}
|
||||
// _ = s.String()
|
||||
func (s *ActivityPubService) String() string {
|
||||
if s == nil {
|
||||
return "forge.ActivityPubService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.ActivityPubService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the ActivityPub service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ActivityPubService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *ActivityPubService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the admin service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.AdminService{}
|
||||
// _ = s.String()
|
||||
func (s *AdminService) String() string {
|
||||
if s == nil {
|
||||
return "forge.AdminService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.AdminService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the admin service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.AdminService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *AdminService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the branch service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.BranchService{}
|
||||
// _ = s.String()
|
||||
func (s *BranchService) String() string {
|
||||
if s == nil {
|
||||
return "forge.BranchService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.BranchService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the branch service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.BranchService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *BranchService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the commit service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.CommitService{}
|
||||
// _ = s.String()
|
||||
func (s *CommitService) String() string {
|
||||
if s == nil {
|
||||
return "forge.CommitService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.CommitService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the commit service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.CommitService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *CommitService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the content service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ContentService{}
|
||||
// _ = s.String()
|
||||
func (s *ContentService) String() string {
|
||||
if s == nil {
|
||||
return "forge.ContentService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.ContentService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the content service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ContentService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *ContentService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the issue service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.IssueService{}
|
||||
// _ = s.String()
|
||||
func (s *IssueService) String() string {
|
||||
if s == nil {
|
||||
return "forge.IssueService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.IssueService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the issue service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.IssueService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *IssueService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the label service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.LabelService{}
|
||||
// _ = s.String()
|
||||
func (s *LabelService) String() string {
|
||||
if s == nil {
|
||||
return "forge.LabelService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.LabelService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the label service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.LabelService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *LabelService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the milestone service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.MilestoneService{}
|
||||
// _ = s.String()
|
||||
func (s *MilestoneService) String() string {
|
||||
if s == nil {
|
||||
return "forge.MilestoneService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.MilestoneService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the milestone service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.MilestoneService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *MilestoneService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the misc service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.MiscService{}
|
||||
// _ = s.String()
|
||||
func (s *MiscService) String() string {
|
||||
if s == nil {
|
||||
return "forge.MiscService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.MiscService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the misc service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.MiscService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *MiscService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the notification service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.NotificationService{}
|
||||
// _ = s.String()
|
||||
func (s *NotificationService) String() string {
|
||||
if s == nil {
|
||||
return "forge.NotificationService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.NotificationService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the notification service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.NotificationService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *NotificationService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the organisation service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.OrgService{}
|
||||
// _ = s.String()
|
||||
func (s *OrgService) String() string {
|
||||
if s == nil {
|
||||
return "forge.OrgService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.OrgService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the organisation service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.OrgService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *OrgService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the package service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.PackageService{}
|
||||
// _ = s.String()
|
||||
func (s *PackageService) String() string {
|
||||
if s == nil {
|
||||
return "forge.PackageService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.PackageService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the package service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.PackageService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *PackageService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the pull request service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.PullService{}
|
||||
// _ = s.String()
|
||||
func (s *PullService) String() string {
|
||||
if s == nil {
|
||||
return "forge.PullService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.PullService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the pull request service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.PullService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *PullService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the release service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ReleaseService{}
|
||||
// _ = s.String()
|
||||
func (s *ReleaseService) String() string {
|
||||
if s == nil {
|
||||
return "forge.ReleaseService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.ReleaseService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the release service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.ReleaseService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *ReleaseService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the repository service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.RepoService{}
|
||||
// _ = s.String()
|
||||
func (s *RepoService) String() string {
|
||||
if s == nil {
|
||||
return "forge.RepoService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.RepoService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the repository service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.RepoService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *RepoService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the team service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.TeamService{}
|
||||
// _ = s.String()
|
||||
func (s *TeamService) String() string {
|
||||
if s == nil {
|
||||
return "forge.TeamService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.TeamService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the team service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.TeamService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *TeamService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the user service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.UserService{}
|
||||
// _ = s.String()
|
||||
func (s *UserService) String() string {
|
||||
if s == nil {
|
||||
return "forge.UserService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.UserService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the user service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.UserService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *UserService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the webhook service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.WebhookService{}
|
||||
// _ = s.String()
|
||||
func (s *WebhookService) String() string {
|
||||
if s == nil {
|
||||
return "forge.WebhookService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.WebhookService", "resource", &s.Resource)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the webhook service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.WebhookService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *WebhookService) GoString() string { return s.String() }
|
||||
|
||||
// String returns a safe summary of the wiki service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.WikiService{}
|
||||
// _ = s.String()
|
||||
func (s *WikiService) String() string {
|
||||
if s == nil {
|
||||
return "forge.WikiService{<nil>}"
|
||||
}
|
||||
return serviceString("forge.WikiService", "client", s.client)
|
||||
}
|
||||
|
||||
// GoString returns a safe Go-syntax summary of the wiki service.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// s := &forge.WikiService{}
|
||||
// _ = fmt.Sprintf("%#v", s)
|
||||
func (s *WikiService) GoString() string { return s.String() }
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
package forge
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestClient_String_NilSafe(t *testing.T) {
|
||||
var c *Client
|
||||
want := "forge.Client{<nil>}"
|
||||
if got := c.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(c); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", c); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestForge_String_NilSafe(t *testing.T) {
|
||||
var f *Forge
|
||||
want := "forge.Forge{<nil>}"
|
||||
if got := f.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(f); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", f); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResource_String_NilSafe(t *testing.T) {
|
||||
var r *Resource[int, struct{}, struct{}]
|
||||
want := "forge.Resource{<nil>}"
|
||||
if got := r.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(r); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", r); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIError_String_NilSafe(t *testing.T) {
|
||||
var e *APIError
|
||||
want := "forge.APIError{<nil>}"
|
||||
if got := e.String(); got != want {
|
||||
t.Fatalf("got String()=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprint(e); got != want {
|
||||
t.Fatalf("got fmt.Sprint=%q, want %q", got, want)
|
||||
}
|
||||
if got := fmt.Sprintf("%#v", e); got != want {
|
||||
t.Fatalf("got GoString=%q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
68
teams.go
68
teams.go
|
|
@ -2,17 +2,13 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"iter"
|
||||
|
||||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
// TeamService handles team operations.
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
||||
// _, err := f.Teams.ListMembers(ctx, 42)
|
||||
type TeamService struct {
|
||||
Resource[types.Team, types.CreateTeamOption, types.EditTeamOption]
|
||||
}
|
||||
|
|
@ -25,104 +21,62 @@ func newTeamService(c *Client) *TeamService {
|
|||
}
|
||||
}
|
||||
|
||||
// CreateOrgTeam creates a team within an organisation.
|
||||
func (s *TeamService) CreateOrgTeam(ctx context.Context, org string, opts *types.CreateTeamOption) (*types.Team, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org))
|
||||
var out types.Team
|
||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ListMembers returns all members of a team.
|
||||
func (s *TeamService) ListMembers(ctx context.Context, teamID int64) ([]types.User, error) {
|
||||
path := ResolvePath("/api/v1/teams/{id}/members", pathParams("id", int64String(teamID)))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/members", teamID)
|
||||
return ListAll[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterMembers returns an iterator over all members of a team.
|
||||
func (s *TeamService) IterMembers(ctx context.Context, teamID int64) iter.Seq2[types.User, error] {
|
||||
path := ResolvePath("/api/v1/teams/{id}/members", pathParams("id", int64String(teamID)))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/members", teamID)
|
||||
return ListIter[types.User](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// AddMember adds a user to a team.
|
||||
func (s *TeamService) AddMember(ctx context.Context, teamID int64, username string) error {
|
||||
path := ResolvePath("/api/v1/teams/{id}/members/{username}", pathParams("id", int64String(teamID), "username", username))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/members/%s", teamID, username)
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// GetMember returns a particular member of a team.
|
||||
func (s *TeamService) GetMember(ctx context.Context, teamID int64, username string) (*types.User, error) {
|
||||
path := ResolvePath("/api/v1/teams/{id}/members/{username}", pathParams("id", int64String(teamID), "username", username))
|
||||
var out types.User
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// RemoveMember removes a user from a team.
|
||||
func (s *TeamService) RemoveMember(ctx context.Context, teamID int64, username string) error {
|
||||
path := ResolvePath("/api/v1/teams/{id}/members/{username}", pathParams("id", int64String(teamID), "username", username))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/members/%s", teamID, username)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// ListRepos returns all repositories managed by a team.
|
||||
func (s *TeamService) ListRepos(ctx context.Context, teamID int64) ([]types.Repository, error) {
|
||||
path := ResolvePath("/api/v1/teams/{id}/repos", pathParams("id", int64String(teamID)))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/repos", teamID)
|
||||
return ListAll[types.Repository](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterRepos returns an iterator over all repositories managed by a team.
|
||||
func (s *TeamService) IterRepos(ctx context.Context, teamID int64) iter.Seq2[types.Repository, error] {
|
||||
path := ResolvePath("/api/v1/teams/{id}/repos", pathParams("id", int64String(teamID)))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/repos", teamID)
|
||||
return ListIter[types.Repository](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// AddRepo adds a repository to a team.
|
||||
func (s *TeamService) AddRepo(ctx context.Context, teamID int64, org, repo string) error {
|
||||
path := ResolvePath("/api/v1/teams/{id}/repos/{org}/{repo}", pathParams("id", int64String(teamID), "org", org, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, repo)
|
||||
return s.client.Put(ctx, path, nil, nil)
|
||||
}
|
||||
|
||||
// RemoveRepo removes a repository from a team.
|
||||
func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error {
|
||||
path := ResolvePath("/api/v1/teams/{id}/repos/{org}/{repo}", pathParams("id", int64String(teamID), "org", org, "repo", repo))
|
||||
path := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, repo)
|
||||
return s.client.Delete(ctx, path)
|
||||
}
|
||||
|
||||
// GetRepo returns a particular repository managed by a team.
|
||||
func (s *TeamService) GetRepo(ctx context.Context, teamID int64, org, repo string) (*types.Repository, error) {
|
||||
path := ResolvePath("/api/v1/teams/{id}/repos/{org}/{repo}", pathParams("id", int64String(teamID), "org", org, "repo", repo))
|
||||
var out types.Repository
|
||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// ListOrgTeams returns all teams in an organisation.
|
||||
func (s *TeamService) ListOrgTeams(ctx context.Context, org string) ([]types.Team, error) {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/teams", org)
|
||||
return ListAll[types.Team](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterOrgTeams returns an iterator over all teams in an organisation.
|
||||
func (s *TeamService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[types.Team, error] {
|
||||
path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org))
|
||||
path := fmt.Sprintf("/api/v1/orgs/%s/teams", org)
|
||||
return ListIter[types.Team](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// ListActivityFeeds returns a team's activity feed entries.
|
||||
func (s *TeamService) ListActivityFeeds(ctx context.Context, teamID int64) ([]types.Activity, error) {
|
||||
path := ResolvePath("/api/v1/teams/{id}/activities/feeds", pathParams("id", int64String(teamID)))
|
||||
return ListAll[types.Activity](ctx, s.client, path, nil)
|
||||
}
|
||||
|
||||
// IterActivityFeeds returns an iterator over a team's activity feed entries.
|
||||
func (s *TeamService) IterActivityFeeds(ctx context.Context, teamID int64) iter.Seq2[types.Activity, error] {
|
||||
path := ResolvePath("/api/v1/teams/{id}/activities/feeds", pathParams("id", int64String(teamID)))
|
||||
return ListIter[types.Activity](ctx, s.client, path, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ package forge
|
|||
|
||||
import (
|
||||
"context"
|
||||
json "github.com/goccy/go-json"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
|
@ -10,7 +10,7 @@ import (
|
|||
"dappco.re/go/core/forge/types"
|
||||
)
|
||||
|
||||
func TestTeamService_Get_Good(t *testing.T) {
|
||||
func TestTeamService_Good_Get(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -32,38 +32,7 @@ func TestTeamService_Get_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTeamService_CreateOrgTeam_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
t.Errorf("expected POST, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/orgs/core/teams" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
var opts types.CreateTeamOption
|
||||
if err := json.NewDecoder(r.Body).Decode(&opts); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if opts.Name != "platform" {
|
||||
t.Errorf("got name=%q, want %q", opts.Name, "platform")
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.Team{ID: 7, Name: opts.Name})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
team, err := f.Teams.CreateOrgTeam(context.Background(), "core", &types.CreateTeamOption{
|
||||
Name: "platform",
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if team.ID != 7 || team.Name != "platform" {
|
||||
t.Fatalf("got %#v", team)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeamService_ListMembers_Good(t *testing.T) {
|
||||
func TestTeamService_Good_ListMembers(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
|
|
@ -92,7 +61,7 @@ func TestTeamService_ListMembers_Good(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestTeamService_AddMember_Good(t *testing.T) {
|
||||
func TestTeamService_Good_AddMember(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPut {
|
||||
t.Errorf("expected PUT, got %s", r.Method)
|
||||
|
|
@ -110,25 +79,3 @@ func TestTeamService_AddMember_Good(t *testing.T) {
|
|||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTeamService_GetMember_Good(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodGet {
|
||||
t.Errorf("expected GET, got %s", r.Method)
|
||||
}
|
||||
if r.URL.Path != "/api/v1/teams/42/members/alice" {
|
||||
t.Errorf("wrong path: %s", r.URL.Path)
|
||||
}
|
||||
json.NewEncoder(w).Encode(types.User{ID: 1, UserName: "alice"})
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
f := NewForge(srv.URL, "tok")
|
||||
member, err := f.Teams.GetMember(context.Background(), 42, "alice")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if member.UserName != "alice" {
|
||||
t.Errorf("got username=%q, want %q", member.UserName, "alice")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,84 +4,58 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// ActionTask — ActionTask represents a ActionTask
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ActionTask{DisplayTitle: "example"}
|
||||
type ActionTask struct {
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
DisplayTitle string `json:"display_title,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
HeadBranch string `json:"head_branch,omitempty"`
|
||||
HeadSHA string `json:"head_sha,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RunNumber int64 `json:"run_number,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
DisplayTitle string `json:"display_title,omitempty"`
|
||||
Event string `json:"event,omitempty"`
|
||||
HeadBranch string `json:"head_branch,omitempty"`
|
||||
HeadSHA string `json:"head_sha,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RunNumber int64 `json:"run_number,omitempty"`
|
||||
RunStartedAt time.Time `json:"run_started_at,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
WorkflowID string `json:"workflow_id,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
WorkflowID string `json:"workflow_id,omitempty"`
|
||||
}
|
||||
|
||||
// ActionTaskResponse — ActionTaskResponse returns a ActionTask
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ActionTaskResponse{TotalCount: 1}
|
||||
type ActionTaskResponse struct {
|
||||
Entries []*ActionTask `json:"workflow_runs,omitempty"`
|
||||
TotalCount int64 `json:"total_count,omitempty"`
|
||||
Entries []*ActionTask `json:"workflow_runs,omitempty"`
|
||||
TotalCount int64 `json:"total_count,omitempty"`
|
||||
}
|
||||
|
||||
// ActionVariable — ActionVariable return value of the query API
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ActionVariable{Name: "example"}
|
||||
type ActionVariable struct {
|
||||
Data string `json:"data,omitempty"` // the value of the variable
|
||||
Name string `json:"name,omitempty"` // the name of the variable
|
||||
OwnerID int64 `json:"owner_id,omitempty"` // the owner to which the variable belongs
|
||||
RepoID int64 `json:"repo_id,omitempty"` // the repository to which the variable belongs
|
||||
Data string `json:"data,omitempty"` // the value of the variable
|
||||
Name string `json:"name,omitempty"` // the name of the variable
|
||||
OwnerID int64 `json:"owner_id,omitempty"` // the owner to which the variable belongs
|
||||
RepoID int64 `json:"repo_id,omitempty"` // the repository to which the variable belongs
|
||||
}
|
||||
|
||||
// CreateVariableOption — CreateVariableOption the option when creating variable
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateVariableOption{Value: "example"}
|
||||
type CreateVariableOption struct {
|
||||
Value string `json:"value"` // Value of the variable to create
|
||||
}
|
||||
|
||||
// DispatchWorkflowOption — DispatchWorkflowOption options when dispatching a workflow
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := DispatchWorkflowOption{Ref: "main"}
|
||||
type DispatchWorkflowOption struct {
|
||||
Inputs map[string]string `json:"inputs,omitempty"` // Input keys and values configured in the workflow file.
|
||||
Ref string `json:"ref"` // Git reference for the workflow
|
||||
Inputs map[string]any `json:"inputs,omitempty"` // Input keys and values configured in the workflow file.
|
||||
Ref string `json:"ref"` // Git reference for the workflow
|
||||
}
|
||||
|
||||
// Secret — Secret represents a secret
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Secret{Name: "example"}
|
||||
type Secret struct {
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Name string `json:"name,omitempty"` // the secret's name
|
||||
Name string `json:"name,omitempty"` // the secret's name
|
||||
}
|
||||
|
||||
// UpdateVariableOption — UpdateVariableOption the option when updating variable
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := UpdateVariableOption{Value: "example"}
|
||||
type UpdateVariableOption struct {
|
||||
Name string `json:"name,omitempty"` // New name for the variable. If the field is empty, the variable name won't be updated.
|
||||
Value string `json:"value"` // Value of the variable to update
|
||||
Name string `json:"name,omitempty"` // New name for the variable. If the field is empty, the variable name won't be updated.
|
||||
Value string `json:"value"` // Value of the variable to update
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,30 +4,25 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := Activity{RefName: "main"}
|
||||
|
||||
type Activity struct {
|
||||
ActUser *User `json:"act_user,omitempty"`
|
||||
ActUserID int64 `json:"act_user_id,omitempty"`
|
||||
Comment *Comment `json:"comment,omitempty"`
|
||||
CommentID int64 `json:"comment_id,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IsPrivate bool `json:"is_private,omitempty"`
|
||||
OpType string `json:"op_type,omitempty"` // the type of action
|
||||
RefName string `json:"ref_name,omitempty"`
|
||||
Repo *Repository `json:"repo,omitempty"`
|
||||
RepoID int64 `json:"repo_id,omitempty"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
ActUser *User `json:"act_user,omitempty"`
|
||||
ActUserID int64 `json:"act_user_id,omitempty"`
|
||||
Comment *Comment `json:"comment,omitempty"`
|
||||
CommentID int64 `json:"comment_id,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IsPrivate bool `json:"is_private,omitempty"`
|
||||
OpType string `json:"op_type,omitempty"` // the type of action
|
||||
RefName string `json:"ref_name,omitempty"`
|
||||
Repo *Repository `json:"repo,omitempty"`
|
||||
RepoID int64 `json:"repo_id,omitempty"`
|
||||
UserID int64 `json:"user_id,omitempty"`
|
||||
}
|
||||
|
||||
// ActivityPub — ActivityPub type
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ActivityPub{Context: "example"}
|
||||
type ActivityPub struct {
|
||||
Context string `json:"@context,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,24 +4,18 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// Cron — Cron represents a Cron task
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Cron{Name: "example"}
|
||||
type Cron struct {
|
||||
ExecTimes int64 `json:"exec_times,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Next time.Time `json:"next,omitempty"`
|
||||
Prev time.Time `json:"prev,omitempty"`
|
||||
Schedule string `json:"schedule,omitempty"`
|
||||
ExecTimes int64 `json:"exec_times,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Next time.Time `json:"next,omitempty"`
|
||||
Prev time.Time `json:"prev,omitempty"`
|
||||
Schedule string `json:"schedule,omitempty"`
|
||||
}
|
||||
|
||||
// RenameUserOption — RenameUserOption options when renaming a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := RenameUserOption{NewName: "example"}
|
||||
type RenameUserOption struct {
|
||||
NewName string `json:"new_username"` // New username for this user. This name cannot be in use yet by any other user.
|
||||
}
|
||||
|
||||
|
|
|
|||
198
types/branch.go
198
types/branch.go
|
|
@ -4,138 +4,116 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// Branch — Branch represents a repository branch
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Branch{EffectiveBranchProtectionName: "main"}
|
||||
type Branch struct {
|
||||
Commit *PayloadCommit `json:"commit,omitempty"`
|
||||
EffectiveBranchProtectionName string `json:"effective_branch_protection_name,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Protected bool `json:"protected,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UserCanMerge bool `json:"user_can_merge,omitempty"`
|
||||
UserCanPush bool `json:"user_can_push,omitempty"`
|
||||
Commit *PayloadCommit `json:"commit,omitempty"`
|
||||
EffectiveBranchProtectionName string `json:"effective_branch_protection_name,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Protected bool `json:"protected,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UserCanMerge bool `json:"user_can_merge,omitempty"`
|
||||
UserCanPush bool `json:"user_can_push,omitempty"`
|
||||
}
|
||||
|
||||
// BranchProtection — BranchProtection represents a branch protection for a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := BranchProtection{BranchName: "main"}
|
||||
type BranchProtection struct {
|
||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||
EnablePush bool `json:"enable_push,omitempty"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
RuleName string `json:"rule_name,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||
EnablePush bool `json:"enable_push,omitempty"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
RuleName string `json:"rule_name,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// CreateBranchProtectionOption — CreateBranchProtectionOption options for creating a branch protection
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateBranchProtectionOption{BranchName: "main"}
|
||||
type CreateBranchProtectionOption struct {
|
||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||
EnablePush bool `json:"enable_push,omitempty"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
RuleName string `json:"rule_name,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||
EnablePush bool `json:"enable_push,omitempty"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
RuleName string `json:"rule_name,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||
}
|
||||
|
||||
// CreateBranchRepoOption — CreateBranchRepoOption options when creating a branch in a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateBranchRepoOption{BranchName: "main"}
|
||||
type CreateBranchRepoOption struct {
|
||||
BranchName string `json:"new_branch_name"` // Name of the branch to create
|
||||
BranchName string `json:"new_branch_name"` // Name of the branch to create
|
||||
OldBranchName string `json:"old_branch_name,omitempty"` // Deprecated: true Name of the old branch to create from
|
||||
OldRefName string `json:"old_ref_name,omitempty"` // Name of the old branch/tag/commit to create from
|
||||
OldRefName string `json:"old_ref_name,omitempty"` // Name of the old branch/tag/commit to create from
|
||||
}
|
||||
|
||||
// EditBranchProtectionOption — EditBranchProtectionOption options for editing a branch protection
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditBranchProtectionOption{ApprovalsWhitelistTeams: []string{"example"}}
|
||||
type EditBranchProtectionOption struct {
|
||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||
EnablePush bool `json:"enable_push,omitempty"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||
EnablePush bool `json:"enable_push,omitempty"`
|
||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateBranchRepoOption — UpdateBranchRepoOption options when updating a branch in a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := UpdateBranchRepoOption{Name: "example"}
|
||||
type UpdateBranchRepoOption struct {
|
||||
Name string `json:"name"` // New branch name
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,21 +4,19 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// Comment — Comment represents a comment on a commit or issue
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Comment{Body: "example"}
|
||||
type Comment struct {
|
||||
Attachments []*Attachment `json:"assets,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IssueURL string `json:"issue_url,omitempty"`
|
||||
OriginalAuthor string `json:"original_author,omitempty"`
|
||||
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
||||
PRURL string `json:"pull_request_url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Attachments []*Attachment `json:"assets,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IssueURL string `json:"issue_url,omitempty"`
|
||||
OriginalAuthor string `json:"original_author,omitempty"`
|
||||
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
||||
PRURL string `json:"pull_request_url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,91 +4,65 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := Commit{HTMLURL: "https://example.com"}
|
||||
|
||||
type Commit struct {
|
||||
Author *User `json:"author,omitempty"`
|
||||
Commit *RepoCommit `json:"commit,omitempty"`
|
||||
Committer *User `json:"committer,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Files []*CommitAffectedFiles `json:"files,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
Parents []*CommitMeta `json:"parents,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Stats *CommitStats `json:"stats,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Author *User `json:"author,omitempty"`
|
||||
Commit *RepoCommit `json:"commit,omitempty"`
|
||||
Committer *User `json:"committer,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Files []*CommitAffectedFiles `json:"files,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
Parents []*CommitMeta `json:"parents,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Stats *CommitStats `json:"stats,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// CommitAffectedFiles — CommitAffectedFiles store information about files affected by the commit
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CommitAffectedFiles{Filename: "example"}
|
||||
type CommitAffectedFiles struct {
|
||||
Filename string `json:"filename,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// CommitDateOptions — CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CommitDateOptions{Author: time.Now()}
|
||||
type CommitDateOptions struct {
|
||||
Author time.Time `json:"author,omitempty"`
|
||||
Author time.Time `json:"author,omitempty"`
|
||||
Committer time.Time `json:"committer,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := CommitMeta{SHA: "example"}
|
||||
type CommitMeta struct {
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// CommitStats — CommitStats is statistics for a RepoCommit
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CommitStats{Additions: 1}
|
||||
type CommitStats struct {
|
||||
Additions int64 `json:"additions,omitempty"`
|
||||
Deletions int64 `json:"deletions,omitempty"`
|
||||
Total int64 `json:"total,omitempty"`
|
||||
Total int64 `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
// CommitStatus — CommitStatus holds a single status of a single Commit
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CommitStatus{Description: "example"}
|
||||
type CommitStatus struct {
|
||||
Context string `json:"context,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Creator *User `json:"creator,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Status CommitStatusState `json:"status,omitempty"`
|
||||
TargetURL string `json:"target_url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
Context string `json:"context,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Creator *User `json:"creator,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Status *CommitStatusState `json:"status,omitempty"`
|
||||
TargetURL string `json:"target_url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// CommitStatusState — CommitStatusState holds the state of a CommitStatus It can be "pending", "success", "error" and "failure"
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CommitStatusState("example")
|
||||
type CommitStatusState string
|
||||
// CommitStatusState has no fields in the swagger spec.
|
||||
type CommitStatusState struct{}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := CommitUser{Name: "example"}
|
||||
type CommitUser struct {
|
||||
Date string `json:"date,omitempty"`
|
||||
Date string `json:"date,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,53 +4,35 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// Attachment — Attachment a generic attachment
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Attachment{Name: "example"}
|
||||
type Attachment struct {
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DownloadCount int64 `json:"download_count,omitempty"`
|
||||
DownloadURL string `json:"browser_download_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DownloadCount int64 `json:"download_count,omitempty"`
|
||||
DownloadURL string `json:"browser_download_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
UUID string `json:"uuid,omitempty"`
|
||||
}
|
||||
|
||||
// EditAttachmentOptions — EditAttachmentOptions options for editing attachments
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditAttachmentOptions{Name: "example"}
|
||||
type EditAttachmentOptions struct {
|
||||
DownloadURL string `json:"browser_download_url,omitempty"` // (Can only be set if existing attachment is of external type)
|
||||
Name string `json:"name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Permission — Permission represents a set of permissions
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Permission{Admin: true}
|
||||
type Permission struct {
|
||||
Admin bool `json:"admin,omitempty"`
|
||||
Pull bool `json:"pull,omitempty"`
|
||||
Push bool `json:"push,omitempty"`
|
||||
Pull bool `json:"pull,omitempty"`
|
||||
Push bool `json:"push,omitempty"`
|
||||
}
|
||||
|
||||
// StateType — StateType issue state type
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := StateType("example")
|
||||
// StateType is the state of an issue or PR: "open", "closed".
|
||||
type StateType string
|
||||
|
||||
// TimeStamp — TimeStamp defines a timestamp
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := TimeStamp(1)
|
||||
type TimeStamp int64
|
||||
// TimeStamp is a Forgejo timestamp string.
|
||||
type TimeStamp string
|
||||
|
||||
|
|
|
|||
153
types/content.go
153
types/content.go
|
|
@ -4,134 +4,101 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// ContentsResponse — ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ContentsResponse{Name: "example"}
|
||||
type ContentsResponse struct {
|
||||
Content string `json:"content,omitempty"` // `content` is populated when `type` is `file`, otherwise null
|
||||
DownloadURL string `json:"download_url,omitempty"`
|
||||
Encoding string `json:"encoding,omitempty"` // `encoding` is populated when `type` is `file`, otherwise null
|
||||
GitURL string `json:"git_url,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
LastCommitSHA string `json:"last_commit_sha,omitempty"`
|
||||
Links *FileLinksResponse `json:"_links,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
SubmoduleGitURL string `json:"submodule_git_url,omitempty"` // `submodule_git_url` is populated when `type` is `submodule`, otherwise null
|
||||
Target string `json:"target,omitempty"` // `target` is populated when `type` is `symlink`, otherwise null
|
||||
Type string `json:"type,omitempty"` // `type` will be `file`, `dir`, `symlink`, or `submodule`
|
||||
URL string `json:"url,omitempty"`
|
||||
Content string `json:"content,omitempty"` // `content` is populated when `type` is `file`, otherwise null
|
||||
DownloadURL string `json:"download_url,omitempty"`
|
||||
Encoding string `json:"encoding,omitempty"` // `encoding` is populated when `type` is `file`, otherwise null
|
||||
GitURL string `json:"git_url,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
LastCommitSHA string `json:"last_commit_sha,omitempty"`
|
||||
Links *FileLinksResponse `json:"_links,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
SubmoduleGitURL string `json:"submodule_git_url,omitempty"` // `submodule_git_url` is populated when `type` is `submodule`, otherwise null
|
||||
Target string `json:"target,omitempty"` // `target` is populated when `type` is `symlink`, otherwise null
|
||||
Type string `json:"type,omitempty"` // `type` will be `file`, `dir`, `symlink`, or `submodule`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// CreateFileOptions — CreateFileOptions options for creating files Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateFileOptions{ContentBase64: "example"}
|
||||
type CreateFileOptions struct {
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
ContentBase64 string `json:"content"` // content must be base64 encoded
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
ContentBase64 string `json:"content"` // content must be base64 encoded
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
}
|
||||
|
||||
// DeleteFileOptions — DeleteFileOptions options for deleting files (used for other File structs below) Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := DeleteFileOptions{SHA: "example"}
|
||||
type DeleteFileOptions struct {
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
SHA string `json:"sha"` // sha is the SHA for the file that already exists
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
SHA string `json:"sha"` // sha is the SHA for the file that already exists
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := FileCommitResponse{HTMLURL: "https://example.com"}
|
||||
type FileCommitResponse struct {
|
||||
Author *CommitUser `json:"author,omitempty"`
|
||||
Committer *CommitUser `json:"committer,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Parents []*CommitMeta `json:"parents,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Tree *CommitMeta `json:"tree,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Author *CommitUser `json:"author,omitempty"`
|
||||
Committer *CommitUser `json:"committer,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Parents []*CommitMeta `json:"parents,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Tree *CommitMeta `json:"tree,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// FileDeleteResponse — FileDeleteResponse contains information about a repo's file that was deleted
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := FileDeleteResponse{Commit: &FileCommitResponse{}}
|
||||
type FileDeleteResponse struct {
|
||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||
Content any `json:"content,omitempty"`
|
||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||
Content any `json:"content,omitempty"`
|
||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
// FileLinksResponse — FileLinksResponse contains the links for a repo's file
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := FileLinksResponse{GitURL: "https://example.com"}
|
||||
type FileLinksResponse struct {
|
||||
GitURL string `json:"git,omitempty"`
|
||||
GitURL string `json:"git,omitempty"`
|
||||
HTMLURL string `json:"html,omitempty"`
|
||||
Self string `json:"self,omitempty"`
|
||||
Self string `json:"self,omitempty"`
|
||||
}
|
||||
|
||||
// FileResponse — FileResponse contains information about a repo's file
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := FileResponse{Commit: &FileCommitResponse{}}
|
||||
type FileResponse struct {
|
||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||
Content *ContentsResponse `json:"content,omitempty"`
|
||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||
Content *ContentsResponse `json:"content,omitempty"`
|
||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
// FilesResponse — FilesResponse contains information about multiple files from a repo
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := FilesResponse{Files: {}}
|
||||
type FilesResponse struct {
|
||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||
Files []*ContentsResponse `json:"files,omitempty"`
|
||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||
Files []*ContentsResponse `json:"files,omitempty"`
|
||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
// UpdateFileOptions — UpdateFileOptions options for updating files Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := UpdateFileOptions{ContentBase64: "example"}
|
||||
type UpdateFileOptions struct {
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
ContentBase64 string `json:"content"` // content must be base64 encoded
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
FromPath string `json:"from_path,omitempty"` // from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
SHA string `json:"sha"` // sha is the SHA for the file that already exists
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
ContentBase64 string `json:"content"` // content must be base64 encoded
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
FromPath string `json:"from_path,omitempty"` // from_path (optional) is the path of the original file which will be moved/renamed to the path in the URL
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
SHA string `json:"sha"` // sha is the SHA for the file that already exists
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,61 +2,41 @@
|
|||
|
||||
package types
|
||||
|
||||
|
||||
// APIError — APIError is an api error with a message
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := APIError{Message: "example"}
|
||||
type APIError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := APIForbiddenError{Message: "example"}
|
||||
type APIForbiddenError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := APIInvalidTopicsError{InvalidTopics: []string{"example"}}
|
||||
type APIInvalidTopicsError struct {
|
||||
InvalidTopics []string `json:"invalidTopics,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := APINotFound{Errors: []string{"example"}}
|
||||
type APINotFound struct {
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Errors []string `json:"errors,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := APIRepoArchivedError{Message: "example"}
|
||||
type APIRepoArchivedError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := APIUnauthorizedError{Message: "example"}
|
||||
type APIUnauthorizedError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := APIValidationError{Message: "example"}
|
||||
type APIValidationError struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,61 +2,43 @@
|
|||
|
||||
package types
|
||||
|
||||
|
||||
// NodeInfo — NodeInfo contains standardized way of exposing metadata about a server running one of the distributed social networks
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NodeInfo{Protocols: []string{"example"}}
|
||||
type NodeInfo struct {
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
OpenRegistrations bool `json:"openRegistrations,omitempty"`
|
||||
Protocols []string `json:"protocols,omitempty"`
|
||||
Services *NodeInfoServices `json:"services,omitempty"`
|
||||
Software *NodeInfoSoftware `json:"software,omitempty"`
|
||||
Usage *NodeInfoUsage `json:"usage,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Metadata map[string]any `json:"metadata,omitempty"`
|
||||
OpenRegistrations bool `json:"openRegistrations,omitempty"`
|
||||
Protocols []string `json:"protocols,omitempty"`
|
||||
Services *NodeInfoServices `json:"services,omitempty"`
|
||||
Software *NodeInfoSoftware `json:"software,omitempty"`
|
||||
Usage *NodeInfoUsage `json:"usage,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// NodeInfoServices — NodeInfoServices contains the third party sites this server can connect to via their application API
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NodeInfoServices{Inbound: []string{"example"}}
|
||||
type NodeInfoServices struct {
|
||||
Inbound []string `json:"inbound,omitempty"`
|
||||
Inbound []string `json:"inbound,omitempty"`
|
||||
Outbound []string `json:"outbound,omitempty"`
|
||||
}
|
||||
|
||||
// NodeInfoSoftware — NodeInfoSoftware contains Metadata about server software in use
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NodeInfoSoftware{Name: "example"}
|
||||
type NodeInfoSoftware struct {
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Homepage string `json:"homepage,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Repository string `json:"repository,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// NodeInfoUsage — NodeInfoUsage contains usage statistics for this server
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NodeInfoUsage{LocalComments: 1}
|
||||
type NodeInfoUsage struct {
|
||||
LocalComments int64 `json:"localComments,omitempty"`
|
||||
LocalPosts int64 `json:"localPosts,omitempty"`
|
||||
Users *NodeInfoUsageUsers `json:"users,omitempty"`
|
||||
LocalComments int64 `json:"localComments,omitempty"`
|
||||
LocalPosts int64 `json:"localPosts,omitempty"`
|
||||
Users *NodeInfoUsageUsers `json:"users,omitempty"`
|
||||
}
|
||||
|
||||
// NodeInfoUsageUsers — NodeInfoUsageUsers contains statistics about the users of this server
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NodeInfoUsageUsers{ActiveHalfyear: 1}
|
||||
type NodeInfoUsageUsers struct {
|
||||
ActiveHalfyear int64 `json:"activeHalfyear,omitempty"`
|
||||
ActiveMonth int64 `json:"activeMonth,omitempty"`
|
||||
Total int64 `json:"total,omitempty"`
|
||||
ActiveMonth int64 `json:"activeMonth,omitempty"`
|
||||
Total int64 `json:"total,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
120
types/git.go
120
types/git.go
|
|
@ -2,133 +2,93 @@
|
|||
|
||||
package types
|
||||
|
||||
|
||||
// AnnotatedTag — AnnotatedTag represents an annotated tag
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := AnnotatedTag{Message: "example"}
|
||||
type AnnotatedTag struct {
|
||||
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Object *AnnotatedTagObject `json:"object,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Tagger *CommitUser `json:"tagger,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Object *AnnotatedTagObject `json:"object,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Tag string `json:"tag,omitempty"`
|
||||
Tagger *CommitUser `json:"tagger,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
// AnnotatedTagObject — AnnotatedTagObject contains meta information of the tag object
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := AnnotatedTagObject{SHA: "example"}
|
||||
type AnnotatedTagObject struct {
|
||||
SHA string `json:"sha,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// ChangedFile — ChangedFile store information about files affected by the pull request
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ChangedFile{ContentsURL: "https://example.com"}
|
||||
type ChangedFile struct {
|
||||
Additions int64 `json:"additions,omitempty"`
|
||||
Changes int64 `json:"changes,omitempty"`
|
||||
ContentsURL string `json:"contents_url,omitempty"`
|
||||
Deletions int64 `json:"deletions,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
Additions int64 `json:"additions,omitempty"`
|
||||
Changes int64 `json:"changes,omitempty"`
|
||||
ContentsURL string `json:"contents_url,omitempty"`
|
||||
Deletions int64 `json:"deletions,omitempty"`
|
||||
Filename string `json:"filename,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
PreviousFilename string `json:"previous_filename,omitempty"`
|
||||
RawURL string `json:"raw_url,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
RawURL string `json:"raw_url,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
// EditGitHookOption — EditGitHookOption options when modifying one Git hook
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditGitHookOption{Content: "example"}
|
||||
type EditGitHookOption struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
// GitBlobResponse — GitBlobResponse represents a git blob
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GitBlobResponse{Content: "example"}
|
||||
type GitBlobResponse struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Encoding string `json:"encoding,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// GitEntry — GitEntry represents a git tree
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GitEntry{Mode: "example"}
|
||||
type GitEntry struct {
|
||||
Mode string `json:"mode,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// GitHook — GitHook represents a Git repository hook
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GitHook{Name: "example"}
|
||||
type GitHook struct {
|
||||
Content string `json:"content,omitempty"`
|
||||
IsActive bool `json:"is_active,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
IsActive bool `json:"is_active,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := GitObject{SHA: "example"}
|
||||
type GitObject struct {
|
||||
SHA string `json:"sha,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// GitTreeResponse — GitTreeResponse returns a git tree
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GitTreeResponse{SHA: "example"}
|
||||
type GitTreeResponse struct {
|
||||
Entries []*GitEntry `json:"tree,omitempty"`
|
||||
Page int64 `json:"page,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
TotalCount int64 `json:"total_count,omitempty"`
|
||||
Truncated bool `json:"truncated,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Entries []*GitEntry `json:"tree,omitempty"`
|
||||
Page int64 `json:"page,omitempty"`
|
||||
SHA string `json:"sha,omitempty"`
|
||||
TotalCount int64 `json:"total_count,omitempty"`
|
||||
Truncated bool `json:"truncated,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Note — Note contains information related to a git note
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Note{Message: "example"}
|
||||
type Note struct {
|
||||
Commit *Commit `json:"commit,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Commit *Commit `json:"commit,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := NoteOptions{Message: "example"}
|
||||
type NoteOptions struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
103
types/hook.go
103
types/hook.go
|
|
@ -4,87 +4,66 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// CreateHookOption — CreateHookOption options when create a hook
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateHookOption{Type: "example"}
|
||||
type CreateHookOption struct {
|
||||
Active bool `json:"active,omitempty"`
|
||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||
BranchFilter string `json:"branch_filter,omitempty"`
|
||||
Config *CreateHookOptionConfig `json:"config"`
|
||||
Events []string `json:"events,omitempty"`
|
||||
Type string `json:"type"`
|
||||
Active bool `json:"active,omitempty"`
|
||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||
BranchFilter string `json:"branch_filter,omitempty"`
|
||||
Config *CreateHookOptionConfig `json:"config"`
|
||||
Events []string `json:"events,omitempty"`
|
||||
Type string `json:"type"`
|
||||
}
|
||||
|
||||
// CreateHookOptionConfig — CreateHookOptionConfig has all config options in it required are "content_type" and "url" Required
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateHookOptionConfig(map[string]any{"key": "value"})
|
||||
type CreateHookOptionConfig map[string]any
|
||||
// CreateHookOptionConfig has no fields in the swagger spec.
|
||||
type CreateHookOptionConfig struct{}
|
||||
|
||||
// EditHookOption — EditHookOption options when modify one hook
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditHookOption{AuthorizationHeader: "example"}
|
||||
type EditHookOption struct {
|
||||
Active bool `json:"active,omitempty"`
|
||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||
BranchFilter string `json:"branch_filter,omitempty"`
|
||||
Config map[string]string `json:"config,omitempty"`
|
||||
Events []string `json:"events,omitempty"`
|
||||
Active bool `json:"active,omitempty"`
|
||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||
BranchFilter string `json:"branch_filter,omitempty"`
|
||||
Config map[string]any `json:"config,omitempty"`
|
||||
Events []string `json:"events,omitempty"`
|
||||
}
|
||||
|
||||
// Hook — Hook a hook is a web hook when one repository changed
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Hook{AuthorizationHeader: "example"}
|
||||
type Hook struct {
|
||||
Active bool `json:"active,omitempty"`
|
||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||
BranchFilter string `json:"branch_filter,omitempty"`
|
||||
Config map[string]string `json:"config,omitempty"` // Deprecated: use Metadata instead
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Events []string `json:"events,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Metadata any `json:"metadata,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
Active bool `json:"active,omitempty"`
|
||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||
BranchFilter string `json:"branch_filter,omitempty"`
|
||||
Config map[string]any `json:"config,omitempty"` // Deprecated: use Metadata instead
|
||||
ContentType string `json:"content_type,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Events []string `json:"events,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Metadata any `json:"metadata,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// PayloadCommit — PayloadCommit represents a commit
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PayloadCommit{Added: []string{"example"}}
|
||||
type PayloadCommit struct {
|
||||
Added []string `json:"added,omitempty"`
|
||||
Author *PayloadUser `json:"author,omitempty"`
|
||||
Committer *PayloadUser `json:"committer,omitempty"`
|
||||
ID string `json:"id,omitempty"` // sha1 hash of the commit
|
||||
Message string `json:"message,omitempty"`
|
||||
Modified []string `json:"modified,omitempty"`
|
||||
Removed []string `json:"removed,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Added []string `json:"added,omitempty"`
|
||||
Author *PayloadUser `json:"author,omitempty"`
|
||||
Committer *PayloadUser `json:"committer,omitempty"`
|
||||
ID string `json:"id,omitempty"` // sha1 hash of the commit
|
||||
Message string `json:"message,omitempty"`
|
||||
Modified []string `json:"modified,omitempty"`
|
||||
Removed []string `json:"removed,omitempty"`
|
||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
// PayloadCommitVerification — PayloadCommitVerification represents the GPG verification of a commit
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PayloadCommitVerification{Payload: "example"}
|
||||
type PayloadCommitVerification struct {
|
||||
Payload string `json:"payload,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Signature string `json:"signature,omitempty"`
|
||||
Signer *PayloadUser `json:"signer,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
Payload string `json:"payload,omitempty"`
|
||||
Reason string `json:"reason,omitempty"`
|
||||
Signature string `json:"signature,omitempty"`
|
||||
Signer *PayloadUser `json:"signer,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
210
types/issue.go
210
types/issue.go
|
|
@ -4,200 +4,142 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// CreateIssueCommentOption — CreateIssueCommentOption options for creating a comment on an issue
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateIssueCommentOption{Body: "example"}
|
||||
type CreateIssueCommentOption struct {
|
||||
Body string `json:"body"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
Body string `json:"body"`
|
||||
Updated *time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// CreateIssueOption — CreateIssueOption options to create one issue
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateIssueOption{Title: "example"}
|
||||
type CreateIssueOption struct {
|
||||
Assignee string `json:"assignee,omitempty"` // deprecated
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Closed bool `json:"closed,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Labels []int64 `json:"labels,omitempty"` // list of label ids
|
||||
Milestone int64 `json:"milestone,omitempty"` // milestone id
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Title string `json:"title"`
|
||||
Assignee string `json:"assignee,omitempty"` // deprecated
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Closed bool `json:"closed,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Labels []int64 `json:"labels,omitempty"` // list of label ids
|
||||
Milestone int64 `json:"milestone,omitempty"` // milestone id
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Title string `json:"title"`
|
||||
}
|
||||
|
||||
// EditDeadlineOption — EditDeadlineOption options for creating a deadline
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditDeadlineOption{Deadline: time.Now()}
|
||||
type EditDeadlineOption struct {
|
||||
Deadline time.Time `json:"due_date"`
|
||||
}
|
||||
|
||||
// EditIssueCommentOption — EditIssueCommentOption options for editing a comment
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditIssueCommentOption{Body: "example"}
|
||||
type EditIssueCommentOption struct {
|
||||
Body string `json:"body"`
|
||||
Body string `json:"body"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// EditIssueOption — EditIssueOption options for editing an issue
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditIssueOption{Body: "example"}
|
||||
type EditIssueOption struct {
|
||||
Assignee string `json:"assignee,omitempty"` // deprecated
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Milestone int64 `json:"milestone,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
Assignee string `json:"assignee,omitempty"` // deprecated
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Milestone int64 `json:"milestone,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// Issue — Issue represents an issue in a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Issue{Body: "example"}
|
||||
type Issue struct {
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
Assignees []*User `json:"assignees,omitempty"`
|
||||
Attachments []*Attachment `json:"assets,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Closed time.Time `json:"closed_at,omitempty"`
|
||||
Comments int64 `json:"comments,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Index int64 `json:"number,omitempty"`
|
||||
IsLocked bool `json:"is_locked,omitempty"`
|
||||
Labels []*Label `json:"labels,omitempty"`
|
||||
Milestone *Milestone `json:"milestone,omitempty"`
|
||||
OriginalAuthor string `json:"original_author,omitempty"`
|
||||
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
||||
PinOrder int64 `json:"pin_order,omitempty"`
|
||||
PullRequest *PullRequestMeta `json:"pull_request,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Repository *RepositoryMeta `json:"repository,omitempty"`
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
Assignees []*User `json:"assignees,omitempty"`
|
||||
Attachments []*Attachment `json:"assets,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Closed time.Time `json:"closed_at,omitempty"`
|
||||
Comments int64 `json:"comments,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Index int64 `json:"number,omitempty"`
|
||||
IsLocked bool `json:"is_locked,omitempty"`
|
||||
Labels []*Label `json:"labels,omitempty"`
|
||||
Milestone *Milestone `json:"milestone,omitempty"`
|
||||
OriginalAuthor string `json:"original_author,omitempty"`
|
||||
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
||||
PinOrder int64 `json:"pin_order,omitempty"`
|
||||
PullRequest *PullRequestMeta `json:"pull_request,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Repository *RepositoryMeta `json:"repository,omitempty"`
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueConfig{BlankIssuesEnabled: true}
|
||||
type IssueConfig struct {
|
||||
BlankIssuesEnabled bool `json:"blank_issues_enabled,omitempty"`
|
||||
ContactLinks []*IssueConfigContactLink `json:"contact_links,omitempty"`
|
||||
BlankIssuesEnabled bool `json:"blank_issues_enabled,omitempty"`
|
||||
ContactLinks []*IssueConfigContactLink `json:"contact_links,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueConfigContactLink{Name: "example"}
|
||||
type IssueConfigContactLink struct {
|
||||
About string `json:"about,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueConfigValidation{Message: "example"}
|
||||
type IssueConfigValidation struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Valid bool `json:"valid,omitempty"`
|
||||
Valid bool `json:"valid,omitempty"`
|
||||
}
|
||||
|
||||
// IssueDeadline — IssueDeadline represents an issue deadline
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueDeadline{Deadline: time.Now()}
|
||||
type IssueDeadline struct {
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
}
|
||||
|
||||
// IssueFormField — IssueFormField represents a form field
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueFormField{ID: "example"}
|
||||
type IssueFormField struct {
|
||||
Attributes map[string]any `json:"attributes,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Type IssueFormFieldType `json:"type,omitempty"`
|
||||
Validations map[string]any `json:"validations,omitempty"`
|
||||
Visible []IssueFormFieldVisible `json:"visible,omitempty"`
|
||||
Attributes map[string]any `json:"attributes,omitempty"`
|
||||
ID string `json:"id,omitempty"`
|
||||
Type *IssueFormFieldType `json:"type,omitempty"`
|
||||
Validations map[string]any `json:"validations,omitempty"`
|
||||
Visible []*IssueFormFieldVisible `json:"visible,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueFormFieldType("example")
|
||||
type IssueFormFieldType string
|
||||
// IssueFormFieldType has no fields in the swagger spec.
|
||||
type IssueFormFieldType struct{}
|
||||
|
||||
// IssueFormFieldVisible — IssueFormFieldVisible defines issue form field visible
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueFormFieldVisible("example")
|
||||
type IssueFormFieldVisible string
|
||||
// IssueFormFieldVisible has no fields in the swagger spec.
|
||||
type IssueFormFieldVisible struct{}
|
||||
|
||||
// IssueLabelsOption — IssueLabelsOption a collection of labels
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueLabelsOption{Updated: time.Now()}
|
||||
type IssueLabelsOption struct {
|
||||
Labels []any `json:"labels,omitempty"` // Labels can be a list of integers representing label IDs or a list of strings representing label names
|
||||
Labels []any `json:"labels,omitempty"` // Labels can be a list of integers representing label IDs or a list of strings representing label names
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// IssueMeta — IssueMeta basic issue information
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueMeta{Name: "example"}
|
||||
type IssueMeta struct {
|
||||
Index int64 `json:"index,omitempty"`
|
||||
Name string `json:"repo,omitempty"`
|
||||
Index int64 `json:"index,omitempty"`
|
||||
Name string `json:"repo,omitempty"`
|
||||
Owner string `json:"owner,omitempty"`
|
||||
}
|
||||
|
||||
// IssueTemplate — IssueTemplate represents an issue template for a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueTemplate{FileName: "example"}
|
||||
type IssueTemplate struct {
|
||||
About string `json:"about,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Fields []*IssueFormField `json:"body,omitempty"`
|
||||
FileName string `json:"file_name,omitempty"`
|
||||
Labels IssueTemplateLabels `json:"labels,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
About string `json:"about,omitempty"`
|
||||
Content string `json:"content,omitempty"`
|
||||
Fields []*IssueFormField `json:"body,omitempty"`
|
||||
FileName string `json:"file_name,omitempty"`
|
||||
Labels *IssueTemplateLabels `json:"labels,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := IssueTemplateLabels([]string{"example"})
|
||||
type IssueTemplateLabels []string
|
||||
// IssueTemplateLabels has no fields in the swagger spec.
|
||||
type IssueTemplateLabels struct{}
|
||||
|
||||
|
|
|
|||
100
types/key.go
100
types/key.go
|
|
@ -4,88 +4,66 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// CreateGPGKeyOption — CreateGPGKeyOption options create user GPG key
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateGPGKeyOption{ArmoredKey: "example"}
|
||||
type CreateGPGKeyOption struct {
|
||||
ArmoredKey string `json:"armored_public_key"` // An armored GPG key to add
|
||||
Signature string `json:"armored_signature,omitempty"`
|
||||
Signature string `json:"armored_signature,omitempty"`
|
||||
}
|
||||
|
||||
// CreateKeyOption — CreateKeyOption options when creating a key
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateKeyOption{Title: "example"}
|
||||
type CreateKeyOption struct {
|
||||
Key string `json:"key"` // An armored SSH key to add
|
||||
ReadOnly bool `json:"read_only,omitempty"` // Describe if the key has only read access or read/write
|
||||
Title string `json:"title"` // Title of the key to add
|
||||
Key string `json:"key"` // An armored SSH key to add
|
||||
ReadOnly bool `json:"read_only,omitempty"` // Describe if the key has only read access or read/write
|
||||
Title string `json:"title"` // Title of the key to add
|
||||
}
|
||||
|
||||
// DeployKey — DeployKey a deploy key
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := DeployKey{Title: "example"}
|
||||
type DeployKey struct {
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
KeyID int64 `json:"key_id,omitempty"`
|
||||
ReadOnly bool `json:"read_only,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
KeyID int64 `json:"key_id,omitempty"`
|
||||
ReadOnly bool `json:"read_only,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// GPGKey — GPGKey a user GPG key to sign commit and tag in repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GPGKey{KeyID: "example"}
|
||||
type GPGKey struct {
|
||||
CanCertify bool `json:"can_certify,omitempty"`
|
||||
CanEncryptComms bool `json:"can_encrypt_comms,omitempty"`
|
||||
CanEncryptStorage bool `json:"can_encrypt_storage,omitempty"`
|
||||
CanSign bool `json:"can_sign,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Emails []*GPGKeyEmail `json:"emails,omitempty"`
|
||||
Expires time.Time `json:"expires_at,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
KeyID string `json:"key_id,omitempty"`
|
||||
PrimaryKeyID string `json:"primary_key_id,omitempty"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
SubsKey []*GPGKey `json:"subkeys,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
CanCertify bool `json:"can_certify,omitempty"`
|
||||
CanEncryptComms bool `json:"can_encrypt_comms,omitempty"`
|
||||
CanEncryptStorage bool `json:"can_encrypt_storage,omitempty"`
|
||||
CanSign bool `json:"can_sign,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Emails []*GPGKeyEmail `json:"emails,omitempty"`
|
||||
Expires time.Time `json:"expires_at,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
KeyID string `json:"key_id,omitempty"`
|
||||
PrimaryKeyID string `json:"primary_key_id,omitempty"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
SubsKey []*GPGKey `json:"subkeys,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
}
|
||||
|
||||
// GPGKeyEmail — GPGKeyEmail an email attached to a GPGKey
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GPGKeyEmail{Email: "alice@example.com"}
|
||||
type GPGKeyEmail struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
Verified bool `json:"verified,omitempty"`
|
||||
}
|
||||
|
||||
// PublicKey — PublicKey publickey is a user key to push code to repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PublicKey{Title: "example"}
|
||||
type PublicKey struct {
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
KeyType string `json:"key_type,omitempty"`
|
||||
ReadOnly bool `json:"read_only,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Fingerprint string `json:"fingerprint,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
KeyType string `json:"key_type,omitempty"`
|
||||
ReadOnly bool `json:"read_only,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,64 +4,46 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// CreateLabelOption — CreateLabelOption options for creating a label
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateLabelOption{Name: "example"}
|
||||
type CreateLabelOption struct {
|
||||
Color string `json:"color"`
|
||||
Color string `json:"color"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
// DeleteLabelsOption — DeleteLabelOption options for deleting a label
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := DeleteLabelsOption{Updated: time.Now()}
|
||||
type DeleteLabelsOption struct {
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
// EditLabelOption — EditLabelOption options for editing a label
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditLabelOption{Description: "example"}
|
||||
type EditLabelOption struct {
|
||||
Color string `json:"color,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Label — Label a label to an issue or a pr
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Label{Description: "example"}
|
||||
type Label struct {
|
||||
Color string `json:"color,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IsArchived bool `json:"is_archived,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// LabelTemplate — LabelTemplate info of a Label template
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := LabelTemplate{Description: "example"}
|
||||
type LabelTemplate struct {
|
||||
Color string `json:"color,omitempty"`
|
||||
Color string `json:"color,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Exclusive bool `json:"exclusive,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,44 +4,34 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// CreateMilestoneOption — CreateMilestoneOption options for creating a milestone
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateMilestoneOption{Description: "example"}
|
||||
type CreateMilestoneOption struct {
|
||||
Deadline time.Time `json:"due_on,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Deadline time.Time `json:"due_on,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// EditMilestoneOption — EditMilestoneOption options for editing a milestone
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditMilestoneOption{Description: "example"}
|
||||
type EditMilestoneOption struct {
|
||||
Deadline time.Time `json:"due_on,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Deadline time.Time `json:"due_on,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// Milestone — Milestone milestone is a collection of issues on one repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Milestone{Description: "example"}
|
||||
type Milestone struct {
|
||||
Closed time.Time `json:"closed_at,omitempty"`
|
||||
ClosedIssues int64 `json:"closed_issues,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Deadline time.Time `json:"due_on,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
OpenIssues int64 `json:"open_issues,omitempty"`
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
Closed time.Time `json:"closed_at,omitempty"`
|
||||
ClosedIssues int64 `json:"closed_issues,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Deadline time.Time `json:"due_on,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
OpenIssues int64 `json:"open_issues,omitempty"`
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
356
types/misc.go
356
types/misc.go
|
|
@ -4,360 +4,252 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// AddCollaboratorOption — AddCollaboratorOption options when adding a user as a collaborator of a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := AddCollaboratorOption{Permission: "example"}
|
||||
type AddCollaboratorOption struct {
|
||||
Permission string `json:"permission,omitempty"`
|
||||
}
|
||||
|
||||
// AddTimeOption — AddTimeOption options for adding time to an issue
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := AddTimeOption{Time: 1}
|
||||
type AddTimeOption struct {
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
Time int64 `json:"time"` // time in seconds
|
||||
User string `json:"user_name,omitempty"` // User who spent the time (optional)
|
||||
Time int64 `json:"time"` // time in seconds
|
||||
User string `json:"user_name,omitempty"` // User who spent the time (optional)
|
||||
}
|
||||
|
||||
// ChangeFileOperation — ChangeFileOperation for creating, updating or deleting a file
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ChangeFileOperation{Operation: "example"}
|
||||
type ChangeFileOperation struct {
|
||||
ContentBase64 string `json:"content,omitempty"` // new or updated file content, must be base64 encoded
|
||||
FromPath string `json:"from_path,omitempty"` // old path of the file to move
|
||||
Operation string `json:"operation"` // indicates what to do with the file
|
||||
Path string `json:"path"` // path to the existing or new file
|
||||
SHA string `json:"sha,omitempty"` // sha is the SHA for the file that already exists, required for update or delete
|
||||
ContentBase64 string `json:"content,omitempty"` // new or updated file content, must be base64 encoded
|
||||
FromPath string `json:"from_path,omitempty"` // old path of the file to move
|
||||
Operation string `json:"operation"` // indicates what to do with the file
|
||||
Path string `json:"path"` // path to the existing or new file
|
||||
SHA string `json:"sha,omitempty"` // sha is the SHA for the file that already exists, required for update or delete
|
||||
}
|
||||
|
||||
// ChangeFilesOptions — ChangeFilesOptions options for creating, updating or deleting multiple files Note: `author` and `committer` are optional (if only one is given, it will be used for the other, otherwise the authenticated user will be used)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ChangeFilesOptions{Files: {}}
|
||||
type ChangeFilesOptions struct {
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
Files []*ChangeFileOperation `json:"files"` // list of file operations
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
Author *Identity `json:"author,omitempty"`
|
||||
BranchName string `json:"branch,omitempty"` // branch (optional) to base this file from. if not given, the default branch is used
|
||||
Committer *Identity `json:"committer,omitempty"`
|
||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||
Files []*ChangeFileOperation `json:"files"` // list of file operations
|
||||
Message string `json:"message,omitempty"` // message (optional) for the commit of this file. if not supplied, a default message will be used
|
||||
NewBranchName string `json:"new_branch,omitempty"` // new_branch (optional) will make a new branch from `branch` before creating the file
|
||||
Signoff bool `json:"signoff,omitempty"` // Add a Signed-off-by trailer by the committer at the end of the commit log message.
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := Compare{TotalCommits: 1}
|
||||
type Compare struct {
|
||||
Commits []*Commit `json:"commits,omitempty"`
|
||||
TotalCommits int64 `json:"total_commits,omitempty"`
|
||||
Commits []*Commit `json:"commits,omitempty"`
|
||||
TotalCommits int64 `json:"total_commits,omitempty"`
|
||||
}
|
||||
|
||||
// CreateForkOption — CreateForkOption options for creating a fork
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateForkOption{Name: "example"}
|
||||
type CreateForkOption struct {
|
||||
Name string `json:"name,omitempty"` // name of the forked repository
|
||||
Name string `json:"name,omitempty"` // name of the forked repository
|
||||
Organization string `json:"organization,omitempty"` // organization name, if forking into an organization
|
||||
}
|
||||
|
||||
// CreateOrUpdateSecretOption — CreateOrUpdateSecretOption options when creating or updating secret
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateOrUpdateSecretOption{Data: "example"}
|
||||
type CreateOrUpdateSecretOption struct {
|
||||
Data string `json:"data"` // Data of the secret to update
|
||||
}
|
||||
|
||||
// DismissPullReviewOptions — DismissPullReviewOptions are options to dismiss a pull review
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := DismissPullReviewOptions{Message: "example"}
|
||||
type DismissPullReviewOptions struct {
|
||||
Message string `json:"message,omitempty"`
|
||||
Priors bool `json:"priors,omitempty"`
|
||||
Priors bool `json:"priors,omitempty"`
|
||||
}
|
||||
|
||||
// ForgeLike — ForgeLike activity data type
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ForgeLike{}
|
||||
//
|
||||
// ForgeLike has no fields in the swagger spec.
|
||||
type ForgeLike struct{}
|
||||
|
||||
// GenerateRepoOption — GenerateRepoOption options when creating repository using a template
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GenerateRepoOption{Name: "example"}
|
||||
type GenerateRepoOption struct {
|
||||
Avatar bool `json:"avatar,omitempty"` // include avatar of the template repo
|
||||
DefaultBranch string `json:"default_branch,omitempty"` // Default branch of the new repository
|
||||
Description string `json:"description,omitempty"` // Description of the repository to create
|
||||
GitContent bool `json:"git_content,omitempty"` // include git content of default branch in template repo
|
||||
GitHooks bool `json:"git_hooks,omitempty"` // include git hooks in template repo
|
||||
Labels bool `json:"labels,omitempty"` // include labels in template repo
|
||||
Name string `json:"name"` // Name of the repository to create
|
||||
Owner string `json:"owner"` // The organization or person who will own the new repository
|
||||
Private bool `json:"private,omitempty"` // Whether the repository is private
|
||||
ProtectedBranch bool `json:"protected_branch,omitempty"` // include protected branches in template repo
|
||||
Topics bool `json:"topics,omitempty"` // include topics in template repo
|
||||
Webhooks bool `json:"webhooks,omitempty"` // include webhooks in template repo
|
||||
Avatar bool `json:"avatar,omitempty"` // include avatar of the template repo
|
||||
DefaultBranch string `json:"default_branch,omitempty"` // Default branch of the new repository
|
||||
Description string `json:"description,omitempty"` // Description of the repository to create
|
||||
GitContent bool `json:"git_content,omitempty"` // include git content of default branch in template repo
|
||||
GitHooks bool `json:"git_hooks,omitempty"` // include git hooks in template repo
|
||||
Labels bool `json:"labels,omitempty"` // include labels in template repo
|
||||
Name string `json:"name"` // Name of the repository to create
|
||||
Owner string `json:"owner"` // The organization or person who will own the new repository
|
||||
Private bool `json:"private,omitempty"` // Whether the repository is private
|
||||
ProtectedBranch bool `json:"protected_branch,omitempty"` // include protected branches in template repo
|
||||
Topics bool `json:"topics,omitempty"` // include topics in template repo
|
||||
Webhooks bool `json:"webhooks,omitempty"` // include webhooks in template repo
|
||||
}
|
||||
|
||||
// GitignoreTemplateInfo — GitignoreTemplateInfo name and text of a gitignore template
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GitignoreTemplateInfo{Name: "example"}
|
||||
type GitignoreTemplateInfo struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Source string `json:"source,omitempty"`
|
||||
}
|
||||
|
||||
// Identity — Identity for a person's identity like an author or committer
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Identity{Name: "example"}
|
||||
type Identity struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// LicenseTemplateInfo — LicensesInfo contains information about a License
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := LicenseTemplateInfo{Body: "example"}
|
||||
type LicenseTemplateInfo struct {
|
||||
Body string `json:"body,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Implementation string `json:"implementation,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// LicensesTemplateListEntry — LicensesListEntry is used for the API
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := LicensesTemplateListEntry{Name: "example"}
|
||||
type LicensesTemplateListEntry struct {
|
||||
Key string `json:"key,omitempty"`
|
||||
Key string `json:"key,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// MarkdownOption — MarkdownOption markdown options
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := MarkdownOption{Context: "example"}
|
||||
type MarkdownOption struct {
|
||||
Context string `json:"Context,omitempty"` // Context to render in: body
|
||||
Mode string `json:"Mode,omitempty"` // Mode to render (comment, gfm, markdown) in: body
|
||||
Text string `json:"Text,omitempty"` // Text markdown to render in: body
|
||||
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
||||
Mode string `json:"Mode,omitempty"` // Mode to render (comment, gfm, markdown) in: body
|
||||
Text string `json:"Text,omitempty"` // Text markdown to render in: body
|
||||
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
||||
}
|
||||
|
||||
// MarkupOption — MarkupOption markup options
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := MarkupOption{BranchPath: "main"}
|
||||
type MarkupOption struct {
|
||||
BranchPath string `json:"BranchPath,omitempty"` // The current branch path where the form gets posted in: body
|
||||
Context string `json:"Context,omitempty"` // Context to render in: body
|
||||
FilePath string `json:"FilePath,omitempty"` // File path for detecting extension in file mode in: body
|
||||
Mode string `json:"Mode,omitempty"` // Mode to render (comment, gfm, markdown, file) in: body
|
||||
Text string `json:"Text,omitempty"` // Text markup to render in: body
|
||||
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
||||
Context string `json:"Context,omitempty"` // Context to render in: body
|
||||
FilePath string `json:"FilePath,omitempty"` // File path for detecting extension in file mode in: body
|
||||
Mode string `json:"Mode,omitempty"` // Mode to render (comment, gfm, markdown, file) in: body
|
||||
Text string `json:"Text,omitempty"` // Text markup to render in: body
|
||||
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
||||
}
|
||||
|
||||
// MergePullRequestOption — MergePullRequestForm form for merging Pull Request
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := MergePullRequestOption{Do: "example"}
|
||||
type MergePullRequestOption struct {
|
||||
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge,omitempty"`
|
||||
Do string `json:"Do"`
|
||||
ForceMerge bool `json:"force_merge,omitempty"`
|
||||
HeadCommitID string `json:"head_commit_id,omitempty"`
|
||||
MergeCommitID string `json:"MergeCommitID,omitempty"`
|
||||
MergeMessageField string `json:"MergeMessageField,omitempty"`
|
||||
MergeTitleField string `json:"MergeTitleField,omitempty"`
|
||||
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"`
|
||||
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge,omitempty"`
|
||||
Do string `json:"Do"`
|
||||
ForceMerge bool `json:"force_merge,omitempty"`
|
||||
HeadCommitID string `json:"head_commit_id,omitempty"`
|
||||
MergeCommitID string `json:"MergeCommitID,omitempty"`
|
||||
MergeMessageField string `json:"MergeMessageField,omitempty"`
|
||||
MergeTitleField string `json:"MergeTitleField,omitempty"`
|
||||
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"`
|
||||
}
|
||||
|
||||
// MigrateRepoOptions — MigrateRepoOptions options for migrating repository's this is used to interact with api v1
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := MigrateRepoOptions{RepoName: "example"}
|
||||
type MigrateRepoOptions struct {
|
||||
AuthPassword string `json:"auth_password,omitempty"`
|
||||
AuthToken string `json:"auth_token,omitempty"`
|
||||
AuthUsername string `json:"auth_username,omitempty"`
|
||||
CloneAddr string `json:"clone_addr"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Issues bool `json:"issues,omitempty"`
|
||||
LFS bool `json:"lfs,omitempty"`
|
||||
LFSEndpoint string `json:"lfs_endpoint,omitempty"`
|
||||
Labels bool `json:"labels,omitempty"`
|
||||
Milestones bool `json:"milestones,omitempty"`
|
||||
Mirror bool `json:"mirror,omitempty"`
|
||||
AuthPassword string `json:"auth_password,omitempty"`
|
||||
AuthToken string `json:"auth_token,omitempty"`
|
||||
AuthUsername string `json:"auth_username,omitempty"`
|
||||
CloneAddr string `json:"clone_addr"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Issues bool `json:"issues,omitempty"`
|
||||
LFS bool `json:"lfs,omitempty"`
|
||||
LFSEndpoint string `json:"lfs_endpoint,omitempty"`
|
||||
Labels bool `json:"labels,omitempty"`
|
||||
Milestones bool `json:"milestones,omitempty"`
|
||||
Mirror bool `json:"mirror,omitempty"`
|
||||
MirrorInterval string `json:"mirror_interval,omitempty"`
|
||||
Private bool `json:"private,omitempty"`
|
||||
PullRequests bool `json:"pull_requests,omitempty"`
|
||||
Releases bool `json:"releases,omitempty"`
|
||||
RepoName string `json:"repo_name"`
|
||||
RepoOwner string `json:"repo_owner,omitempty"` // Name of User or Organisation who will own Repo after migration
|
||||
RepoOwnerID int64 `json:"uid,omitempty"` // deprecated (only for backwards compatibility)
|
||||
Service string `json:"service,omitempty"`
|
||||
Wiki bool `json:"wiki,omitempty"`
|
||||
Private bool `json:"private,omitempty"`
|
||||
PullRequests bool `json:"pull_requests,omitempty"`
|
||||
Releases bool `json:"releases,omitempty"`
|
||||
RepoName string `json:"repo_name"`
|
||||
RepoOwner string `json:"repo_owner,omitempty"` // Name of User or Organisation who will own Repo after migration
|
||||
RepoOwnerID int64 `json:"uid,omitempty"` // deprecated (only for backwards compatibility)
|
||||
Service string `json:"service,omitempty"`
|
||||
Wiki bool `json:"wiki,omitempty"`
|
||||
}
|
||||
|
||||
// NewIssuePinsAllowed — NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NewIssuePinsAllowed{Issues: true}
|
||||
type NewIssuePinsAllowed struct {
|
||||
Issues bool `json:"issues,omitempty"`
|
||||
Issues bool `json:"issues,omitempty"`
|
||||
PullRequests bool `json:"pull_requests,omitempty"`
|
||||
}
|
||||
|
||||
// NotifySubjectType — NotifySubjectType represent type of notification subject
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NotifySubjectType("example")
|
||||
type NotifySubjectType string
|
||||
// NotifySubjectType has no fields in the swagger spec.
|
||||
type NotifySubjectType struct{}
|
||||
|
||||
// PRBranchInfo — PRBranchInfo information about a branch
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PRBranchInfo{Name: "example"}
|
||||
type PRBranchInfo struct {
|
||||
Name string `json:"label,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Repo *Repository `json:"repo,omitempty"`
|
||||
RepoID int64 `json:"repo_id,omitempty"`
|
||||
Sha string `json:"sha,omitempty"`
|
||||
Name string `json:"label,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
Repo *Repository `json:"repo,omitempty"`
|
||||
RepoID int64 `json:"repo_id,omitempty"`
|
||||
Sha string `json:"sha,omitempty"`
|
||||
}
|
||||
|
||||
// PayloadUser — PayloadUser represents the author or committer of a commit
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PayloadUser{Name: "example"}
|
||||
type PayloadUser struct {
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"` // Full name of the commit author
|
||||
Email string `json:"email,omitempty"`
|
||||
Name string `json:"name,omitempty"` // Full name of the commit author
|
||||
UserName string `json:"username,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := Reference{Ref: "main"}
|
||||
type Reference struct {
|
||||
Object *GitObject `json:"object,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Ref string `json:"ref,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// ReplaceFlagsOption — ReplaceFlagsOption options when replacing the flags of a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ReplaceFlagsOption{Flags: []string{"example"}}
|
||||
type ReplaceFlagsOption struct {
|
||||
Flags []string `json:"flags,omitempty"`
|
||||
}
|
||||
|
||||
// SearchResults — SearchResults results of a successful search
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := SearchResults{OK: true}
|
||||
type SearchResults struct {
|
||||
Data []*Repository `json:"data,omitempty"`
|
||||
OK bool `json:"ok,omitempty"`
|
||||
OK bool `json:"ok,omitempty"`
|
||||
}
|
||||
|
||||
// ServerVersion — ServerVersion wraps the version of the server
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ServerVersion{Version: "example"}
|
||||
type ServerVersion struct {
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// TimelineComment — TimelineComment represents a timeline comment (comment of any type) on a commit or issue
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := TimelineComment{Body: "example"}
|
||||
type TimelineComment struct {
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
AssigneeTeam *Team `json:"assignee_team,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DependentIssue *Issue `json:"dependent_issue,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IssueURL string `json:"issue_url,omitempty"`
|
||||
Label *Label `json:"label,omitempty"`
|
||||
Milestone *Milestone `json:"milestone,omitempty"`
|
||||
NewRef string `json:"new_ref,omitempty"`
|
||||
NewTitle string `json:"new_title,omitempty"`
|
||||
OldMilestone *Milestone `json:"old_milestone,omitempty"`
|
||||
OldProjectID int64 `json:"old_project_id,omitempty"`
|
||||
OldRef string `json:"old_ref,omitempty"`
|
||||
OldTitle string `json:"old_title,omitempty"`
|
||||
PRURL string `json:"pull_request_url,omitempty"`
|
||||
ProjectID int64 `json:"project_id,omitempty"`
|
||||
RefAction string `json:"ref_action,omitempty"`
|
||||
RefComment *Comment `json:"ref_comment,omitempty"`
|
||||
RefCommitSHA string `json:"ref_commit_sha,omitempty"` // commit SHA where issue/PR was referenced
|
||||
RefIssue *Issue `json:"ref_issue,omitempty"`
|
||||
RemovedAssignee bool `json:"removed_assignee,omitempty"` // whether the assignees were removed or added
|
||||
ResolveDoer *User `json:"resolve_doer,omitempty"`
|
||||
ReviewID int64 `json:"review_id,omitempty"`
|
||||
TrackedTime *TrackedTime `json:"tracked_time,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
AssigneeTeam *Team `json:"assignee_team,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DependentIssue *Issue `json:"dependent_issue,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IssueURL string `json:"issue_url,omitempty"`
|
||||
Label *Label `json:"label,omitempty"`
|
||||
Milestone *Milestone `json:"milestone,omitempty"`
|
||||
NewRef string `json:"new_ref,omitempty"`
|
||||
NewTitle string `json:"new_title,omitempty"`
|
||||
OldMilestone *Milestone `json:"old_milestone,omitempty"`
|
||||
OldProjectID int64 `json:"old_project_id,omitempty"`
|
||||
OldRef string `json:"old_ref,omitempty"`
|
||||
OldTitle string `json:"old_title,omitempty"`
|
||||
PRURL string `json:"pull_request_url,omitempty"`
|
||||
ProjectID int64 `json:"project_id,omitempty"`
|
||||
RefAction string `json:"ref_action,omitempty"`
|
||||
RefComment *Comment `json:"ref_comment,omitempty"`
|
||||
RefCommitSHA string `json:"ref_commit_sha,omitempty"` // commit SHA where issue/PR was referenced
|
||||
RefIssue *Issue `json:"ref_issue,omitempty"`
|
||||
RemovedAssignee bool `json:"removed_assignee,omitempty"` // whether the assignees were removed or added
|
||||
ResolveDoer *User `json:"resolve_doer,omitempty"`
|
||||
ReviewID int64 `json:"review_id,omitempty"`
|
||||
TrackedTime *TrackedTime `json:"tracked_time,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// WatchInfo — WatchInfo represents an API watch status of one repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := WatchInfo{RepositoryURL: "https://example.com"}
|
||||
type WatchInfo struct {
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
Ignored bool `json:"ignored,omitempty"`
|
||||
Reason any `json:"reason,omitempty"`
|
||||
RepositoryURL string `json:"repository_url,omitempty"`
|
||||
Subscribed bool `json:"subscribed,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
Ignored bool `json:"ignored,omitempty"`
|
||||
Reason any `json:"reason,omitempty"`
|
||||
RepositoryURL string `json:"repository_url,omitempty"`
|
||||
Subscribed bool `json:"subscribed,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,41 +4,31 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// NotificationCount — NotificationCount number of unread notifications
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NotificationCount{New: 1}
|
||||
type NotificationCount struct {
|
||||
New int64 `json:"new,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationSubject — NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NotificationSubject{Title: "example"}
|
||||
type NotificationSubject struct {
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
LatestCommentHTMLURL string `json:"latest_comment_html_url,omitempty"`
|
||||
LatestCommentURL string `json:"latest_comment_url,omitempty"`
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Type NotifySubjectType `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
LatestCommentHTMLURL string `json:"latest_comment_html_url,omitempty"`
|
||||
LatestCommentURL string `json:"latest_comment_url,omitempty"`
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Type *NotifySubjectType `json:"type,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
}
|
||||
|
||||
// NotificationThread — NotificationThread expose Notification on API
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := NotificationThread{URL: "https://example.com"}
|
||||
type NotificationThread struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Pinned bool `json:"pinned,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
Subject *NotificationSubject `json:"subject,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Unread bool `json:"unread,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Pinned bool `json:"pinned,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
Subject *NotificationSubject `json:"subject,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Unread bool `json:"unread,omitempty"`
|
||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,47 +4,35 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := AccessToken{Name: "example"}
|
||||
|
||||
type AccessToken struct {
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
Token string `json:"sha1,omitempty"`
|
||||
TokenLastEight string `json:"token_last_eight,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
Token string `json:"sha1,omitempty"`
|
||||
TokenLastEight string `json:"token_last_eight,omitempty"`
|
||||
}
|
||||
|
||||
// CreateAccessTokenOption — CreateAccessTokenOption options when create access token
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateAccessTokenOption{Name: "example"}
|
||||
type CreateAccessTokenOption struct {
|
||||
Name string `json:"name"`
|
||||
Name string `json:"name"`
|
||||
Scopes []string `json:"scopes,omitempty"`
|
||||
}
|
||||
|
||||
// CreateOAuth2ApplicationOptions — CreateOAuth2ApplicationOptions holds options to create an oauth2 application
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateOAuth2ApplicationOptions{Name: "example"}
|
||||
type CreateOAuth2ApplicationOptions struct {
|
||||
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := OAuth2Application{Name: "example"}
|
||||
type OAuth2Application struct {
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
ClientSecret string `json:"client_secret,omitempty"`
|
||||
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
ClientID string `json:"client_id,omitempty"`
|
||||
ClientSecret string `json:"client_secret,omitempty"`
|
||||
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
||||
Created time.Time `json:"created,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
78
types/org.go
78
types/org.go
|
|
@ -2,65 +2,51 @@
|
|||
|
||||
package types
|
||||
|
||||
|
||||
// CreateOrgOption — CreateOrgOption options for creating an organization
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateOrgOption{UserName: "example"}
|
||||
type CreateOrgOption struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||
UserName string `json:"username"`
|
||||
Visibility string `json:"visibility,omitempty"` // possible values are `public` (default), `limited` or `private`
|
||||
Website string `json:"website,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||
UserName string `json:"username"`
|
||||
Visibility string `json:"visibility,omitempty"` // possible values are `public` (default), `limited` or `private`
|
||||
Website string `json:"website,omitempty"`
|
||||
}
|
||||
|
||||
// EditOrgOption — EditOrgOption options for editing an organization
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditOrgOption{Description: "example"}
|
||||
type EditOrgOption struct {
|
||||
Description string `json:"description,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||
Visibility string `json:"visibility,omitempty"` // possible values are `public`, `limited` or `private`
|
||||
Website string `json:"website,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||
Visibility string `json:"visibility,omitempty"` // possible values are `public`, `limited` or `private`
|
||||
Website string `json:"website,omitempty"`
|
||||
}
|
||||
|
||||
// Organization — Organization represents an organization
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Organization{Description: "example"}
|
||||
type Organization struct {
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||
UserName string `json:"username,omitempty"` // deprecated
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Email string `json:"email,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Location string `json:"location,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||
UserName string `json:"username,omitempty"` // deprecated
|
||||
Visibility string `json:"visibility,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
}
|
||||
|
||||
// OrganizationPermissions — OrganizationPermissions list different users permissions on an organization
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := OrganizationPermissions{CanCreateRepository: true}
|
||||
type OrganizationPermissions struct {
|
||||
CanCreateRepository bool `json:"can_create_repository,omitempty"`
|
||||
CanRead bool `json:"can_read,omitempty"`
|
||||
CanWrite bool `json:"can_write,omitempty"`
|
||||
IsAdmin bool `json:"is_admin,omitempty"`
|
||||
IsOwner bool `json:"is_owner,omitempty"`
|
||||
CanRead bool `json:"can_read,omitempty"`
|
||||
CanWrite bool `json:"can_write,omitempty"`
|
||||
IsAdmin bool `json:"is_admin,omitempty"`
|
||||
IsOwner bool `json:"is_owner,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,34 +4,28 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// Package — Package represents a package
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Package{Name: "example"}
|
||||
type Package struct {
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
Creator *User `json:"creator,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Owner *User `json:"owner,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
Creator *User `json:"creator,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Owner *User `json:"owner,omitempty"`
|
||||
Repository *Repository `json:"repository,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Version string `json:"version,omitempty"`
|
||||
}
|
||||
|
||||
// PackageFile — PackageFile represents a package file
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PackageFile{Name: "example"}
|
||||
type PackageFile struct {
|
||||
HashMD5 string `json:"md5,omitempty"`
|
||||
HashSHA1 string `json:"sha1,omitempty"`
|
||||
HashMD5 string `json:"md5,omitempty"`
|
||||
HashSHA1 string `json:"sha1,omitempty"`
|
||||
HashSHA256 string `json:"sha256,omitempty"`
|
||||
HashSHA512 string `json:"sha512,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Size int64 `json:"Size,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Size int64 `json:"Size,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
249
types/pr.go
249
types/pr.go
|
|
@ -4,191 +4,150 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// CreatePullRequestOption — CreatePullRequestOption options when creating a pull request
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreatePullRequestOption{Body: "example"}
|
||||
type CreatePullRequestOption struct {
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Base string `json:"base,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Head string `json:"head,omitempty"`
|
||||
Labels []int64 `json:"labels,omitempty"`
|
||||
Milestone int64 `json:"milestone,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Base string `json:"base,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Head string `json:"head,omitempty"`
|
||||
Labels []int64 `json:"labels,omitempty"`
|
||||
Milestone int64 `json:"milestone,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// CreatePullReviewComment — CreatePullReviewComment represent a review comment for creation api
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreatePullReviewComment{Body: "example"}
|
||||
type CreatePullReviewComment struct {
|
||||
Body string `json:"body,omitempty"`
|
||||
NewLineNum int64 `json:"new_position,omitempty"` // if comment to new file line or 0
|
||||
OldLineNum int64 `json:"old_position,omitempty"` // if comment to old file line or 0
|
||||
Path string `json:"path,omitempty"` // the tree path
|
||||
Body string `json:"body,omitempty"`
|
||||
NewLineNum int64 `json:"new_position,omitempty"` // if comment to new file line or 0
|
||||
OldLineNum int64 `json:"old_position,omitempty"` // if comment to old file line or 0
|
||||
Path string `json:"path,omitempty"` // the tree path
|
||||
}
|
||||
|
||||
// CreatePullReviewCommentOptions — CreatePullReviewCommentOptions are options to create a pull review comment
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreatePullReviewCommentOptions(CreatePullReviewComment{})
|
||||
type CreatePullReviewCommentOptions CreatePullReviewComment
|
||||
// CreatePullReviewCommentOptions has no fields in the swagger spec.
|
||||
type CreatePullReviewCommentOptions struct{}
|
||||
|
||||
// CreatePullReviewOptions — CreatePullReviewOptions are options to create a pull review
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreatePullReviewOptions{Body: "example"}
|
||||
type CreatePullReviewOptions struct {
|
||||
Body string `json:"body,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Comments []*CreatePullReviewComment `json:"comments,omitempty"`
|
||||
CommitID string `json:"commit_id,omitempty"`
|
||||
Event ReviewStateType `json:"event,omitempty"`
|
||||
CommitID string `json:"commit_id,omitempty"`
|
||||
Event *ReviewStateType `json:"event,omitempty"`
|
||||
}
|
||||
|
||||
// EditPullRequestOption — EditPullRequestOption options when modify pull request
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditPullRequestOption{Body: "example"}
|
||||
type EditPullRequestOption struct {
|
||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Base string `json:"base,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Labels []int64 `json:"labels,omitempty"`
|
||||
Milestone int64 `json:"milestone,omitempty"`
|
||||
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
||||
Assignee string `json:"assignee,omitempty"`
|
||||
Assignees []string `json:"assignees,omitempty"`
|
||||
Base string `json:"base,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Labels []int64 `json:"labels,omitempty"`
|
||||
Milestone int64 `json:"milestone,omitempty"`
|
||||
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
||||
State string `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
}
|
||||
|
||||
// PullRequest — PullRequest represents a pull request
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PullRequest{Body: "example"}
|
||||
type PullRequest struct {
|
||||
Additions int64 `json:"additions,omitempty"`
|
||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
Assignees []*User `json:"assignees,omitempty"`
|
||||
Base *PRBranchInfo `json:"base,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
ChangedFiles int64 `json:"changed_files,omitempty"`
|
||||
Closed time.Time `json:"closed_at,omitempty"`
|
||||
Comments int64 `json:"comments,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Deletions int64 `json:"deletions,omitempty"`
|
||||
DiffURL string `json:"diff_url,omitempty"`
|
||||
Draft bool `json:"draft,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HasMerged bool `json:"merged,omitempty"`
|
||||
Head *PRBranchInfo `json:"head,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Index int64 `json:"number,omitempty"`
|
||||
IsLocked bool `json:"is_locked,omitempty"`
|
||||
Labels []*Label `json:"labels,omitempty"`
|
||||
MergeBase string `json:"merge_base,omitempty"`
|
||||
Mergeable bool `json:"mergeable,omitempty"`
|
||||
Merged time.Time `json:"merged_at,omitempty"`
|
||||
MergedBy *User `json:"merged_by,omitempty"`
|
||||
MergedCommitID string `json:"merge_commit_sha,omitempty"`
|
||||
Milestone *Milestone `json:"milestone,omitempty"`
|
||||
PatchURL string `json:"patch_url,omitempty"`
|
||||
PinOrder int64 `json:"pin_order,omitempty"`
|
||||
RequestedReviewers []*User `json:"requested_reviewers,omitempty"`
|
||||
RequestedReviewersTeams []*Team `json:"requested_reviewers_teams,omitempty"`
|
||||
ReviewComments int64 `json:"review_comments,omitempty"` // number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Additions int64 `json:"additions,omitempty"`
|
||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
||||
Assignee *User `json:"assignee,omitempty"`
|
||||
Assignees []*User `json:"assignees,omitempty"`
|
||||
Base *PRBranchInfo `json:"base,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
ChangedFiles int64 `json:"changed_files,omitempty"`
|
||||
Closed time.Time `json:"closed_at,omitempty"`
|
||||
Comments int64 `json:"comments,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Deadline time.Time `json:"due_date,omitempty"`
|
||||
Deletions int64 `json:"deletions,omitempty"`
|
||||
DiffURL string `json:"diff_url,omitempty"`
|
||||
Draft bool `json:"draft,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HasMerged bool `json:"merged,omitempty"`
|
||||
Head *PRBranchInfo `json:"head,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Index int64 `json:"number,omitempty"`
|
||||
IsLocked bool `json:"is_locked,omitempty"`
|
||||
Labels []*Label `json:"labels,omitempty"`
|
||||
MergeBase string `json:"merge_base,omitempty"`
|
||||
Mergeable bool `json:"mergeable,omitempty"`
|
||||
Merged time.Time `json:"merged_at,omitempty"`
|
||||
MergedBy *User `json:"merged_by,omitempty"`
|
||||
MergedCommitID string `json:"merge_commit_sha,omitempty"`
|
||||
Milestone *Milestone `json:"milestone,omitempty"`
|
||||
PatchURL string `json:"patch_url,omitempty"`
|
||||
PinOrder int64 `json:"pin_order,omitempty"`
|
||||
RequestedReviewers []*User `json:"requested_reviewers,omitempty"`
|
||||
RequestedReviewersTeams []*Team `json:"requested_reviewers_teams,omitempty"`
|
||||
ReviewComments int64 `json:"review_comments,omitempty"` // number of review comments made on the diff of a PR review (not including comments on commits or issues in a PR)
|
||||
State StateType `json:"state,omitempty"`
|
||||
Title string `json:"title,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// PullRequestMeta — PullRequestMeta PR info if an issue is a PR
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PullRequestMeta{HTMLURL: "https://example.com"}
|
||||
type PullRequestMeta struct {
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HasMerged bool `json:"merged,omitempty"`
|
||||
IsWorkInProgress bool `json:"draft,omitempty"`
|
||||
Merged time.Time `json:"merged_at,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HasMerged bool `json:"merged,omitempty"`
|
||||
IsWorkInProgress bool `json:"draft,omitempty"`
|
||||
Merged time.Time `json:"merged_at,omitempty"`
|
||||
}
|
||||
|
||||
// PullReview — PullReview represents a pull request review
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PullReview{Body: "example"}
|
||||
type PullReview struct {
|
||||
Body string `json:"body,omitempty"`
|
||||
CodeCommentsCount int64 `json:"comments_count,omitempty"`
|
||||
CommitID string `json:"commit_id,omitempty"`
|
||||
Dismissed bool `json:"dismissed,omitempty"`
|
||||
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Official bool `json:"official,omitempty"`
|
||||
Stale bool `json:"stale,omitempty"`
|
||||
State ReviewStateType `json:"state,omitempty"`
|
||||
Submitted time.Time `json:"submitted_at,omitempty"`
|
||||
Team *Team `json:"team,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
CodeCommentsCount int64 `json:"comments_count,omitempty"`
|
||||
CommitID string `json:"commit_id,omitempty"`
|
||||
Dismissed bool `json:"dismissed,omitempty"`
|
||||
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Official bool `json:"official,omitempty"`
|
||||
Stale bool `json:"stale,omitempty"`
|
||||
State *ReviewStateType `json:"state,omitempty"`
|
||||
Submitted time.Time `json:"submitted_at,omitempty"`
|
||||
Team *Team `json:"team,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// PullReviewComment — PullReviewComment represents a comment on a pull request review
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PullReviewComment{Body: "example"}
|
||||
type PullReviewComment struct {
|
||||
Body string `json:"body,omitempty"`
|
||||
CommitID string `json:"commit_id,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DiffHunk string `json:"diff_hunk,omitempty"`
|
||||
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
LineNum int `json:"position,omitempty"`
|
||||
OldLineNum int `json:"original_position,omitempty"`
|
||||
OrigCommitID string `json:"original_commit_id,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Resolver *User `json:"resolver,omitempty"`
|
||||
ReviewID int64 `json:"pull_request_review_id,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
CommitID string `json:"commit_id,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DiffHunk string `json:"diff_hunk,omitempty"`
|
||||
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
LineNum int `json:"position,omitempty"`
|
||||
OldLineNum int `json:"original_position,omitempty"`
|
||||
OrigCommitID string `json:"original_commit_id,omitempty"`
|
||||
Path string `json:"path,omitempty"`
|
||||
Resolver *User `json:"resolver,omitempty"`
|
||||
ReviewID int64 `json:"pull_request_review_id,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// PullReviewRequestOptions — PullReviewRequestOptions are options to add or remove pull review requests
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PullReviewRequestOptions{Reviewers: []string{"example"}}
|
||||
type PullReviewRequestOptions struct {
|
||||
Reviewers []string `json:"reviewers,omitempty"`
|
||||
Reviewers []string `json:"reviewers,omitempty"`
|
||||
TeamReviewers []string `json:"team_reviewers,omitempty"`
|
||||
}
|
||||
|
||||
// SubmitPullReviewOptions — SubmitPullReviewOptions are options to submit a pending pull review
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := SubmitPullReviewOptions{Body: "example"}
|
||||
type SubmitPullReviewOptions struct {
|
||||
Body string `json:"body,omitempty"`
|
||||
Event ReviewStateType `json:"event,omitempty"`
|
||||
Body string `json:"body,omitempty"`
|
||||
Event *ReviewStateType `json:"event,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
142
types/quota.go
142
types/quota.go
|
|
@ -2,197 +2,123 @@
|
|||
|
||||
package types
|
||||
|
||||
|
||||
// CreateQuotaGroupOptions — CreateQutaGroupOptions represents the options for creating a quota group
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateQuotaGroupOptions{Name: "example"}
|
||||
type CreateQuotaGroupOptions struct {
|
||||
Name string `json:"name,omitempty"` // Name of the quota group to create
|
||||
Name string `json:"name,omitempty"` // Name of the quota group to create
|
||||
Rules []*CreateQuotaRuleOptions `json:"rules,omitempty"` // Rules to add to the newly created group. If a rule does not exist, it will be created.
|
||||
}
|
||||
|
||||
// CreateQuotaRuleOptions — CreateQuotaRuleOptions represents the options for creating a quota rule
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateQuotaRuleOptions{Name: "example"}
|
||||
type CreateQuotaRuleOptions struct {
|
||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||
Name string `json:"name,omitempty"` // Name of the rule to create
|
||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||
Name string `json:"name,omitempty"` // Name of the rule to create
|
||||
Subjects []string `json:"subjects,omitempty"` // The subjects affected by the rule
|
||||
}
|
||||
|
||||
// EditQuotaRuleOptions — EditQuotaRuleOptions represents the options for editing a quota rule
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditQuotaRuleOptions{Subjects: []string{"example"}}
|
||||
type EditQuotaRuleOptions struct {
|
||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||
Subjects []string `json:"subjects,omitempty"` // The subjects affected by the rule
|
||||
}
|
||||
|
||||
// QuotaGroup — QuotaGroup represents a quota group
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaGroup{Name: "example"}
|
||||
type QuotaGroup struct {
|
||||
Name string `json:"name,omitempty"` // Name of the group
|
||||
Name string `json:"name,omitempty"` // Name of the group
|
||||
Rules []*QuotaRuleInfo `json:"rules,omitempty"` // Rules associated with the group
|
||||
}
|
||||
|
||||
// QuotaGroupList — QuotaGroupList represents a list of quota groups
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaGroupList([]*QuotaGroup{})
|
||||
type QuotaGroupList []*QuotaGroup
|
||||
// QuotaGroupList has no fields in the swagger spec.
|
||||
type QuotaGroupList struct{}
|
||||
|
||||
// QuotaInfo — QuotaInfo represents information about a user's quota
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaInfo{Groups: {}}
|
||||
type QuotaInfo struct {
|
||||
Groups QuotaGroupList `json:"groups,omitempty"`
|
||||
Used *QuotaUsed `json:"used,omitempty"`
|
||||
Groups *QuotaGroupList `json:"groups,omitempty"`
|
||||
Used *QuotaUsed `json:"used,omitempty"`
|
||||
}
|
||||
|
||||
// QuotaRuleInfo — QuotaRuleInfo contains information about a quota rule
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaRuleInfo{Name: "example"}
|
||||
type QuotaRuleInfo struct {
|
||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||
Name string `json:"name,omitempty"` // Name of the rule (only shown to admins)
|
||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||
Name string `json:"name,omitempty"` // Name of the rule (only shown to admins)
|
||||
Subjects []string `json:"subjects,omitempty"` // Subjects the rule affects
|
||||
}
|
||||
|
||||
// QuotaUsed — QuotaUsed represents the quota usage of a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsed{Size: &QuotaUsedSize{}}
|
||||
type QuotaUsed struct {
|
||||
Size *QuotaUsedSize `json:"size,omitempty"`
|
||||
}
|
||||
|
||||
// QuotaUsedArtifact — QuotaUsedArtifact represents an artifact counting towards a user's quota
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedArtifact{Name: "example"}
|
||||
type QuotaUsedArtifact struct {
|
||||
HTMLURL string `json:"html_url,omitempty"` // HTML URL to the action run containing the artifact
|
||||
Name string `json:"name,omitempty"` // Name of the artifact
|
||||
Size int64 `json:"size,omitempty"` // Size of the artifact (compressed)
|
||||
Name string `json:"name,omitempty"` // Name of the artifact
|
||||
Size int64 `json:"size,omitempty"` // Size of the artifact (compressed)
|
||||
}
|
||||
|
||||
// QuotaUsedArtifactList — QuotaUsedArtifactList represents a list of artifacts counting towards a user's quota
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedArtifactList([]*QuotaUsedArtifact{})
|
||||
type QuotaUsedArtifactList []*QuotaUsedArtifact
|
||||
// QuotaUsedArtifactList has no fields in the swagger spec.
|
||||
type QuotaUsedArtifactList struct{}
|
||||
|
||||
// QuotaUsedAttachment — QuotaUsedAttachment represents an attachment counting towards a user's quota
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedAttachment{Name: "example"}
|
||||
type QuotaUsedAttachment struct {
|
||||
APIURL string `json:"api_url,omitempty"` // API URL for the attachment
|
||||
APIURL string `json:"api_url,omitempty"` // API URL for the attachment
|
||||
ContainedIn map[string]any `json:"contained_in,omitempty"` // Context for the attachment: URLs to the containing object
|
||||
Name string `json:"name,omitempty"` // Filename of the attachment
|
||||
Size int64 `json:"size,omitempty"` // Size of the attachment (in bytes)
|
||||
Name string `json:"name,omitempty"` // Filename of the attachment
|
||||
Size int64 `json:"size,omitempty"` // Size of the attachment (in bytes)
|
||||
}
|
||||
|
||||
// QuotaUsedAttachmentList — QuotaUsedAttachmentList represents a list of attachment counting towards a user's quota
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedAttachmentList([]*QuotaUsedAttachment{})
|
||||
type QuotaUsedAttachmentList []*QuotaUsedAttachment
|
||||
// QuotaUsedAttachmentList has no fields in the swagger spec.
|
||||
type QuotaUsedAttachmentList struct{}
|
||||
|
||||
// QuotaUsedPackage — QuotaUsedPackage represents a package counting towards a user's quota
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedPackage{Name: "example"}
|
||||
type QuotaUsedPackage struct {
|
||||
HTMLURL string `json:"html_url,omitempty"` // HTML URL to the package version
|
||||
Name string `json:"name,omitempty"` // Name of the package
|
||||
Size int64 `json:"size,omitempty"` // Size of the package version
|
||||
Type string `json:"type,omitempty"` // Type of the package
|
||||
Version string `json:"version,omitempty"` // Version of the package
|
||||
Name string `json:"name,omitempty"` // Name of the package
|
||||
Size int64 `json:"size,omitempty"` // Size of the package version
|
||||
Type string `json:"type,omitempty"` // Type of the package
|
||||
Version string `json:"version,omitempty"` // Version of the package
|
||||
}
|
||||
|
||||
// QuotaUsedPackageList — QuotaUsedPackageList represents a list of packages counting towards a user's quota
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedPackageList([]*QuotaUsedPackage{})
|
||||
type QuotaUsedPackageList []*QuotaUsedPackage
|
||||
// QuotaUsedPackageList has no fields in the swagger spec.
|
||||
type QuotaUsedPackageList struct{}
|
||||
|
||||
// QuotaUsedSize — QuotaUsedSize represents the size-based quota usage of a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedSize{Assets: &QuotaUsedSizeAssets{}}
|
||||
type QuotaUsedSize struct {
|
||||
Assets *QuotaUsedSizeAssets `json:"assets,omitempty"`
|
||||
Git *QuotaUsedSizeGit `json:"git,omitempty"`
|
||||
Repos *QuotaUsedSizeRepos `json:"repos,omitempty"`
|
||||
Git *QuotaUsedSizeGit `json:"git,omitempty"`
|
||||
Repos *QuotaUsedSizeRepos `json:"repos,omitempty"`
|
||||
}
|
||||
|
||||
// QuotaUsedSizeAssets — QuotaUsedSizeAssets represents the size-based asset usage of a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedSizeAssets{Artifacts: 1}
|
||||
type QuotaUsedSizeAssets struct {
|
||||
Artifacts int64 `json:"artifacts,omitempty"` // Storage size used for the user's artifacts
|
||||
Artifacts int64 `json:"artifacts,omitempty"` // Storage size used for the user's artifacts
|
||||
Attachments *QuotaUsedSizeAssetsAttachments `json:"attachments,omitempty"`
|
||||
Packages *QuotaUsedSizeAssetsPackages `json:"packages,omitempty"`
|
||||
Packages *QuotaUsedSizeAssetsPackages `json:"packages,omitempty"`
|
||||
}
|
||||
|
||||
// QuotaUsedSizeAssetsAttachments — QuotaUsedSizeAssetsAttachments represents the size-based attachment quota usage of a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedSizeAssetsAttachments{Issues: 1}
|
||||
type QuotaUsedSizeAssetsAttachments struct {
|
||||
Issues int64 `json:"issues,omitempty"` // Storage size used for the user's issue & comment attachments
|
||||
Issues int64 `json:"issues,omitempty"` // Storage size used for the user's issue & comment attachments
|
||||
Releases int64 `json:"releases,omitempty"` // Storage size used for the user's release attachments
|
||||
}
|
||||
|
||||
// QuotaUsedSizeAssetsPackages — QuotaUsedSizeAssetsPackages represents the size-based package quota usage of a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedSizeAssetsPackages{All: 1}
|
||||
type QuotaUsedSizeAssetsPackages struct {
|
||||
All int64 `json:"all,omitempty"` // Storage suze used for the user's packages
|
||||
}
|
||||
|
||||
// QuotaUsedSizeGit — QuotaUsedSizeGit represents the size-based git (lfs) quota usage of a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedSizeGit{LFS: 1}
|
||||
type QuotaUsedSizeGit struct {
|
||||
LFS int64 `json:"LFS,omitempty"` // Storage size of the user's Git LFS objects
|
||||
}
|
||||
|
||||
// QuotaUsedSizeRepos — QuotaUsedSizeRepos represents the size-based repository quota usage of a user
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := QuotaUsedSizeRepos{Private: 1}
|
||||
type QuotaUsedSizeRepos struct {
|
||||
Private int64 `json:"private,omitempty"` // Storage size of the user's private repositories
|
||||
Public int64 `json:"public,omitempty"` // Storage size of the user's public repositories
|
||||
Public int64 `json:"public,omitempty"` // Storage size of the user's public repositories
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,22 +4,16 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// EditReactionOption — EditReactionOption contain the reaction type
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditReactionOption{Reaction: "example"}
|
||||
type EditReactionOption struct {
|
||||
Reaction string `json:"content,omitempty"`
|
||||
}
|
||||
|
||||
// Reaction — Reaction contain one reaction
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Reaction{Reaction: "example"}
|
||||
type Reaction struct {
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Reaction string `json:"content,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
Reaction string `json:"content,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,58 +4,48 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
|
||||
// CreateReleaseOption — CreateReleaseOption options when creating a release
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateReleaseOption{TagName: "v1.0.0"}
|
||||
type CreateReleaseOption struct {
|
||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||
IsDraft bool `json:"draft,omitempty"`
|
||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||
Note string `json:"body,omitempty"`
|
||||
TagName string `json:"tag_name"`
|
||||
Target string `json:"target_commitish,omitempty"`
|
||||
Title string `json:"name,omitempty"`
|
||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||
IsDraft bool `json:"draft,omitempty"`
|
||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||
Note string `json:"body,omitempty"`
|
||||
TagName string `json:"tag_name"`
|
||||
Target string `json:"target_commitish,omitempty"`
|
||||
Title string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// EditReleaseOption — EditReleaseOption options when editing a release
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditReleaseOption{TagName: "v1.0.0"}
|
||||
type EditReleaseOption struct {
|
||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||
IsDraft bool `json:"draft,omitempty"`
|
||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||
Note string `json:"body,omitempty"`
|
||||
TagName string `json:"tag_name,omitempty"`
|
||||
Target string `json:"target_commitish,omitempty"`
|
||||
Title string `json:"name,omitempty"`
|
||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||
IsDraft bool `json:"draft,omitempty"`
|
||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||
Note string `json:"body,omitempty"`
|
||||
TagName string `json:"tag_name,omitempty"`
|
||||
Target string `json:"target_commitish,omitempty"`
|
||||
Title string `json:"name,omitempty"`
|
||||
}
|
||||
|
||||
// Release — Release represents a repository release
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Release{TagName: "v1.0.0"}
|
||||
type Release struct {
|
||||
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count,omitempty"`
|
||||
Attachments []*Attachment `json:"assets,omitempty"`
|
||||
Author *User `json:"author,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IsDraft bool `json:"draft,omitempty"`
|
||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||
Note string `json:"body,omitempty"`
|
||||
PublishedAt time.Time `json:"published_at,omitempty"`
|
||||
TagName string `json:"tag_name,omitempty"`
|
||||
TarURL string `json:"tarball_url,omitempty"`
|
||||
Target string `json:"target_commitish,omitempty"`
|
||||
Title string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
UploadURL string `json:"upload_url,omitempty"`
|
||||
ZipURL string `json:"zipball_url,omitempty"`
|
||||
Attachments []*Attachment `json:"assets,omitempty"`
|
||||
Author *User `json:"author,omitempty"`
|
||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IsDraft bool `json:"draft,omitempty"`
|
||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||
Note string `json:"body,omitempty"`
|
||||
PublishedAt time.Time `json:"published_at,omitempty"`
|
||||
TagName string `json:"tag_name,omitempty"`
|
||||
TarURL string `json:"tarball_url,omitempty"`
|
||||
Target string `json:"target_commitish,omitempty"`
|
||||
Title string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
UploadURL string `json:"upload_url,omitempty"`
|
||||
ZipURL string `json:"zipball_url,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
340
types/repo.go
340
types/repo.go
|
|
@ -4,270 +4,214 @@ package types
|
|||
|
||||
import "time"
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreatePushMirrorOption{Interval: "example"}
|
||||
|
||||
type CreatePushMirrorOption struct {
|
||||
Interval string `json:"interval,omitempty"`
|
||||
RemoteAddress string `json:"remote_address,omitempty"`
|
||||
Interval string `json:"interval,omitempty"`
|
||||
RemoteAddress string `json:"remote_address,omitempty"`
|
||||
RemotePassword string `json:"remote_password,omitempty"`
|
||||
RemoteUsername string `json:"remote_username,omitempty"`
|
||||
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
||||
UseSSH bool `json:"use_ssh,omitempty"`
|
||||
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
||||
UseSSH bool `json:"use_ssh,omitempty"`
|
||||
}
|
||||
|
||||
// CreateRepoOption — CreateRepoOption options when creating repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := CreateRepoOption{Name: "example"}
|
||||
type CreateRepoOption struct {
|
||||
AutoInit bool `json:"auto_init,omitempty"` // Whether the repository should be auto-initialized?
|
||||
DefaultBranch string `json:"default_branch,omitempty"` // DefaultBranch of the repository (used when initializes and in template)
|
||||
Description string `json:"description,omitempty"` // Description of the repository to create
|
||||
Gitignores string `json:"gitignores,omitempty"` // Gitignores to use
|
||||
IssueLabels string `json:"issue_labels,omitempty"` // Label-Set to use
|
||||
License string `json:"license,omitempty"` // License to use
|
||||
Name string `json:"name"` // Name of the repository to create
|
||||
AutoInit bool `json:"auto_init,omitempty"` // Whether the repository should be auto-initialized?
|
||||
DefaultBranch string `json:"default_branch,omitempty"` // DefaultBranch of the repository (used when initializes and in template)
|
||||
Description string `json:"description,omitempty"` // Description of the repository to create
|
||||
Gitignores string `json:"gitignores,omitempty"` // Gitignores to use
|
||||
IssueLabels string `json:"issue_labels,omitempty"` // Label-Set to use
|
||||
License string `json:"license,omitempty"` // License to use
|
||||
Name string `json:"name"` // Name of the repository to create
|
||||
ObjectFormatName string `json:"object_format_name,omitempty"` // ObjectFormatName of the underlying git repository
|
||||
Private bool `json:"private,omitempty"` // Whether the repository is private
|
||||
Readme string `json:"readme,omitempty"` // Readme of the repository to create
|
||||
Template bool `json:"template,omitempty"` // Whether the repository is template
|
||||
TrustModel string `json:"trust_model,omitempty"` // TrustModel of the repository
|
||||
Private bool `json:"private,omitempty"` // Whether the repository is private
|
||||
Readme string `json:"readme,omitempty"` // Readme of the repository to create
|
||||
Template bool `json:"template,omitempty"` // Whether the repository is template
|
||||
TrustModel string `json:"trust_model,omitempty"` // TrustModel of the repository
|
||||
}
|
||||
|
||||
// EditRepoOption — EditRepoOption options when editing a repository's properties
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := EditRepoOption{Description: "example"}
|
||||
type EditRepoOption struct {
|
||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge,omitempty"` // either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging.
|
||||
AllowManualMerge bool `json:"allow_manual_merge,omitempty"` // either `true` to allow mark pr as merged manually, or `false` to prevent it.
|
||||
AllowMerge bool `json:"allow_merge_commits,omitempty"` // either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits.
|
||||
AllowRebase bool `json:"allow_rebase,omitempty"` // either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging.
|
||||
AllowRebaseMerge bool `json:"allow_rebase_explicit,omitempty"` // either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits.
|
||||
AllowRebaseUpdate bool `json:"allow_rebase_update,omitempty"` // either `true` to allow updating pull request branch by rebase, or `false` to prevent it.
|
||||
AllowSquash bool `json:"allow_squash_merge,omitempty"` // either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging.
|
||||
Archived bool `json:"archived,omitempty"` // set to `true` to archive this repository.
|
||||
AutodetectManualMerge bool `json:"autodetect_manual_merge,omitempty"` // either `true` to enable AutodetectManualMerge, or `false` to prevent it. Note: In some special cases, misjudgments can occur.
|
||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit,omitempty"` // set to `true` to allow edits from maintainers by default
|
||||
DefaultBranch string `json:"default_branch,omitempty"` // sets the default branch for this repository.
|
||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge,omitempty"` // set to `true` to delete pr branch after merge by default
|
||||
DefaultMergeStyle string `json:"default_merge_style,omitempty"` // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", or "fast-forward-only".
|
||||
DefaultUpdateStyle string `json:"default_update_style,omitempty"` // set to a update style to be used by this repository: "rebase" or "merge"
|
||||
Description string `json:"description,omitempty"` // a short description of the repository.
|
||||
EnablePrune bool `json:"enable_prune,omitempty"` // enable prune - remove obsolete remote-tracking references when mirroring
|
||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
GloballyEditableWiki bool `json:"globally_editable_wiki,omitempty"` // set the globally editable state of the wiki
|
||||
HasActions bool `json:"has_actions,omitempty"` // either `true` to enable actions unit, or `false` to disable them.
|
||||
HasIssues bool `json:"has_issues,omitempty"` // either `true` to enable issues for this repository or `false` to disable them.
|
||||
HasPackages bool `json:"has_packages,omitempty"` // either `true` to enable packages unit, or `false` to disable them.
|
||||
HasProjects bool `json:"has_projects,omitempty"` // either `true` to enable project unit, or `false` to disable them.
|
||||
HasPullRequests bool `json:"has_pull_requests,omitempty"` // either `true` to allow pull requests, or `false` to prevent pull request.
|
||||
HasReleases bool `json:"has_releases,omitempty"` // either `true` to enable releases unit, or `false` to disable them.
|
||||
HasWiki bool `json:"has_wiki,omitempty"` // either `true` to enable the wiki for this repository or `false` to disable it.
|
||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts,omitempty"` // either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace.
|
||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||
MirrorInterval string `json:"mirror_interval,omitempty"` // set to a string like `8h30m0s` to set the mirror interval time
|
||||
Name string `json:"name,omitempty"` // name of the repository
|
||||
Private bool `json:"private,omitempty"` // either `true` to make the repository private or `false` to make it public. Note: you will get a 422 error if the organization restricts changing repository visibility to organization owners and a non-owner tries to change the value of private.
|
||||
Template bool `json:"template,omitempty"` // either `true` to make this repository a template or `false` to make it a normal repository
|
||||
Website string `json:"website,omitempty"` // a URL with more information about the repository.
|
||||
WikiBranch string `json:"wiki_branch,omitempty"` // sets the branch used for this repository's wiki.
|
||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge,omitempty"` // either `true` to allow fast-forward-only merging pull requests, or `false` to prevent fast-forward-only merging.
|
||||
AllowManualMerge bool `json:"allow_manual_merge,omitempty"` // either `true` to allow mark pr as merged manually, or `false` to prevent it.
|
||||
AllowMerge bool `json:"allow_merge_commits,omitempty"` // either `true` to allow merging pull requests with a merge commit, or `false` to prevent merging pull requests with merge commits.
|
||||
AllowRebase bool `json:"allow_rebase,omitempty"` // either `true` to allow rebase-merging pull requests, or `false` to prevent rebase-merging.
|
||||
AllowRebaseMerge bool `json:"allow_rebase_explicit,omitempty"` // either `true` to allow rebase with explicit merge commits (--no-ff), or `false` to prevent rebase with explicit merge commits.
|
||||
AllowRebaseUpdate bool `json:"allow_rebase_update,omitempty"` // either `true` to allow updating pull request branch by rebase, or `false` to prevent it.
|
||||
AllowSquash bool `json:"allow_squash_merge,omitempty"` // either `true` to allow squash-merging pull requests, or `false` to prevent squash-merging.
|
||||
Archived bool `json:"archived,omitempty"` // set to `true` to archive this repository.
|
||||
AutodetectManualMerge bool `json:"autodetect_manual_merge,omitempty"` // either `true` to enable AutodetectManualMerge, or `false` to prevent it. Note: In some special cases, misjudgments can occur.
|
||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit,omitempty"` // set to `true` to allow edits from maintainers by default
|
||||
DefaultBranch string `json:"default_branch,omitempty"` // sets the default branch for this repository.
|
||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge,omitempty"` // set to `true` to delete pr branch after merge by default
|
||||
DefaultMergeStyle string `json:"default_merge_style,omitempty"` // set to a merge style to be used by this repository: "merge", "rebase", "rebase-merge", "squash", or "fast-forward-only".
|
||||
DefaultUpdateStyle string `json:"default_update_style,omitempty"` // set to a update style to be used by this repository: "rebase" or "merge"
|
||||
Description string `json:"description,omitempty"` // a short description of the repository.
|
||||
EnablePrune bool `json:"enable_prune,omitempty"` // enable prune - remove obsolete remote-tracking references when mirroring
|
||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
GloballyEditableWiki bool `json:"globally_editable_wiki,omitempty"` // set the globally editable state of the wiki
|
||||
HasActions bool `json:"has_actions,omitempty"` // either `true` to enable actions unit, or `false` to disable them.
|
||||
HasIssues bool `json:"has_issues,omitempty"` // either `true` to enable issues for this repository or `false` to disable them.
|
||||
HasPackages bool `json:"has_packages,omitempty"` // either `true` to enable packages unit, or `false` to disable them.
|
||||
HasProjects bool `json:"has_projects,omitempty"` // either `true` to enable project unit, or `false` to disable them.
|
||||
HasPullRequests bool `json:"has_pull_requests,omitempty"` // either `true` to allow pull requests, or `false` to prevent pull request.
|
||||
HasReleases bool `json:"has_releases,omitempty"` // either `true` to enable releases unit, or `false` to disable them.
|
||||
HasWiki bool `json:"has_wiki,omitempty"` // either `true` to enable the wiki for this repository or `false` to disable it.
|
||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts,omitempty"` // either `true` to ignore whitespace for conflicts, or `false` to not ignore whitespace.
|
||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||
MirrorInterval string `json:"mirror_interval,omitempty"` // set to a string like `8h30m0s` to set the mirror interval time
|
||||
Name string `json:"name,omitempty"` // name of the repository
|
||||
Private bool `json:"private,omitempty"` // either `true` to make the repository private or `false` to make it public. Note: you will get a 422 error if the organization restricts changing repository visibility to organization owners and a non-owner tries to change the value of private.
|
||||
Template bool `json:"template,omitempty"` // either `true` to make this repository a template or `false` to make it a normal repository
|
||||
Website string `json:"website,omitempty"` // a URL with more information about the repository.
|
||||
WikiBranch string `json:"wiki_branch,omitempty"` // sets the branch used for this repository's wiki.
|
||||
}
|
||||
|
||||
// ExternalTracker — ExternalTracker represents settings for external tracker
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ExternalTracker{ExternalTrackerFormat: "example"}
|
||||
type ExternalTracker struct {
|
||||
ExternalTrackerFormat string `json:"external_tracker_format,omitempty"` // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
|
||||
ExternalTrackerFormat string `json:"external_tracker_format,omitempty"` // External Issue Tracker URL Format. Use the placeholders {user}, {repo} and {index} for the username, repository name and issue index.
|
||||
ExternalTrackerRegexpPattern string `json:"external_tracker_regexp_pattern,omitempty"` // External Issue Tracker issue regular expression
|
||||
ExternalTrackerStyle string `json:"external_tracker_style,omitempty"` // External Issue Tracker Number Format, either `numeric`, `alphanumeric`, or `regexp`
|
||||
ExternalTrackerURL string `json:"external_tracker_url,omitempty"` // URL of external issue tracker.
|
||||
ExternalTrackerStyle string `json:"external_tracker_style,omitempty"` // External Issue Tracker Number Format, either `numeric`, `alphanumeric`, or `regexp`
|
||||
ExternalTrackerURL string `json:"external_tracker_url,omitempty"` // URL of external issue tracker.
|
||||
}
|
||||
|
||||
// ExternalWiki — ExternalWiki represents setting for external wiki
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ExternalWiki{ExternalWikiURL: "https://example.com"}
|
||||
type ExternalWiki struct {
|
||||
ExternalWikiURL string `json:"external_wiki_url,omitempty"` // URL of external wiki.
|
||||
}
|
||||
|
||||
// InternalTracker — InternalTracker represents settings for internal tracker
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := InternalTracker{AllowOnlyContributorsToTrackTime: true}
|
||||
type InternalTracker struct {
|
||||
AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time,omitempty"` // Let only contributors track time (Built-in issue tracker)
|
||||
EnableIssueDependencies bool `json:"enable_issue_dependencies,omitempty"` // Enable dependencies for issues and pull requests (Built-in issue tracker)
|
||||
EnableTimeTracker bool `json:"enable_time_tracker,omitempty"` // Enable time tracking (Built-in issue tracker)
|
||||
EnableIssueDependencies bool `json:"enable_issue_dependencies,omitempty"` // Enable dependencies for issues and pull requests (Built-in issue tracker)
|
||||
EnableTimeTracker bool `json:"enable_time_tracker,omitempty"` // Enable time tracking (Built-in issue tracker)
|
||||
}
|
||||
|
||||
// PushMirror — PushMirror represents information of a push mirror
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := PushMirror{RemoteName: "example"}
|
||||
type PushMirror struct {
|
||||
CreatedUnix time.Time `json:"created,omitempty"`
|
||||
Interval string `json:"interval,omitempty"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
CreatedUnix time.Time `json:"created,omitempty"`
|
||||
Interval string `json:"interval,omitempty"`
|
||||
LastError string `json:"last_error,omitempty"`
|
||||
LastUpdateUnix time.Time `json:"last_update,omitempty"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
RemoteAddress string `json:"remote_address,omitempty"`
|
||||
RemoteName string `json:"remote_name,omitempty"`
|
||||
RepoName string `json:"repo_name,omitempty"`
|
||||
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
||||
PublicKey string `json:"public_key,omitempty"`
|
||||
RemoteAddress string `json:"remote_address,omitempty"`
|
||||
RemoteName string `json:"remote_name,omitempty"`
|
||||
RepoName string `json:"repo_name,omitempty"`
|
||||
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
||||
}
|
||||
|
||||
// RepoCollaboratorPermission — RepoCollaboratorPermission to get repository permission for a collaborator
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := RepoCollaboratorPermission{RoleName: "example"}
|
||||
type RepoCollaboratorPermission struct {
|
||||
Permission string `json:"permission,omitempty"`
|
||||
RoleName string `json:"role_name,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
RoleName string `json:"role_name,omitempty"`
|
||||
User *User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// Usage:
|
||||
//
|
||||
// opts := RepoCommit{Message: "example"}
|
||||
type RepoCommit struct {
|
||||
Author *CommitUser `json:"author,omitempty"`
|
||||
Committer *CommitUser `json:"committer,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Tree *CommitMeta `json:"tree,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Author *CommitUser `json:"author,omitempty"`
|
||||
Committer *CommitUser `json:"committer,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Tree *CommitMeta `json:"tree,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||
}
|
||||
|
||||
// RepoTopicOptions — RepoTopicOptions a collection of repo topic names
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := RepoTopicOptions{Topics: []string{"example"}}
|
||||
type RepoTopicOptions struct {
|
||||
Topics []string `json:"topics,omitempty"` // list of topic names
|
||||
}
|
||||
|
||||
// RepoTransfer — RepoTransfer represents a pending repo transfer
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := RepoTransfer{Teams: {}}
|
||||
type RepoTransfer struct {
|
||||
Doer *User `json:"doer,omitempty"`
|
||||
Recipient *User `json:"recipient,omitempty"`
|
||||
Teams []*Team `json:"teams,omitempty"`
|
||||
Doer *User `json:"doer,omitempty"`
|
||||
Recipient *User `json:"recipient,omitempty"`
|
||||
Teams []*Team `json:"teams,omitempty"`
|
||||
}
|
||||
|
||||
// Repository — Repository represents a repository
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := Repository{Description: "example"}
|
||||
type Repository struct {
|
||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge,omitempty"`
|
||||
AllowMerge bool `json:"allow_merge_commits,omitempty"`
|
||||
AllowRebase bool `json:"allow_rebase,omitempty"`
|
||||
AllowRebaseMerge bool `json:"allow_rebase_explicit,omitempty"`
|
||||
AllowRebaseUpdate bool `json:"allow_rebase_update,omitempty"`
|
||||
AllowSquash bool `json:"allow_squash_merge,omitempty"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
ArchivedAt time.Time `json:"archived_at,omitempty"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
CloneURL string `json:"clone_url,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit,omitempty"`
|
||||
DefaultBranch string `json:"default_branch,omitempty"`
|
||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge,omitempty"`
|
||||
DefaultMergeStyle string `json:"default_merge_style,omitempty"`
|
||||
DefaultUpdateStyle string `json:"default_update_style,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Empty bool `json:"empty,omitempty"`
|
||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
Fork bool `json:"fork,omitempty"`
|
||||
Forks int64 `json:"forks_count,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
GloballyEditableWiki bool `json:"globally_editable_wiki,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HasActions bool `json:"has_actions,omitempty"`
|
||||
HasIssues bool `json:"has_issues,omitempty"`
|
||||
HasPackages bool `json:"has_packages,omitempty"`
|
||||
HasProjects bool `json:"has_projects,omitempty"`
|
||||
HasPullRequests bool `json:"has_pull_requests,omitempty"`
|
||||
HasReleases bool `json:"has_releases,omitempty"`
|
||||
HasWiki bool `json:"has_wiki,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts,omitempty"`
|
||||
Internal bool `json:"internal,omitempty"`
|
||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
LanguagesURL string `json:"languages_url,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
Mirror bool `json:"mirror,omitempty"`
|
||||
MirrorInterval string `json:"mirror_interval,omitempty"`
|
||||
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ObjectFormatName string `json:"object_format_name,omitempty"` // ObjectFormatName of the underlying git repository
|
||||
OpenIssues int64 `json:"open_issues_count,omitempty"`
|
||||
OpenPulls int64 `json:"open_pr_counter,omitempty"`
|
||||
OriginalURL string `json:"original_url,omitempty"`
|
||||
Owner *User `json:"owner,omitempty"`
|
||||
Parent *Repository `json:"parent,omitempty"`
|
||||
Permissions *Permission `json:"permissions,omitempty"`
|
||||
Private bool `json:"private,omitempty"`
|
||||
Releases int64 `json:"release_counter,omitempty"`
|
||||
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
|
||||
SSHURL string `json:"ssh_url,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Stars int64 `json:"stars_count,omitempty"`
|
||||
Template bool `json:"template,omitempty"`
|
||||
Topics []string `json:"topics,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
Watchers int64 `json:"watchers_count,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
WikiBranch string `json:"wiki_branch,omitempty"`
|
||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge,omitempty"`
|
||||
AllowMerge bool `json:"allow_merge_commits,omitempty"`
|
||||
AllowRebase bool `json:"allow_rebase,omitempty"`
|
||||
AllowRebaseMerge bool `json:"allow_rebase_explicit,omitempty"`
|
||||
AllowRebaseUpdate bool `json:"allow_rebase_update,omitempty"`
|
||||
AllowSquash bool `json:"allow_squash_merge,omitempty"`
|
||||
Archived bool `json:"archived,omitempty"`
|
||||
ArchivedAt time.Time `json:"archived_at,omitempty"`
|
||||
AvatarURL string `json:"avatar_url,omitempty"`
|
||||
CloneURL string `json:"clone_url,omitempty"`
|
||||
Created time.Time `json:"created_at,omitempty"`
|
||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit,omitempty"`
|
||||
DefaultBranch string `json:"default_branch,omitempty"`
|
||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge,omitempty"`
|
||||
DefaultMergeStyle string `json:"default_merge_style,omitempty"`
|
||||
DefaultUpdateStyle string `json:"default_update_style,omitempty"`
|
||||
Description string `json:"description,omitempty"`
|
||||
Empty bool `json:"empty,omitempty"`
|
||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||
Fork bool `json:"fork,omitempty"`
|
||||
Forks int64 `json:"forks_count,omitempty"`
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
GloballyEditableWiki bool `json:"globally_editable_wiki,omitempty"`
|
||||
HTMLURL string `json:"html_url,omitempty"`
|
||||
HasActions bool `json:"has_actions,omitempty"`
|
||||
HasIssues bool `json:"has_issues,omitempty"`
|
||||
HasPackages bool `json:"has_packages,omitempty"`
|
||||
HasProjects bool `json:"has_projects,omitempty"`
|
||||
HasPullRequests bool `json:"has_pull_requests,omitempty"`
|
||||
HasReleases bool `json:"has_releases,omitempty"`
|
||||
HasWiki bool `json:"has_wiki,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts,omitempty"`
|
||||
Internal bool `json:"internal,omitempty"`
|
||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||
Language string `json:"language,omitempty"`
|
||||
LanguagesURL string `json:"languages_url,omitempty"`
|
||||
Link string `json:"link,omitempty"`
|
||||
Mirror bool `json:"mirror,omitempty"`
|
||||
MirrorInterval string `json:"mirror_interval,omitempty"`
|
||||
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
ObjectFormatName string `json:"object_format_name,omitempty"` // ObjectFormatName of the underlying git repository
|
||||
OpenIssues int64 `json:"open_issues_count,omitempty"`
|
||||
OpenPulls int64 `json:"open_pr_counter,omitempty"`
|
||||
OriginalURL string `json:"original_url,omitempty"`
|
||||
Owner *User `json:"owner,omitempty"`
|
||||
Parent *Repository `json:"parent,omitempty"`
|
||||
Permissions *Permission `json:"permissions,omitempty"`
|
||||
Private bool `json:"private,omitempty"`
|
||||
Releases int64 `json:"release_counter,omitempty"`
|
||||
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
|
||||
SSHURL string `json:"ssh_url,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Stars int64 `json:"stars_count,omitempty"`
|
||||
Template bool `json:"template,omitempty"`
|
||||
Topics []string `json:"topics,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Updated time.Time `json:"updated_at,omitempty"`
|
||||
Watchers int64 `json:"watchers_count,omitempty"`
|
||||
Website string `json:"website,omitempty"`
|
||||
WikiBranch string `json:"wiki_branch,omitempty"`
|
||||
}
|
||||
|
||||
// RepositoryMeta — RepositoryMeta basic repository information
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := RepositoryMeta{FullName: "example"}
|
||||
type RepositoryMeta struct {
|
||||
FullName string `json:"full_name,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Owner string `json:"owner,omitempty"`
|
||||
ID int64 `json:"id,omitempty"`
|
||||
Name string `json:"name,omitempty"`
|
||||
Owner string `json:"owner,omitempty"`
|
||||
}
|
||||
|
||||
// TransferRepoOption — TransferRepoOption options when transfer a repository's ownership
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := TransferRepoOption{NewOwner: "example"}
|
||||
type TransferRepoOption struct {
|
||||
NewOwner string `json:"new_owner"`
|
||||
TeamIDs []int64 `json:"team_ids,omitempty"` // ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories.
|
||||
NewOwner string `json:"new_owner"`
|
||||
TeamIDs []int64 `json:"team_ids,omitempty"` // ID of the team or teams to add to the repository. Teams can only be added to organization-owned repositories.
|
||||
}
|
||||
|
||||
// UpdateRepoAvatarOption — UpdateRepoAvatarUserOption options when updating the repo avatar
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := UpdateRepoAvatarOption{Image: "example"}
|
||||
type UpdateRepoAvatarOption struct {
|
||||
Image string `json:"image,omitempty"` // image must be base64 encoded
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,8 @@
|
|||
|
||||
package types
|
||||
|
||||
|
||||
// ReviewStateType — ReviewStateType review state type
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := ReviewStateType("example")
|
||||
type ReviewStateType string
|
||||
// ReviewStateType has no fields in the swagger spec.
|
||||
type ReviewStateType struct{}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,52 +2,38 @@
|
|||
|
||||
package types
|
||||
|
||||
|
||||
// GeneralAPISettings — GeneralAPISettings contains global api settings exposed by it
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GeneralAPISettings{DefaultGitTreesPerPage: 1}
|
||||
type GeneralAPISettings struct {
|
||||
DefaultGitTreesPerPage int64 `json:"default_git_trees_per_page,omitempty"`
|
||||
DefaultMaxBlobSize int64 `json:"default_max_blob_size,omitempty"`
|
||||
DefaultPagingNum int64 `json:"default_paging_num,omitempty"`
|
||||
MaxResponseItems int64 `json:"max_response_items,omitempty"`
|
||||
DefaultMaxBlobSize int64 `json:"default_max_blob_size,omitempty"`
|
||||
DefaultPagingNum int64 `json:"default_paging_num,omitempty"`
|
||||
MaxResponseItems int64 `json:"max_response_items,omitempty"`
|
||||
}
|
||||
|
||||
// GeneralAttachmentSettings — GeneralAttachmentSettings contains global Attachment settings exposed by API
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GeneralAttachmentSettings{AllowedTypes: "example"}
|
||||
type GeneralAttachmentSettings struct {
|
||||
AllowedTypes string `json:"allowed_types,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
MaxFiles int64 `json:"max_files,omitempty"`
|
||||
MaxSize int64 `json:"max_size,omitempty"`
|
||||
Enabled bool `json:"enabled,omitempty"`
|
||||
MaxFiles int64 `json:"max_files,omitempty"`
|
||||
MaxSize int64 `json:"max_size,omitempty"`
|
||||
}
|
||||
|
||||
// GeneralRepoSettings — GeneralRepoSettings contains global repository settings exposed by API
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GeneralRepoSettings{ForksDisabled: true}
|
||||
type GeneralRepoSettings struct {
|
||||
ForksDisabled bool `json:"forks_disabled,omitempty"`
|
||||
HTTPGitDisabled bool `json:"http_git_disabled,omitempty"`
|
||||
LFSDisabled bool `json:"lfs_disabled,omitempty"`
|
||||
MigrationsDisabled bool `json:"migrations_disabled,omitempty"`
|
||||
MirrorsDisabled bool `json:"mirrors_disabled,omitempty"`
|
||||
StarsDisabled bool `json:"stars_disabled,omitempty"`
|
||||
ForksDisabled bool `json:"forks_disabled,omitempty"`
|
||||
HTTPGitDisabled bool `json:"http_git_disabled,omitempty"`
|
||||
LFSDisabled bool `json:"lfs_disabled,omitempty"`
|
||||
MigrationsDisabled bool `json:"migrations_disabled,omitempty"`
|
||||
MirrorsDisabled bool `json:"mirrors_disabled,omitempty"`
|
||||
StarsDisabled bool `json:"stars_disabled,omitempty"`
|
||||
TimeTrackingDisabled bool `json:"time_tracking_disabled,omitempty"`
|
||||
}
|
||||
|
||||
// GeneralUISettings — GeneralUISettings contains global ui settings exposed by API
|
||||
//
|
||||
// Usage:
|
||||
//
|
||||
// opts := GeneralUISettings{AllowedReactions: []string{"example"}}
|
||||
type GeneralUISettings struct {
|
||||
AllowedReactions []string `json:"allowed_reactions,omitempty"`
|
||||
CustomEmojis []string `json:"custom_emojis,omitempty"`
|
||||
DefaultTheme string `json:"default_theme,omitempty"`
|
||||
CustomEmojis []string `json:"custom_emojis,omitempty"`
|
||||
DefaultTheme string `json:"default_theme,omitempty"`
|
||||
}
|
||||
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue