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`).
|
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.
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ActionsService handles CI/CD actions operations across repositories and
|
// 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.
|
// 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 {
|
type ActionsService struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
@ -28,239 +21,82 @@ func newActionsService(c *Client) *ActionsService {
|
||||||
|
|
||||||
// ListRepoSecrets returns all secrets for a repository.
|
// ListRepoSecrets returns all secrets for a repository.
|
||||||
func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string) ([]types.Secret, error) {
|
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)
|
return ListAll[types.Secret](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterRepoSecrets returns an iterator over all secrets for a repository.
|
// 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] {
|
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)
|
return ListIter[types.Secret](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRepoSecret creates or updates a secret in a repository.
|
// CreateRepoSecret creates or updates a secret in a repository.
|
||||||
// Forgejo expects a PUT with {"data": "secret-value"} body.
|
// Forgejo expects a PUT with {"data": "secret-value"} body.
|
||||||
func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error {
|
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}
|
body := map[string]string{"data": data}
|
||||||
return s.client.Put(ctx, path, body, nil)
|
return s.client.Put(ctx, path, body, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteRepoSecret removes a secret from a repository.
|
// DeleteRepoSecret removes a secret from a repository.
|
||||||
func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRepoVariables returns all action variables for a repository.
|
// ListRepoVariables returns all action variables for a repository.
|
||||||
func (s *ActionsService) ListRepoVariables(ctx context.Context, owner, repo string) ([]types.ActionVariable, error) {
|
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)
|
return ListAll[types.ActionVariable](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterRepoVariables returns an iterator over all action variables for a repository.
|
// 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] {
|
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)
|
return ListIter[types.ActionVariable](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRepoVariable creates a new action variable in a repository.
|
// CreateRepoVariable creates a new action variable in a repository.
|
||||||
// Forgejo expects a POST with {"value": "var-value"} body.
|
// Forgejo expects a POST with {"value": "var-value"} body.
|
||||||
func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error {
|
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}
|
body := types.CreateVariableOption{Value: value}
|
||||||
return s.client.Post(ctx, path, body, nil)
|
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.
|
// DeleteRepoVariable removes an action variable from a repository.
|
||||||
func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListOrgSecrets returns all secrets for an organisation.
|
// ListOrgSecrets returns all secrets for an organisation.
|
||||||
func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string) ([]types.Secret, error) {
|
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)
|
return ListAll[types.Secret](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterOrgSecrets returns an iterator over all secrets for an organisation.
|
// IterOrgSecrets returns an iterator over all secrets for an organisation.
|
||||||
func (s *ActionsService) IterOrgSecrets(ctx context.Context, org string) iter.Seq2[types.Secret, error] {
|
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)
|
return ListIter[types.Secret](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListOrgVariables returns all action variables for an organisation.
|
// ListOrgVariables returns all action variables for an organisation.
|
||||||
func (s *ActionsService) ListOrgVariables(ctx context.Context, org string) ([]types.ActionVariable, error) {
|
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)
|
return ListAll[types.ActionVariable](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterOrgVariables returns an iterator over all action variables for an organisation.
|
// 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] {
|
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)
|
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.
|
// DispatchWorkflow triggers a workflow run.
|
||||||
func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error {
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
t.Errorf("expected PUT, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
func TestActionsService_Good_DeleteRepoVariable(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestActionsService_Good_DispatchWorkflow(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
func TestActionsService_Bad_NotFound(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"iter"
|
"iter"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AdminService handles site administration operations.
|
// AdminService handles site administration operations.
|
||||||
// Unlike other services, AdminService does not embed Resource[T,C,U]
|
// Unlike other services, AdminService does not embed Resource[T,C,U]
|
||||||
// because admin endpoints are heterogeneous.
|
// because admin endpoints are heterogeneous.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
|
||||||
// _, err := f.Admin.ListUsers(ctx)
|
|
||||||
type AdminService struct {
|
type AdminService struct {
|
||||||
client *Client
|
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 {
|
func newAdminService(c *Client) *AdminService {
|
||||||
return &AdminService{client: c}
|
return &AdminService{client: c}
|
||||||
}
|
}
|
||||||
|
|
@ -120,58 +37,6 @@ func (s *AdminService) CreateUser(ctx context.Context, opts *types.CreateUserOpt
|
||||||
return &out, nil
|
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).
|
// EditUser edits an existing user (admin only).
|
||||||
func (s *AdminService) EditUser(ctx context.Context, username string, opts map[string]any) error {
|
func (s *AdminService) EditUser(ctx context.Context, username string, opts map[string]any) error {
|
||||||
path := ResolvePath("/api/v1/admin/users/{username}", Params{"username": username})
|
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)
|
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).
|
// RunCron runs a cron task by name (admin only).
|
||||||
func (s *AdminService) RunCron(ctx context.Context, task string) error {
|
func (s *AdminService) RunCron(ctx context.Context, task string) error {
|
||||||
path := ResolvePath("/api/v1/admin/cron/{task}", Params{"task": task})
|
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)
|
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).
|
// AdoptRepo adopts an unadopted repository (admin only).
|
||||||
func (s *AdminService) AdoptRepo(ctx context.Context, owner, repo string) error {
|
func (s *AdminService) AdoptRepo(ctx context.Context, owner, repo string) error {
|
||||||
path := ResolvePath("/api/v1/admin/unadopted/{owner}/{repo}", Params{"owner": owner, "repo": repo})
|
path := ResolvePath("/api/v1/admin/unadopted/{owner}/{repo}", Params{"owner": owner, "repo": repo})
|
||||||
return s.client.Post(ctx, path, nil, nil)
|
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.
|
// GenerateRunnerToken generates an actions runner registration token.
|
||||||
func (s *AdminService) GenerateRunnerToken(ctx context.Context) (string, error) {
|
func (s *AdminService) GenerateRunnerToken(ctx context.Context) (string, error) {
|
||||||
var out struct {
|
var out struct {
|
||||||
|
|
|
||||||
798
admin_test.go
798
admin_test.go
|
|
@ -2,7 +2,7 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPatch {
|
if r.Method != http.MethodPatch {
|
||||||
t.Errorf("expected PATCH, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "user not found"})
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "only admins can create users"})
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BranchService handles branch operations within a repository.
|
// 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 {
|
type BranchService struct {
|
||||||
Resource[types.Branch, types.CreateBranchRepoOption, types.UpdateBranchRepoOption]
|
Resource[types.Branch, types.CreateBranchRepoOption, struct{}]
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBranchService(c *Client) *BranchService {
|
func newBranchService(c *Client) *BranchService {
|
||||||
return &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}",
|
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.
|
// ListBranchProtections returns all branch protections for a repository.
|
||||||
func (s *BranchService) ListBranchProtections(ctx context.Context, owner, repo string) ([]types.BranchProtection, error) {
|
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)
|
return ListAll[types.BranchProtection](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterBranchProtections returns an iterator over all branch protections for a repository.
|
// 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] {
|
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)
|
return ListIter[types.BranchProtection](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBranchProtection returns a single branch protection by name.
|
// GetBranchProtection returns a single branch protection by name.
|
||||||
func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, name string) (*types.BranchProtection, error) {
|
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
|
var out types.BranchProtection
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -92,7 +45,7 @@ func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, na
|
||||||
|
|
||||||
// CreateBranchProtection creates a new branch protection rule.
|
// CreateBranchProtection creates a new branch protection rule.
|
||||||
func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo string, opts *types.CreateBranchProtectionOption) (*types.BranchProtection, error) {
|
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
|
var out types.BranchProtection
|
||||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -102,7 +55,7 @@ func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo
|
||||||
|
|
||||||
// EditBranchProtection updates an existing branch protection rule.
|
// EditBranchProtection updates an existing branch protection rule.
|
||||||
func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, name string, opts *types.EditBranchProtectionOption) (*types.BranchProtection, error) {
|
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
|
var out types.BranchProtection
|
||||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -112,6 +65,6 @@ func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, n
|
||||||
|
|
||||||
// DeleteBranchProtection deletes a branch protection rule.
|
// DeleteBranchProtection deletes a branch protection rule.
|
||||||
func (s *BranchService) DeleteBranchProtection(ctx context.Context, owner, repo, name string) error {
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestBranchService_Good_CreateProtection(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
t.Errorf("expected POST, got %s", r.Method)
|
||||||
|
|
|
||||||
376
client.go
376
client.go
|
|
@ -3,160 +3,67 @@ package forge
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"mime/multipart"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
goio "io"
|
coreerr "dappco.re/go/core/log"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIError represents an error response from the Forgejo API.
|
// APIError represents an error response from the Forgejo API.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// if apiErr, ok := err.(*forge.APIError); ok {
|
|
||||||
// _ = apiErr.StatusCode
|
|
||||||
// }
|
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
StatusCode int
|
StatusCode int
|
||||||
Message string
|
Message string
|
||||||
URL 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 {
|
func (e *APIError) Error() string {
|
||||||
if e == nil {
|
return fmt.Sprintf("forge: %s %d: %s", e.URL, e.StatusCode, e.Message)
|
||||||
return "forge.APIError{<nil>}"
|
|
||||||
}
|
|
||||||
return core.Concat("forge: ", e.URL, " ", strconv.Itoa(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.
|
// IsNotFound returns true if the error is a 404 response.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// if forge.IsNotFound(err) {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
func IsNotFound(err error) bool {
|
func IsNotFound(err error) bool {
|
||||||
var apiErr *APIError
|
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.
|
// IsForbidden returns true if the error is a 403 response.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// if forge.IsForbidden(err) {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
func IsForbidden(err error) bool {
|
func IsForbidden(err error) bool {
|
||||||
var apiErr *APIError
|
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.
|
// IsConflict returns true if the error is a 409 response.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// if forge.IsConflict(err) {
|
|
||||||
// return nil
|
|
||||||
// }
|
|
||||||
func IsConflict(err error) bool {
|
func IsConflict(err error) bool {
|
||||||
var apiErr *APIError
|
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.
|
// Option configures the Client.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := []forge.Option{forge.WithUserAgent("go-forge/1.0")}
|
|
||||||
type Option func(*Client)
|
type Option func(*Client)
|
||||||
|
|
||||||
// WithHTTPClient sets a custom http.Client.
|
// WithHTTPClient sets a custom http.Client.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// c := forge.NewClient(url, token, forge.WithHTTPClient(http.DefaultClient))
|
|
||||||
func WithHTTPClient(hc *http.Client) Option {
|
func WithHTTPClient(hc *http.Client) Option {
|
||||||
return func(c *Client) { c.httpClient = hc }
|
return func(c *Client) { c.httpClient = hc }
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithUserAgent sets the User-Agent header.
|
// WithUserAgent sets the User-Agent header.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// c := forge.NewClient(url, token, forge.WithUserAgent("go-forge/1.0"))
|
|
||||||
func WithUserAgent(ua string) Option {
|
func WithUserAgent(ua string) Option {
|
||||||
return func(c *Client) { c.userAgent = ua }
|
return func(c *Client) { c.userAgent = ua }
|
||||||
}
|
}
|
||||||
|
|
||||||
// RateLimit represents the rate limit information from the Forgejo API.
|
// RateLimit represents the rate limit information from the Forgejo API.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// rl := client.RateLimit()
|
|
||||||
// _ = rl.Remaining
|
|
||||||
type RateLimit struct {
|
type RateLimit struct {
|
||||||
Limit int
|
Limit int
|
||||||
Remaining int
|
Remaining int
|
||||||
Reset int64
|
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.
|
// Client is a low-level HTTP client for the Forgejo API.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// c := forge.NewClient("https://forge.lthn.ai", "token")
|
|
||||||
// _ = c
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
baseURL string
|
baseURL string
|
||||||
token string
|
token string
|
||||||
|
|
@ -165,100 +72,15 @@ type Client struct {
|
||||||
rateLimit RateLimit
|
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.
|
// RateLimit returns the last known rate limit information.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// rl := client.RateLimit()
|
|
||||||
func (c *Client) RateLimit() RateLimit {
|
func (c *Client) RateLimit() RateLimit {
|
||||||
if c == nil {
|
|
||||||
return RateLimit{}
|
|
||||||
}
|
|
||||||
return c.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.
|
// 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 {
|
func NewClient(url, token string, opts ...Option) *Client {
|
||||||
c := &Client{
|
c := &Client{
|
||||||
baseURL: trimTrailingSlashes(url),
|
baseURL: strings.TrimRight(url, "/"),
|
||||||
token: token,
|
token: token,
|
||||||
httpClient: &http.Client{
|
httpClient: &http.Client{
|
||||||
CheckRedirect: func(req *http.Request, via []*http.Request) error {
|
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.
|
// 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 {
|
func (c *Client) Get(ctx context.Context, path string, out any) error {
|
||||||
_, err := c.doJSON(ctx, http.MethodGet, path, nil, out)
|
_, err := c.doJSON(ctx, http.MethodGet, path, nil, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Post performs a POST request.
|
// 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 {
|
func (c *Client) Post(ctx context.Context, path string, body, out any) error {
|
||||||
_, err := c.doJSON(ctx, http.MethodPost, path, body, out)
|
_, err := c.doJSON(ctx, http.MethodPost, path, body, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Patch performs a PATCH request.
|
// 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 {
|
func (c *Client) Patch(ctx context.Context, path string, body, out any) error {
|
||||||
_, err := c.doJSON(ctx, http.MethodPatch, path, body, out)
|
_, err := c.doJSON(ctx, http.MethodPatch, path, body, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Put performs a PUT request.
|
// 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 {
|
func (c *Client) Put(ctx context.Context, path string, body, out any) error {
|
||||||
_, err := c.doJSON(ctx, http.MethodPut, path, body, out)
|
_, err := c.doJSON(ctx, http.MethodPut, path, body, out)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete performs a DELETE request.
|
// 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 {
|
func (c *Client) Delete(ctx context.Context, path string) error {
|
||||||
_, err := c.doJSON(ctx, http.MethodDelete, path, nil, nil)
|
_, err := c.doJSON(ctx, http.MethodDelete, path, nil, nil)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteWithBody performs a DELETE request with a JSON body.
|
// 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 {
|
func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) error {
|
||||||
_, err := c.doJSON(ctx, http.MethodDelete, path, body, nil)
|
_, err := c.doJSON(ctx, http.MethodDelete, path, body, nil)
|
||||||
return err
|
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
|
// PostRaw performs a POST request with a JSON body and returns the raw
|
||||||
// response body as bytes instead of JSON-decoding. Useful for endpoints
|
// response body as bytes instead of JSON-decoding. Useful for endpoints
|
||||||
// such as /markdown that return raw HTML text.
|
// 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) {
|
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
|
url := c.baseURL + path
|
||||||
|
|
||||||
var bodyReader goio.Reader
|
var bodyReader io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
data, err := json.Marshal(body)
|
data, err := json.Marshal(body)
|
||||||
if err != nil {
|
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)
|
bodyReader = bytes.NewReader(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader)
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader)
|
||||||
if err != nil {
|
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)
|
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)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
c.updateRateLimit(resp)
|
|
||||||
|
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode >= 400 {
|
||||||
return nil, c.parseError(resp, path)
|
return nil, c.parseError(resp, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := goio.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
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
|
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
|
// 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.
|
// 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) {
|
func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) {
|
||||||
url := c.baseURL + path
|
url := c.baseURL + path
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||||
if err != 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)
|
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)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
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()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
c.updateRateLimit(resp)
|
|
||||||
|
|
||||||
if resp.StatusCode >= 400 {
|
if resp.StatusCode >= 400 {
|
||||||
return nil, c.parseError(resp, path)
|
return nil, c.parseError(resp, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := goio.ReadAll(resp.Body)
|
data, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
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
|
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) {
|
func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) (*http.Response, error) {
|
||||||
url := c.baseURL + path
|
url := c.baseURL + path
|
||||||
|
|
||||||
var bodyReader goio.Reader
|
var bodyReader io.Reader
|
||||||
if body != nil {
|
if body != nil {
|
||||||
data, err := json.Marshal(body)
|
data, err := json.Marshal(body)
|
||||||
if err != nil {
|
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)
|
bodyReader = bytes.NewReader(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
|
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
|
||||||
if err != nil {
|
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)
|
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)
|
resp, err := c.httpClient.Do(req)
|
||||||
if err != nil {
|
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()
|
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 out != nil && resp.StatusCode != http.StatusNoContent {
|
||||||
if err := json.NewDecoder(resp.Body).Decode(out); err != nil {
|
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
|
// 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)
|
_ = json.Unmarshal(data, &errBody)
|
||||||
|
|
||||||
msg := errBody.Message
|
msg := errBody.Message
|
||||||
|
|
|
||||||
160
client_test.go
160
client_test.go
|
|
@ -2,16 +2,14 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"encoding/json"
|
||||||
json "github.com/goccy/go-json"
|
"errors"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
func TestClient_Good_Delete(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
func TestClient_Bad_ServerError(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "internal error"})
|
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")
|
t.Fatal("expected error")
|
||||||
}
|
}
|
||||||
var apiErr *APIError
|
var apiErr *APIError
|
||||||
if !core.As(err, &apiErr) {
|
if !errors.As(err, &apiErr) {
|
||||||
t.Fatalf("expected APIError, got %T", err)
|
t.Fatalf("expected APIError, got %T", err)
|
||||||
}
|
}
|
||||||
if apiErr.StatusCode != 500 {
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
<-r.Context().Done()
|
<-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",
|
c := NewClient("https://forge.lthn.ai", "tok",
|
||||||
WithUserAgent("go-forge/1.0"),
|
WithUserAgent("go-forge/1.0"),
|
||||||
)
|
)
|
||||||
if c.userAgent != "go-forge/1.0" {
|
if c.userAgent != "go-forge/1.0" {
|
||||||
t.Errorf("got user agent=%q", c.userAgent)
|
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) {
|
func TestClient_Good_WithHTTPClient(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) {
|
|
||||||
custom := &http.Client{}
|
custom := &http.Client{}
|
||||||
c := NewClient("https://forge.lthn.ai", "tok", WithHTTPClient(custom))
|
c := NewClient("https://forge.lthn.ai", "tok", WithHTTPClient(custom))
|
||||||
if c.httpClient != custom {
|
if c.httpClient != custom {
|
||||||
t.Error("expected custom HTTP client to be set")
|
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) {
|
func TestAPIError_Good_Error(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) {
|
|
||||||
e := &APIError{StatusCode: 404, Message: "not found", URL: "/api/v1/repos/x/y"}
|
e := &APIError{StatusCode: 404, Message: "not found", URL: "/api/v1/repos/x/y"}
|
||||||
got := e.Error()
|
got := e.Error()
|
||||||
want := "forge: /api/v1/repos/x/y 404: not found"
|
want := "forge: /api/v1/repos/x/y 404: not found"
|
||||||
if got != want {
|
if got != want {
|
||||||
t.Errorf("got %q, want %q", 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"}
|
err := &APIError{StatusCode: http.StatusConflict, Message: "conflict", URL: "/test"}
|
||||||
if !IsConflict(err) {
|
if !IsConflict(err) {
|
||||||
t.Error("expected IsConflict to return true for 409")
|
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"}
|
err := &APIError{StatusCode: http.StatusNotFound, Message: "not found", URL: "/test"}
|
||||||
if IsConflict(err) {
|
if IsConflict(err) {
|
||||||
t.Error("expected IsConflict to return false for 404")
|
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"}
|
err := &APIError{StatusCode: http.StatusNotFound, Message: "not found", URL: "/test"}
|
||||||
if IsForbidden(err) {
|
if IsForbidden(err) {
|
||||||
t.Error("expected IsForbidden to return false for 404")
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-RateLimit-Limit", "100")
|
w.Header().Set("X-RateLimit-Limit", "100")
|
||||||
w.Header().Set("X-RateLimit-Remaining", "99")
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusForbidden)
|
w.WriteHeader(http.StatusForbidden)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "forbidden"})
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusConflict)
|
w.WriteHeader(http.StatusConflict)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "already exists"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "already exists"})
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,14 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"cmp"
|
|
||||||
"maps"
|
"maps"
|
||||||
|
"path/filepath"
|
||||||
"slices"
|
"slices"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
coreio "dappco.re/go/core/io"
|
coreio "dappco.re/go/core/io"
|
||||||
|
coreerr "dappco.re/go/core/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// typeGrouping maps type name prefixes to output file names.
|
// typeGrouping maps type name prefixes to output file names.
|
||||||
|
|
@ -111,7 +110,7 @@ func classifyType(name string) string {
|
||||||
bestKey := ""
|
bestKey := ""
|
||||||
bestGroup := ""
|
bestGroup := ""
|
||||||
for key, group := range typeGrouping {
|
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
|
bestKey = key
|
||||||
bestGroup = group
|
bestGroup = group
|
||||||
}
|
}
|
||||||
|
|
@ -123,10 +122,10 @@ func classifyType(name string) string {
|
||||||
// Strip CRUD prefixes and Option suffix, then retry.
|
// Strip CRUD prefixes and Option suffix, then retry.
|
||||||
base := name
|
base := name
|
||||||
for _, prefix := range []string{"Create", "Edit", "Delete", "Update", "Add", "Submit", "Replace", "Set", "Transfer"} {
|
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 = strings.TrimSuffix(base, "Option")
|
||||||
base = core.TrimSuffix(base, "Options")
|
base = strings.TrimSuffix(base, "Options")
|
||||||
|
|
||||||
if base != name && base != "" {
|
if base != name && base != "" {
|
||||||
if group, ok := typeGrouping[base]; ok {
|
if group, ok := typeGrouping[base]; ok {
|
||||||
|
|
@ -136,7 +135,7 @@ func classifyType(name string) string {
|
||||||
bestKey = ""
|
bestKey = ""
|
||||||
bestGroup = ""
|
bestGroup = ""
|
||||||
for key, group := range typeGrouping {
|
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
|
bestKey = key
|
||||||
bestGroup = group
|
bestGroup = group
|
||||||
}
|
}
|
||||||
|
|
@ -152,7 +151,7 @@ func classifyType(name string) string {
|
||||||
// sanitiseLine collapses a multi-line string into a single line,
|
// sanitiseLine collapses a multi-line string into a single line,
|
||||||
// replacing newlines and consecutive whitespace with a single space.
|
// replacing newlines and consecutive whitespace with a single space.
|
||||||
func sanitiseLine(s string) string {
|
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.
|
// enumConstName generates a Go constant name for an enum value.
|
||||||
|
|
@ -177,12 +176,6 @@ import "time"
|
||||||
{{- if .Description}}
|
{{- if .Description}}
|
||||||
// {{.Name}} — {{sanitise .Description}}
|
// {{.Name}} — {{sanitise .Description}}
|
||||||
{{- end}}
|
{{- end}}
|
||||||
{{- if .Usage}}
|
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := {{.Usage}}
|
|
||||||
{{- end}}
|
|
||||||
{{- if .IsEnum}}
|
{{- if .IsEnum}}
|
||||||
type {{.Name}} string
|
type {{.Name}} string
|
||||||
|
|
||||||
|
|
@ -191,8 +184,6 @@ const (
|
||||||
{{enumConstName $t.Name .}} {{$t.Name}} = "{{.}}"
|
{{enumConstName $t.Name .}} {{$t.Name}} = "{{.}}"
|
||||||
{{- end}}
|
{{- end}}
|
||||||
)
|
)
|
||||||
{{- else if .IsAlias}}
|
|
||||||
type {{.Name}} {{.AliasType}}
|
|
||||||
{{- else if (eq (len .Fields) 0)}}
|
{{- else if (eq (len .Fields) 0)}}
|
||||||
// {{.Name}} has no fields in the swagger spec.
|
// {{.Name}} has no fields in the swagger spec.
|
||||||
type {{.Name}} struct{}
|
type {{.Name}} struct{}
|
||||||
|
|
@ -213,18 +204,11 @@ type templateData struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate writes Go source files for the extracted types, grouped by logical domain.
|
// 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 {
|
func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error {
|
||||||
if err := coreio.Local.EnsureDir(outDir); err != nil {
|
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.
|
// Group types by output file.
|
||||||
groups := make(map[string][]*GoType)
|
groups := make(map[string][]*GoType)
|
||||||
for _, gt := range types {
|
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.
|
// Sort types within each group for deterministic output.
|
||||||
for file := range groups {
|
for file := range groups {
|
||||||
slices.SortFunc(groups[file], func(a, b *GoType) int {
|
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)
|
slices.Sort(fileNames)
|
||||||
|
|
||||||
for _, file := range 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 {
|
if err := writeFile(outPath, groups[file]); err != nil {
|
||||||
return core.E("Generate", "write "+outPath, err)
|
return coreerr.E("Generate", "write "+outPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
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.
|
// writeFile renders and writes a single Go source file for the given types.
|
||||||
func writeFile(path string, types []*GoType) error {
|
func writeFile(path string, types []*GoType) error {
|
||||||
needTime := slices.ContainsFunc(types, func(gt *GoType) bool {
|
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 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
|
var buf bytes.Buffer
|
||||||
if err := fileHeader.Execute(&buf, data); err != nil {
|
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, buf.String()); err != nil {
|
||||||
if err := coreio.Local.Write(path, content); err != nil {
|
return coreerr.E("writeFile", "write file", err)
|
||||||
return core.E("writeFile", "write file", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
coreio "dappco.re/go/core/io"
|
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")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -21,10 +23,10 @@ func TestGenerate_CreatesFiles_Good(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, _ := coreio.Local.List(outDir)
|
entries, _ := os.ReadDir(outDir)
|
||||||
goFiles := 0
|
goFiles := 0
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if core.HasSuffix(e.Name(), ".go") {
|
if strings.HasSuffix(e.Name(), ".go") {
|
||||||
goFiles++
|
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")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -47,11 +49,11 @@ func TestGenerate_ValidGoSyntax_Good(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, _ := coreio.Local.List(outDir)
|
entries, _ := os.ReadDir(outDir)
|
||||||
var content string
|
var content string
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
if core.HasSuffix(e.Name(), ".go") {
|
if strings.HasSuffix(e.Name(), ".go") {
|
||||||
content, err = coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
content, err = coreio.Local.Read(filepath.Join(outDir, e.Name()))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -60,15 +62,15 @@ func TestGenerate_ValidGoSyntax_Good(t *testing.T) {
|
||||||
if err != nil || content == "" {
|
if err != nil || content == "" {
|
||||||
t.Fatal("could not read any generated file")
|
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")
|
t.Error("missing package declaration")
|
||||||
}
|
}
|
||||||
if !core.Contains(content, "// Code generated") {
|
if !strings.Contains(content, "// Code generated") {
|
||||||
t.Error("missing generated comment")
|
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")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -83,10 +85,10 @@ func TestGenerate_RepositoryType_Good(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var content string
|
var content string
|
||||||
entries, _ := coreio.Local.List(outDir)
|
entries, _ := os.ReadDir(outDir)
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
data, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
data, _ := coreio.Local.Read(filepath.Join(outDir, e.Name()))
|
||||||
if core.Contains(data, "type Repository struct") {
|
if strings.Contains(data, "type Repository struct") {
|
||||||
content = data
|
content = data
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
@ -105,13 +107,13 @@ func TestGenerate_RepositoryType_Good(t *testing.T) {
|
||||||
"`json:\"private,omitempty\"`",
|
"`json:\"private,omitempty\"`",
|
||||||
}
|
}
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
if !core.Contains(content, check) {
|
if !strings.Contains(content, check) {
|
||||||
t.Errorf("missing field with tag %s", 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")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -125,131 +127,11 @@ func TestGenerate_TimeImport_Good(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
entries, _ := coreio.Local.List(outDir)
|
entries, _ := os.ReadDir(outDir)
|
||||||
for _, e := range entries {
|
for _, e := range entries {
|
||||||
content, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name()))
|
content, _ := coreio.Local.Read(filepath.Join(outDir, e.Name()))
|
||||||
if core.Contains(content, "time.Time") && !core.Contains(content, "\"time\"") {
|
if strings.Contains(content, "time.Time") && !strings.Contains(content, "\"time\"") {
|
||||||
t.Errorf("file %s uses time.Time but doesn't import time", e.Name())
|
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 (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -12,26 +11,20 @@ func main() {
|
||||||
outDir := flag.String("out", "types", "output directory for generated types")
|
outDir := flag.String("out", "types", "output directory for generated types")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
if err := run(*specPath, *outDir); err != nil {
|
spec, err := LoadSpec(*specPath)
|
||||||
core.Print(os.Stderr, "forgegen: %v", err)
|
|
||||||
os.Exit(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func run(specPath, outDir string) error {
|
|
||||||
spec, err := LoadSpec(specPath)
|
|
||||||
if err != nil {
|
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)
|
types := ExtractTypes(spec)
|
||||||
pairs := DetectCRUDPairs(spec)
|
pairs := DetectCRUDPairs(spec)
|
||||||
|
|
||||||
core.Print(nil, "Loaded %d types, %d CRUD pairs", len(types), len(pairs))
|
fmt.Printf("Loaded %d types, %d CRUD pairs\n", len(types), len(pairs))
|
||||||
core.Print(nil, "Output dir: %s", outDir)
|
fmt.Printf("Output dir: %s\n", *outDir)
|
||||||
|
|
||||||
if err := Generate(types, pairs, outDir); err != nil {
|
if err := Generate(types, pairs, *outDir); err != nil {
|
||||||
return core.E("forgegen.main", "generate types", err)
|
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
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"cmp"
|
"encoding/json"
|
||||||
json "github.com/goccy/go-json"
|
"fmt"
|
||||||
"slices"
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
coreio "dappco.re/go/core/io"
|
coreio "dappco.re/go/core/io"
|
||||||
|
coreerr "dappco.re/go/core/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Spec represents a Swagger 2.0 specification document.
|
// Spec represents a Swagger 2.0 specification document.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// spec, err := LoadSpec("testdata/swagger.v1.json")
|
|
||||||
// _ = spec
|
|
||||||
type Spec struct {
|
type Spec struct {
|
||||||
Swagger string `json:"swagger"`
|
Swagger string `json:"swagger"`
|
||||||
Info SpecInfo `json:"info"`
|
Info SpecInfo `json:"info"`
|
||||||
|
|
@ -23,70 +19,42 @@ type Spec struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SpecInfo holds metadata about the API specification.
|
// SpecInfo holds metadata about the API specification.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// _ = SpecInfo{Title: "Forgejo API", Version: "1.0"}
|
|
||||||
type SpecInfo struct {
|
type SpecInfo struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaDefinition represents a single type definition in the swagger spec.
|
// SchemaDefinition represents a single type definition in the swagger spec.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// _ = SchemaDefinition{Type: "object"}
|
|
||||||
type SchemaDefinition struct {
|
type SchemaDefinition struct {
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Format string `json:"format"`
|
Type string `json:"type"`
|
||||||
Ref string `json:"$ref"`
|
Properties map[string]SchemaProperty `json:"properties"`
|
||||||
Items *SchemaProperty `json:"items"`
|
Required []string `json:"required"`
|
||||||
Type string `json:"type"`
|
Enum []any `json:"enum"`
|
||||||
Properties map[string]SchemaProperty `json:"properties"`
|
XGoName string `json:"x-go-name"`
|
||||||
Required []string `json:"required"`
|
|
||||||
Enum []any `json:"enum"`
|
|
||||||
AdditionalProperties *SchemaProperty `json:"additionalProperties"`
|
|
||||||
XGoName string `json:"x-go-name"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemaProperty represents a single property within a schema definition.
|
// SchemaProperty represents a single property within a schema definition.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// _ = SchemaProperty{Type: "string"}
|
|
||||||
type SchemaProperty struct {
|
type SchemaProperty struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Format string `json:"format"`
|
Format string `json:"format"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Ref string `json:"$ref"`
|
Ref string `json:"$ref"`
|
||||||
Items *SchemaProperty `json:"items"`
|
Items *SchemaProperty `json:"items"`
|
||||||
Enum []any `json:"enum"`
|
Enum []any `json:"enum"`
|
||||||
AdditionalProperties *SchemaProperty `json:"additionalProperties"`
|
XGoName string `json:"x-go-name"`
|
||||||
XGoName string `json:"x-go-name"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoType is the intermediate representation for a Go type to be generated.
|
// GoType is the intermediate representation for a Go type to be generated.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// _ = GoType{Name: "Repository"}
|
|
||||||
type GoType struct {
|
type GoType struct {
|
||||||
Name string
|
Name string
|
||||||
Description string
|
Description string
|
||||||
Usage string
|
|
||||||
Fields []GoField
|
Fields []GoField
|
||||||
IsEnum bool
|
IsEnum bool
|
||||||
EnumValues []string
|
EnumValues []string
|
||||||
IsAlias bool
|
|
||||||
AliasType string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GoField is the intermediate representation for a single struct field.
|
// GoField is the intermediate representation for a single struct field.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// _ = GoField{GoName: "ID", GoType: "int64"}
|
|
||||||
type GoField struct {
|
type GoField struct {
|
||||||
GoName string
|
GoName string
|
||||||
GoType string
|
GoType string
|
||||||
|
|
@ -96,10 +64,6 @@ type GoField struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CRUDPair groups a base type with its corresponding Create and Edit option types.
|
// CRUDPair groups a base type with its corresponding Create and Edit option types.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// _ = CRUDPair{Base: "Repository", Create: "CreateRepoOption", Edit: "EditRepoOption"}
|
|
||||||
type CRUDPair struct {
|
type CRUDPair struct {
|
||||||
Base string
|
Base string
|
||||||
Create string
|
Create string
|
||||||
|
|
@ -107,29 +71,19 @@ type CRUDPair struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadSpec reads and parses a Swagger 2.0 JSON file from the given path.
|
// 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) {
|
func LoadSpec(path string) (*Spec, error) {
|
||||||
content, err := coreio.Local.Read(path)
|
content, err := coreio.Local.Read(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, core.E("LoadSpec", "read spec", err)
|
return nil, coreerr.E("LoadSpec", "read spec", err)
|
||||||
}
|
}
|
||||||
var spec Spec
|
var spec Spec
|
||||||
if err := json.Unmarshal([]byte(content), &spec); err != nil {
|
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
|
return &spec, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExtractTypes converts all swagger definitions into Go type intermediate representations.
|
// ExtractTypes converts all swagger definitions into Go type intermediate representations.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// types := ExtractTypes(spec)
|
|
||||||
// _ = types["Repository"]
|
|
||||||
func ExtractTypes(spec *Spec) map[string]*GoType {
|
func ExtractTypes(spec *Spec) map[string]*GoType {
|
||||||
result := make(map[string]*GoType)
|
result := make(map[string]*GoType)
|
||||||
for name, def := range spec.Definitions {
|
for name, def := range spec.Definitions {
|
||||||
|
|
@ -137,19 +91,12 @@ func ExtractTypes(spec *Spec) map[string]*GoType {
|
||||||
if len(def.Enum) > 0 {
|
if len(def.Enum) > 0 {
|
||||||
gt.IsEnum = true
|
gt.IsEnum = true
|
||||||
for _, v := range def.Enum {
|
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)
|
slices.Sort(gt.EnumValues)
|
||||||
result[name] = gt
|
result[name] = gt
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if aliasType, ok := definitionAliasType(def, spec.Definitions); ok {
|
|
||||||
gt.IsAlias = true
|
|
||||||
gt.AliasType = aliasType
|
|
||||||
result[name] = gt
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
required := make(map[string]bool)
|
required := make(map[string]bool)
|
||||||
for _, r := range def.Required {
|
for _, r := range def.Required {
|
||||||
required[r] = true
|
required[r] = true
|
||||||
|
|
@ -161,7 +108,7 @@ func ExtractTypes(spec *Spec) map[string]*GoType {
|
||||||
}
|
}
|
||||||
gf := GoField{
|
gf := GoField{
|
||||||
GoName: goName,
|
GoName: goName,
|
||||||
GoType: resolveGoType(prop, spec.Definitions),
|
GoType: resolveGoType(prop),
|
||||||
JSONName: fieldName,
|
JSONName: fieldName,
|
||||||
Comment: prop.Description,
|
Comment: prop.Description,
|
||||||
Required: required[fieldName],
|
Required: required[fieldName],
|
||||||
|
|
@ -169,69 +116,24 @@ func ExtractTypes(spec *Spec) map[string]*GoType {
|
||||||
gt.Fields = append(gt.Fields, gf)
|
gt.Fields = append(gt.Fields, gf)
|
||||||
}
|
}
|
||||||
slices.SortFunc(gt.Fields, func(a, b GoField) int {
|
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
|
result[name] = gt
|
||||||
}
|
}
|
||||||
return result
|
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
|
// DetectCRUDPairs finds Create*Option / Edit*Option pairs in the swagger definitions
|
||||||
// and maps them back to the base type name.
|
// and maps them back to the base type name.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// pairs := DetectCRUDPairs(spec)
|
|
||||||
// _ = pairs
|
|
||||||
func DetectCRUDPairs(spec *Spec) []CRUDPair {
|
func DetectCRUDPairs(spec *Spec) []CRUDPair {
|
||||||
var pairs []CRUDPair
|
var pairs []CRUDPair
|
||||||
for name := range spec.Definitions {
|
for name := range spec.Definitions {
|
||||||
if !core.HasPrefix(name, "Create") || !core.HasSuffix(name, "Option") {
|
if !strings.HasPrefix(name, "Create") || !strings.HasSuffix(name, "Option") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
inner := core.TrimPrefix(name, "Create")
|
inner := strings.TrimPrefix(name, "Create")
|
||||||
inner = core.TrimSuffix(inner, "Option")
|
inner = strings.TrimSuffix(inner, "Option")
|
||||||
editName := core.Concat("Edit", inner, "Option")
|
editName := "Edit" + inner + "Option"
|
||||||
pair := CRUDPair{Base: inner, Create: name}
|
pair := CRUDPair{Base: inner, Create: name}
|
||||||
if _, ok := spec.Definitions[editName]; ok {
|
if _, ok := spec.Definitions[editName]; ok {
|
||||||
pair.Edit = editName
|
pair.Edit = editName
|
||||||
|
|
@ -239,15 +141,16 @@ func DetectCRUDPairs(spec *Spec) []CRUDPair {
|
||||||
pairs = append(pairs, pair)
|
pairs = append(pairs, pair)
|
||||||
}
|
}
|
||||||
slices.SortFunc(pairs, func(a, b CRUDPair) int {
|
slices.SortFunc(pairs, func(a, b CRUDPair) int {
|
||||||
return cmp.Compare(a.Base, b.Base)
|
return strings.Compare(a.Base, b.Base)
|
||||||
})
|
})
|
||||||
return pairs
|
return pairs
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveGoType maps a swagger schema property to a Go type string.
|
// 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 != "" {
|
if prop.Ref != "" {
|
||||||
return refGoType(prop.Ref, defs)
|
parts := strings.Split(prop.Ref, "/")
|
||||||
|
return "*" + parts[len(parts)-1]
|
||||||
}
|
}
|
||||||
switch prop.Type {
|
switch prop.Type {
|
||||||
case "string":
|
case "string":
|
||||||
|
|
@ -279,74 +182,33 @@ func resolveGoType(prop SchemaProperty, defs map[string]SchemaDefinition) string
|
||||||
return "bool"
|
return "bool"
|
||||||
case "array":
|
case "array":
|
||||||
if prop.Items != nil {
|
if prop.Items != nil {
|
||||||
return "[]" + resolveGoType(*prop.Items, defs)
|
return "[]" + resolveGoType(*prop.Items)
|
||||||
}
|
}
|
||||||
return "[]any"
|
return "[]any"
|
||||||
case "object":
|
case "object":
|
||||||
return resolveMapType(prop, defs)
|
return "map[string]any"
|
||||||
default:
|
default:
|
||||||
return "any"
|
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,
|
// pascalCase converts a snake_case or kebab-case string to PascalCase,
|
||||||
// with common acronyms kept uppercase.
|
// with common acronyms kept uppercase.
|
||||||
func pascalCase(s string) string {
|
func pascalCase(s string) string {
|
||||||
var parts []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 {
|
if len(p) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
upper := core.Upper(p)
|
upper := strings.ToUpper(p)
|
||||||
switch upper {
|
switch upper {
|
||||||
case "ID", "URL", "HTML", "SSH", "HTTP", "HTTPS", "API", "URI", "GPG", "IP", "CSS", "JS":
|
case "ID", "URL", "HTML", "SSH", "HTTP", "HTTPS", "API", "URI", "GPG", "IP", "CSS", "JS":
|
||||||
parts = append(parts, upper)
|
parts = append(parts, upper)
|
||||||
default:
|
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"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestParser_LoadSpec_Good(t *testing.T) {
|
func TestParser_Good_LoadSpec(t *testing.T) {
|
||||||
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -70,15 +70,11 @@ func TestParser_FieldTypes_Good(t *testing.T) {
|
||||||
if f.GoType != "*User" {
|
if f.GoType != "*User" {
|
||||||
t.Errorf("owner: got %q, want *User", f.GoType)
|
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")
|
spec, err := LoadSpec("../../testdata/swagger.v1.json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|
@ -105,65 +101,3 @@ func TestParser_DetectCreateEditPairs_Good(t *testing.T) {
|
||||||
t.Fatal("Repo pair not found")
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
@ -12,71 +12,10 @@ import (
|
||||||
// and git notes.
|
// and git notes.
|
||||||
// No Resource embedding — collection and item commit paths differ, and the
|
// No Resource embedding — collection and item commit paths differ, and the
|
||||||
// remaining endpoints are heterogeneous across status and note paths.
|
// 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 {
|
type CommitService struct {
|
||||||
client *Client
|
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 (
|
const (
|
||||||
commitCollectionPath = "/api/v1/repos/{owner}/{repo}/commits"
|
commitCollectionPath = "/api/v1/repos/{owner}/{repo}/commits"
|
||||||
commitItemPath = "/api/v1/repos/{owner}/{repo}/git/commits/{sha}"
|
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.
|
// 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) {
|
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), commitListQuery(filters...), opts)
|
return ListPage[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAll returns all commits for a repository.
|
// ListAll returns all commits for a repository.
|
||||||
func (s *CommitService) ListAll(ctx context.Context, params Params, filters ...CommitListOptions) ([]types.Commit, error) {
|
func (s *CommitService) ListAll(ctx context.Context, params Params) ([]types.Commit, error) {
|
||||||
return ListAll[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), commitListQuery(filters...))
|
return ListAll[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iter returns an iterator over all commits for a repository.
|
// 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] {
|
func (s *CommitService) Iter(ctx context.Context, params Params) iter.Seq2[types.Commit, error] {
|
||||||
return ListIter[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), commitListQuery(filters...))
|
return ListIter[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a single commit by SHA or ref.
|
// 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
|
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).
|
// 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) {
|
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))
|
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
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
var out types.CombinedStatus
|
var out types.CombinedStatus
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// ListStatuses returns all commit statuses for a given ref.
|
||||||
func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref string) ([]types.CommitStatus, error) {
|
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
|
var out []types.CommitStatus
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -161,25 +69,9 @@ func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref strin
|
||||||
return out, nil
|
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.
|
// 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) {
|
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
|
var out types.CommitStatus
|
||||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// GetNote returns the git note for a given commit SHA.
|
||||||
func (s *CommitService) GetNote(ctx context.Context, owner, repo, sha string) (*types.Note, error) {
|
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
|
var out types.Note
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &out, nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -11,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestCommitService_Good_Get(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestCommitService_Good_CreateStatus(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestCommitService_Good_GetCombinedStatus(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
||||||
|
|
|
||||||
122
config.go
122
config.go
|
|
@ -1,108 +1,26 @@
|
||||||
package forge
|
package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
coreerr "dappco.re/go/core/log"
|
||||||
coreio "dappco.re/go/core/io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// DefaultURL is the fallback Forgejo instance URL when neither flag nor
|
// DefaultURL is the fallback Forgejo instance URL when neither flag nor
|
||||||
// environment variable is set.
|
// environment variable is set.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
// cfgURL, _, _ := forge.ResolveConfig("", "")
|
|
||||||
// _ = cfgURL == forge.DefaultURL
|
|
||||||
DefaultURL = "http://localhost:3000"
|
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
|
// ResolveConfig resolves the Forgejo URL and API token from flags, environment
|
||||||
// variables, config file, and built-in defaults. Priority order:
|
// variables, and built-in defaults. Priority order: flags > env > defaults.
|
||||||
// flags > env > config file > defaults.
|
|
||||||
//
|
//
|
||||||
// Environment variables:
|
// Environment variables:
|
||||||
// - FORGE_URL — base URL of the Forgejo instance
|
// - FORGE_URL — base URL of the Forgejo instance
|
||||||
// - FORGE_TOKEN — API token for authentication
|
// - FORGE_TOKEN — API token for authentication
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// url, token, err := forge.ResolveConfig("", "")
|
|
||||||
// _ = url
|
|
||||||
// _ = token
|
|
||||||
func ResolveConfig(flagURL, flagToken string) (url, token string, err error) {
|
func ResolveConfig(flagURL, flagToken string) (url, token string, err error) {
|
||||||
if envURL, ok := os.LookupEnv("FORGE_URL"); ok && envURL != "" {
|
url = os.Getenv("FORGE_URL")
|
||||||
url = envURL
|
token = os.Getenv("FORGE_TOKEN")
|
||||||
}
|
|
||||||
if envToken, ok := os.LookupEnv("FORGE_TOKEN"); ok && envToken != "" {
|
|
||||||
token = envToken
|
|
||||||
}
|
|
||||||
|
|
||||||
if flagURL != "" {
|
if flagURL != "" {
|
||||||
url = flagURL
|
url = flagURL
|
||||||
|
|
@ -110,49 +28,21 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) {
|
||||||
if flagToken != "" {
|
if flagToken != "" {
|
||||||
token = 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 == "" {
|
if url == "" {
|
||||||
url = DefaultURL
|
url = DefaultURL
|
||||||
}
|
}
|
||||||
return url, token, nil
|
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.
|
// NewForgeFromConfig creates a new Forge client using resolved configuration.
|
||||||
// It returns an error if no API token is available from flags, environment,
|
// It returns an error if no API token is available from flags or environment.
|
||||||
// or the saved config file.
|
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// f, err := forge.NewForgeFromConfig("", "")
|
|
||||||
// _ = f
|
|
||||||
func NewForgeFromConfig(flagURL, flagToken string, opts ...Option) (*Forge, error) {
|
func NewForgeFromConfig(flagURL, flagToken string, opts ...Option) (*Forge, error) {
|
||||||
url, token, err := ResolveConfig(flagURL, flagToken)
|
url, token, err := ResolveConfig(flagURL, flagToken)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if token == "" {
|
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
|
return NewForge(url, token, opts...), nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
162
config_test.go
162
config_test.go
|
|
@ -1,15 +1,11 @@
|
||||||
package forge
|
package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"os"
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
coreio "dappco.re/go/core/io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestResolveConfig_EnvOverrides_Good(t *testing.T) {
|
func TestResolveConfig_Good_EnvOverrides(t *testing.T) {
|
||||||
t.Setenv("HOME", t.TempDir())
|
|
||||||
t.Setenv("FORGE_URL", "https://forge.example.com")
|
t.Setenv("FORGE_URL", "https://forge.example.com")
|
||||||
t.Setenv("FORGE_TOKEN", "env-token")
|
t.Setenv("FORGE_TOKEN", "env-token")
|
||||||
|
|
||||||
|
|
@ -25,8 +21,7 @@ func TestResolveConfig_EnvOverrides_Good(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_FlagOverridesEnv_Good(t *testing.T) {
|
func TestResolveConfig_Good_FlagOverridesEnv(t *testing.T) {
|
||||||
t.Setenv("HOME", t.TempDir())
|
|
||||||
t.Setenv("FORGE_URL", "https://env.example.com")
|
t.Setenv("FORGE_URL", "https://env.example.com")
|
||||||
t.Setenv("FORGE_TOKEN", "env-token")
|
t.Setenv("FORGE_TOKEN", "env-token")
|
||||||
|
|
||||||
|
|
@ -42,10 +37,9 @@ func TestResolveConfig_FlagOverridesEnv_Good(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_DefaultURL_Good(t *testing.T) {
|
func TestResolveConfig_Good_DefaultURL(t *testing.T) {
|
||||||
t.Setenv("HOME", t.TempDir())
|
os.Unsetenv("FORGE_URL")
|
||||||
t.Setenv("FORGE_URL", "")
|
os.Unsetenv("FORGE_TOKEN")
|
||||||
t.Setenv("FORGE_TOKEN", "")
|
|
||||||
|
|
||||||
url, _, err := ResolveConfig("", "")
|
url, _, err := ResolveConfig("", "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -56,150 +50,12 @@ func TestResolveConfig_DefaultURL_Good(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestResolveConfig_ConfigFile_Good(t *testing.T) {
|
func TestNewForgeFromConfig_Bad_NoToken(t *testing.T) {
|
||||||
home := t.TempDir()
|
os.Unsetenv("FORGE_URL")
|
||||||
t.Setenv("HOME", home)
|
os.Unsetenv("FORGE_TOKEN")
|
||||||
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", "")
|
|
||||||
|
|
||||||
_, err := NewForgeFromConfig("", "")
|
_, err := NewForgeFromConfig("", "")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("expected error for missing token")
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"iter"
|
"fmt"
|
||||||
"net/url"
|
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ContentService handles file read/write operations via the Forgejo API.
|
// ContentService handles file read/write operations via the Forgejo API.
|
||||||
// No Resource embedding — paths vary by operation.
|
// 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 {
|
type ContentService struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
@ -24,48 +17,9 @@ func newContentService(c *Client) *ContentService {
|
||||||
return &ContentService{client: c}
|
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.
|
// 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) {
|
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
|
var out types.ContentsResponse
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// 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) {
|
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
|
var out types.FileResponse
|
||||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// 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) {
|
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
|
var out types.FileResponse
|
||||||
if err := s.client.Put(ctx, path, opts, &out); err != nil {
|
if err := s.client.Put(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// 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 {
|
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)
|
return s.client.DeleteWithBody(ctx, path, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRawFile returns the raw file content as bytes.
|
// GetRawFile returns the raw file content as bytes.
|
||||||
func (s *ContentService) GetRawFile(ctx context.Context, owner, repo, filepath string) ([]byte, error) {
|
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)
|
return s.client.GetRaw(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,70 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestContentService_ListContents_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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
t.Errorf("expected PUT, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "file not found"})
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "file not found"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "file not found"})
|
||||||
|
|
|
||||||
3
doc.go
3
doc.go
|
|
@ -2,9 +2,8 @@
|
||||||
//
|
//
|
||||||
// Usage:
|
// Usage:
|
||||||
//
|
//
|
||||||
// ctx := context.Background()
|
|
||||||
// f := forge.NewForge("https://forge.lthn.ai", "your-token")
|
// 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/.
|
// Types are generated from Forgejo's swagger.v1.json spec via cmd/forgegen/.
|
||||||
// Run `go generate ./types/...` to regenerate after a Forgejo upgrade.
|
// 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 |
|
| 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 | 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 | 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 | 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) |
|
| 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 | 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 | 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 | 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) |
|
| 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 | 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` |
|
| 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.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.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.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.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.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. |
|
| 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.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.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.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 | 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.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` |
|
| 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.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.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.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.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.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.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.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.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.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 | 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` |
|
| 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.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.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.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.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.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.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.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.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.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.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` |
|
| 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.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 | 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.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.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.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.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.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.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.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.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.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.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.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.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 | 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.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` |
|
| 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 | 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.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.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.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.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.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.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.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.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.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.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.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.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.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.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.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 | 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. |
|
| 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.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 | 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.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.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.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.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.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.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 | 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.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.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.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.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.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 | 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.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.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.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.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.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.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.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.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 | 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.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` |
|
| 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.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 | 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.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.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.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.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. |
|
| 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 | 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.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.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 | 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.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` |
|
| 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) │
|
│ Forge (top-level client) │
|
||||||
│ Aggregates 20 service structs │
|
│ Aggregates 18 service structs │
|
||||||
├─────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────┤
|
||||||
│ Service layer │
|
│ Service layer │
|
||||||
│ RepoService, IssueService, PullService, ... │
|
│ 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) DeleteWithBody(ctx context.Context, path string, body any) error
|
||||||
func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, 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) 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).
|
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 ./...
|
go test ./...
|
||||||
|
|
||||||
# Run a specific test by name
|
# 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
|
# Run tests with race detection
|
||||||
go test -race ./...
|
go test -race ./...
|
||||||
|
|
@ -59,7 +59,7 @@ core go cov --open # Open coverage report in browser
|
||||||
|
|
||||||
### Test naming convention
|
### 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.
|
- **`_Good`** — Happy-path tests confirming correct behaviour.
|
||||||
- **`_Bad`** — Expected error conditions (e.g. 404, 500 responses).
|
- **`_Bad`** — Expected error conditions (e.g. 404, 500 responses).
|
||||||
|
|
@ -67,11 +67,11 @@ Tests follow `Test<TypeOrArea>_<MethodOrCase>_<Good|Bad|Ugly>`:
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
```
|
```
|
||||||
TestClient_Get_Good
|
TestClient_Good_Get
|
||||||
TestClient_ServerError_Bad
|
TestClient_Bad_ServerError
|
||||||
TestClient_NotFound_Bad
|
TestClient_Bad_NotFound
|
||||||
TestClient_ContextCancellation_Good
|
TestClient_Good_ContextCancellation
|
||||||
TestResource_ListAll_Good
|
TestResource_Good_ListAll
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -173,7 +173,7 @@ To add coverage for a new Forgejo API domain:
|
||||||
|
|
||||||
```go
|
```go
|
||||||
func (s *TopicService) ListRepoTopics(ctx context.Context, owner, repo string) ([]types.Topic, error) {
|
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)
|
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
|
# 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`
|
**Module path:** `dappco.re/go/core/forge`
|
||||||
**Go version:** 1.26+
|
**Go version:** 1.26+
|
||||||
|
|
@ -75,7 +75,7 @@ Environment variables:
|
||||||
go-forge/
|
go-forge/
|
||||||
├── client.go HTTP client, auth, error handling, rate limits
|
├── client.go HTTP client, auth, error handling, rate limits
|
||||||
├── config.go Config resolution: flags > env > defaults
|
├── 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
|
├── resource.go Generic Resource[T, C, U] for CRUD operations
|
||||||
├── pagination.go ListPage, ListAll, ListIter — paginated requests
|
├── pagination.go ListPage, ListAll, ListIter — paginated requests
|
||||||
├── params.go Path variable resolution ({owner}/{repo} -> values)
|
├── params.go Path variable resolution ({owner}/{repo} -> values)
|
||||||
|
|
@ -92,13 +92,11 @@ go-forge/
|
||||||
├── webhooks.go WebhookService — repo and org webhooks
|
├── webhooks.go WebhookService — repo and org webhooks
|
||||||
├── notifications.go NotificationService — notifications, threads
|
├── notifications.go NotificationService — notifications, threads
|
||||||
├── packages.go PackageService — package registry
|
├── 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
|
├── contents.go ContentService — file read/write/delete
|
||||||
├── wiki.go WikiService — wiki pages
|
├── wiki.go WikiService — wiki pages
|
||||||
├── misc.go MiscService — markdown, licences, gitignore, version
|
|
||||||
├── commits.go CommitService — statuses, notes
|
├── commits.go CommitService — statuses, notes
|
||||||
├── milestones.go MilestoneService — repository milestones
|
├── misc.go MiscService — markdown, licences, gitignore, version
|
||||||
├── activitypub.go ActivityPubService — ActivityPub actors and inboxes
|
|
||||||
├── types/ 229 generated Go types from swagger.v1.json
|
├── types/ 229 generated Go types from swagger.v1.json
|
||||||
│ ├── generate.go go:generate directive
|
│ ├── generate.go go:generate directive
|
||||||
│ ├── repo.go Repository, CreateRepoOption, EditRepoOption, ...
|
│ ├── repo.go Repository, CreateRepoOption, EditRepoOption, ...
|
||||||
|
|
@ -116,7 +114,7 @@ go-forge/
|
||||||
|
|
||||||
## Services
|
## 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 |
|
| 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 |
|
| `Webhooks` | `WebhookService` | `Resource[Hook, ...]` | Repo and org webhooks |
|
||||||
| `Notifications` | `NotificationService` | (standalone) | Notifications, threads |
|
| `Notifications` | `NotificationService` | (standalone) | Notifications, threads |
|
||||||
| `Packages` | `PackageService` | (standalone) | Package registry |
|
| `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 |
|
| `Contents` | `ContentService` | (standalone) | File read/write/delete |
|
||||||
| `Wiki` | `WikiService` | (standalone) | Wiki pages |
|
| `Wiki` | `WikiService` | (standalone) | Wiki pages |
|
||||||
| `Misc` | `MiscService` | (standalone) | Markdown, licences, gitignore, version |
|
|
||||||
| `Commits` | `CommitService` | (standalone) | Commit statuses, git notes |
|
| `Commits` | `CommitService` | (standalone) | Commit statuses, git notes |
|
||||||
| `Milestones` | `MilestoneService` | (standalone) | Repository milestones |
|
| `Misc` | `MiscService` | (standalone) | Markdown, licences, gitignore, version |
|
||||||
| `ActivityPub` | `ActivityPubService` | (standalone) | ActivityPub actors and inboxes |
|
|
||||||
|
|
||||||
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.
|
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
|
## 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
|
module dappco.re/go/core/forge
|
||||||
|
|
|
||||||
113
forge.go
113
forge.go
|
|
@ -1,14 +1,6 @@
|
||||||
package forge
|
package forge
|
||||||
|
|
||||||
import "net/http"
|
|
||||||
|
|
||||||
// Forge is the top-level client for the Forgejo API.
|
// 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 {
|
type Forge struct {
|
||||||
client *Client
|
client *Client
|
||||||
|
|
||||||
|
|
@ -31,16 +23,9 @@ type Forge struct {
|
||||||
Misc *MiscService
|
Misc *MiscService
|
||||||
Commits *CommitService
|
Commits *CommitService
|
||||||
Milestones *MilestoneService
|
Milestones *MilestoneService
|
||||||
ActivityPub *ActivityPubService
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewForge creates a new Forge client.
|
// 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 {
|
func NewForge(url, token string, opts ...Option) *Forge {
|
||||||
c := NewClient(url, token, opts...)
|
c := NewClient(url, token, opts...)
|
||||||
f := &Forge{client: c}
|
f := &Forge{client: c}
|
||||||
|
|
@ -63,102 +48,8 @@ func NewForge(url, token string, opts ...Option) *Forge {
|
||||||
f.Misc = newMiscService(c)
|
f.Misc = newMiscService(c)
|
||||||
f.Commits = newCommitService(c)
|
f.Commits = newCommitService(c)
|
||||||
f.Milestones = newMilestoneService(c)
|
f.Milestones = newMilestoneService(c)
|
||||||
f.ActivityPub = newActivityPubService(c)
|
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
// Client returns the underlying Forge client.
|
// Client returns the underlying HTTP client.
|
||||||
//
|
func (f *Forge) Client() *Client { return f.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() }
|
|
||||||
|
|
|
||||||
183
forge_test.go
183
forge_test.go
|
|
@ -1,10 +1,8 @@
|
||||||
package forge
|
package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"encoding/json"
|
||||||
json "github.com/goccy/go-json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -12,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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")
|
f := NewForge("https://forge.lthn.ai", "tok")
|
||||||
if f.Repos == nil {
|
if f.Repos == nil {
|
||||||
t.Fatal("Repos service is nil")
|
t.Fatal("Repos service is nil")
|
||||||
|
|
@ -20,12 +18,9 @@ func TestForge_NewForge_Good(t *testing.T) {
|
||||||
if f.Issues == nil {
|
if f.Issues == nil {
|
||||||
t.Fatal("Issues service is 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")
|
f := NewForge("https://forge.lthn.ai", "tok")
|
||||||
c := f.Client()
|
c := f.Client()
|
||||||
if c == nil {
|
if c == nil {
|
||||||
|
|
@ -34,92 +29,9 @@ func TestForge_Client_Good(t *testing.T) {
|
||||||
if c.baseURL != "https://forge.lthn.ai" {
|
if c.baseURL != "https://forge.lthn.ai" {
|
||||||
t.Errorf("got baseURL=%q", c.baseURL)
|
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) {
|
func TestRepoService_Good_ListOrgRepos(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/api/v1/repos/core/go-forge" {
|
if r.URL.Path != "/api/v1/repos/core/go-forge" {
|
||||||
t.Errorf("wrong path: %s", r.URL.Path)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPatch {
|
if r.Method != http.MethodPatch {
|
||||||
t.Errorf("expected PATCH, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
t.Errorf("expected POST, got %s", r.Method)
|
||||||
|
|
@ -245,80 +157,3 @@ func TestRepoService_Fork_Good(t *testing.T) {
|
||||||
t.Error("expected fork=true")
|
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
|
go 1.26.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dappco.re/go/core v0.4.7
|
|
||||||
dappco.re/go/core/io v0.2.0
|
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 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4=
|
||||||
dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E=
|
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 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0=
|
||||||
forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw=
|
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 h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
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 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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=
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
goio "io"
|
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IssueService handles issue operations within a repository.
|
// 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 {
|
type IssueService struct {
|
||||||
Resource[types.Issue, types.CreateIssueOption, types.EditIssueOption]
|
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 {
|
func newIssueService(c *Client) *IssueService {
|
||||||
return &IssueService{
|
return &IssueService{
|
||||||
Resource: *NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](
|
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.
|
// Pin pins an issue.
|
||||||
func (s *IssueService) Pin(ctx context.Context, owner, repo string, index int64) error {
|
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)
|
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.
|
// Unpin unpins an issue.
|
||||||
func (s *IssueService) Unpin(ctx context.Context, owner, repo string, index int64) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetDeadline sets or updates the deadline on an issue.
|
// SetDeadline sets or updates the deadline on an issue.
|
||||||
func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, index int64, deadline string) error {
|
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}
|
body := map[string]string{"due_date": deadline}
|
||||||
return s.client.Post(ctx, path, body, nil)
|
return s.client.Post(ctx, path, body, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddReaction adds a reaction to an issue.
|
// AddReaction adds a reaction to an issue.
|
||||||
func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error {
|
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)))
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index)
|
||||||
body := types.EditReactionOption{Reaction: reaction}
|
body := map[string]string{"content": reaction}
|
||||||
return s.client.Post(ctx, path, body, nil)
|
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.
|
// DeleteReaction removes a reaction from an issue.
|
||||||
func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, index int64, reaction string) error {
|
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)))
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index)
|
||||||
body := types.EditReactionOption{Reaction: reaction}
|
body := map[string]string{"content": reaction}
|
||||||
return s.client.DeleteWithBody(ctx, path, body)
|
return s.client.DeleteWithBody(ctx, path, body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartStopwatch starts the stopwatch on an issue.
|
// StartStopwatch starts the stopwatch on an issue.
|
||||||
func (s *IssueService) StartStopwatch(ctx context.Context, owner, repo string, index int64) error {
|
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)
|
return s.client.Post(ctx, path, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopStopwatch stops the stopwatch on an issue.
|
// StopStopwatch stops the stopwatch on an issue.
|
||||||
func (s *IssueService) StopStopwatch(ctx context.Context, owner, repo string, index int64) error {
|
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)
|
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.
|
// AddLabels adds labels to an issue.
|
||||||
func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index int64, labelIDs []int64) error {
|
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)}
|
body := types.IssueLabelsOption{Labels: toAnySlice(labelIDs)}
|
||||||
return s.client.Post(ctx, path, body, nil)
|
return s.client.Post(ctx, path, body, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveLabel removes a single label from an issue.
|
// RemoveLabel removes a single label from an issue.
|
||||||
func (s *IssueService) RemoveLabel(ctx context.Context, owner, repo string, index int64, labelID int64) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListComments returns all comments on an issue.
|
// ListComments returns all comments on an issue.
|
||||||
func (s *IssueService) ListComments(ctx context.Context, owner, repo string, index int64) ([]types.Comment, error) {
|
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)
|
return ListAll[types.Comment](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterComments returns an iterator over all comments on an issue.
|
// 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] {
|
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)
|
return ListIter[types.Comment](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateComment creates a comment on an issue.
|
// CreateComment creates a comment on an issue.
|
||||||
func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, index int64, body string) (*types.Comment, error) {
|
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}
|
opts := types.CreateIssueCommentOption{Body: body}
|
||||||
var out types.Comment
|
var out types.Comment
|
||||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
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
|
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.
|
// toAnySlice converts a slice of int64 to a slice of any for IssueLabelsOption.
|
||||||
func toAnySlice(ids []int64) []any {
|
func toAnySlice(ids []int64) []any {
|
||||||
out := make([]any, len(ids))
|
out := make([]any, len(ids))
|
||||||
|
|
@ -777,37 +110,3 @@ func toAnySlice(ids []int64) []any {
|
||||||
}
|
}
|
||||||
return out
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
|
|
@ -9,11 +10,6 @@ import (
|
||||||
|
|
||||||
// LabelService handles repository labels, organisation labels, and issue labels.
|
// LabelService handles repository labels, organisation labels, and issue labels.
|
||||||
// No Resource embedding — paths are heterogeneous.
|
// 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 {
|
type LabelService struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
@ -24,19 +20,19 @@ func newLabelService(c *Client) *LabelService {
|
||||||
|
|
||||||
// ListRepoLabels returns all labels for a repository.
|
// ListRepoLabels returns all labels for a repository.
|
||||||
func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) ([]types.Label, error) {
|
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)
|
return ListAll[types.Label](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterRepoLabels returns an iterator over all labels for a repository.
|
// 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] {
|
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)
|
return ListIter[types.Label](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRepoLabel returns a single label by ID.
|
// GetRepoLabel returns a single label by ID.
|
||||||
func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id int64) (*types.Label, error) {
|
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
|
var out types.Label
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// CreateRepoLabel creates a new label in a repository.
|
||||||
func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string, opts *types.CreateLabelOption) (*types.Label, error) {
|
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
|
var out types.Label
|
||||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// 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) {
|
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
|
var out types.Label
|
||||||
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// DeleteRepoLabel deletes a label from a repository.
|
||||||
func (s *LabelService) DeleteRepoLabel(ctx context.Context, owner, repo string, id int64) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListOrgLabels returns all labels for an organisation.
|
// ListOrgLabels returns all labels for an organisation.
|
||||||
func (s *LabelService) ListOrgLabels(ctx context.Context, org string) ([]types.Label, error) {
|
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)
|
return ListAll[types.Label](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterOrgLabels returns an iterator over all labels for an organisation.
|
// IterOrgLabels returns an iterator over all labels for an organisation.
|
||||||
func (s *LabelService) IterOrgLabels(ctx context.Context, org string) iter.Seq2[types.Label, error] {
|
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)
|
return ListIter[types.Label](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOrgLabel creates a new label in an organisation.
|
// CreateOrgLabel creates a new label in an organisation.
|
||||||
func (s *LabelService) CreateOrgLabel(ctx context.Context, org string, opts *types.CreateLabelOption) (*types.Label, error) {
|
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
|
var out types.Label
|
||||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &out, nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPatch {
|
if r.Method != http.MethodPatch {
|
||||||
t.Errorf("expected PATCH, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "label not found"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "label not found"})
|
||||||
|
|
|
||||||
|
|
@ -2,49 +2,12 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"iter"
|
"fmt"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"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.
|
// 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 {
|
type MilestoneService struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
@ -53,27 +16,15 @@ func newMilestoneService(c *Client) *MilestoneService {
|
||||||
return &MilestoneService{client: c}
|
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.
|
// ListAll returns all milestones for a repository.
|
||||||
func (s *MilestoneService) ListAll(ctx context.Context, params Params, filters ...MilestoneListOptions) ([]types.Milestone, error) {
|
func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error) {
|
||||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params)
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones", params["owner"], params["repo"])
|
||||||
return ListAll[types.Milestone](ctx, s.client, path, milestoneQuery(filters...))
|
return ListAll[types.Milestone](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a single milestone by ID.
|
// Get returns a single milestone by ID.
|
||||||
func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error) {
|
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
|
var out types.Milestone
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -83,43 +34,10 @@ func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64
|
||||||
|
|
||||||
// Create creates a new milestone.
|
// Create creates a new milestone.
|
||||||
func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error) {
|
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
|
var out types.Milestone
|
||||||
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
if err := s.client.Post(ctx, path, opts, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &out, nil
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"iter"
|
"fmt"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
@ -11,11 +11,6 @@ import (
|
||||||
// markdown rendering, licence templates, gitignore templates, and
|
// markdown rendering, licence templates, gitignore templates, and
|
||||||
// server metadata.
|
// server metadata.
|
||||||
// No Resource embedding — heterogeneous read-only endpoints.
|
// No Resource embedding — heterogeneous read-only endpoints.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
|
||||||
// _, err := f.Misc.GetVersion(ctx)
|
|
||||||
type MiscService struct {
|
type MiscService struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
@ -35,27 +30,6 @@ func (s *MiscService) RenderMarkdown(ctx context.Context, text, mode string) (st
|
||||||
return string(data), nil
|
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.
|
// ListLicenses returns all available licence templates.
|
||||||
func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplateListEntry, error) {
|
func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplateListEntry, error) {
|
||||||
var out []types.LicensesTemplateListEntry
|
var out []types.LicensesTemplateListEntry
|
||||||
|
|
@ -65,25 +39,9 @@ func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplat
|
||||||
return out, nil
|
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.
|
// GetLicense returns a single licence template by name.
|
||||||
func (s *MiscService) GetLicense(ctx context.Context, name string) (*types.LicenseTemplateInfo, error) {
|
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
|
var out types.LicenseTemplateInfo
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -100,25 +58,9 @@ func (s *MiscService) ListGitignoreTemplates(ctx context.Context) ([]string, err
|
||||||
return out, nil
|
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.
|
// GetGitignoreTemplate returns a single gitignore template by name.
|
||||||
func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*types.GitignoreTemplateInfo, error) {
|
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
|
var out types.GitignoreTemplateInfo
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -135,51 +77,6 @@ func (s *MiscService) GetNodeInfo(ctx context.Context) (*types.NodeInfo, error)
|
||||||
return &out, nil
|
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.
|
// GetVersion returns the server version.
|
||||||
func (s *MiscService) GetVersion(ctx context.Context) (*types.ServerVersion, error) {
|
func (s *MiscService) GetVersion(ctx context.Context) (*types.ServerVersion, error) {
|
||||||
var out types.ServerVersion
|
var out types.ServerVersion
|
||||||
|
|
|
||||||
290
misc_test.go
290
misc_test.go
|
|
@ -2,17 +2,15 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
func TestMiscService_Good_GetVersion(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestMiscService_Bad_NotFound(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "not found"})
|
||||||
|
|
|
||||||
278
notifications.go
278
notifications.go
|
|
@ -2,216 +2,42 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"net/http"
|
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
"dappco.re/go/core/forge/types"
|
"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.
|
// NotificationService handles notification operations via the Forgejo API.
|
||||||
// No Resource embedding — varied endpoint shapes.
|
// No Resource embedding — varied endpoint shapes.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
|
||||||
// _, err := f.Notifications.List(ctx)
|
|
||||||
type NotificationService struct {
|
type NotificationService struct {
|
||||||
client *Client
|
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 {
|
func newNotificationService(c *Client) *NotificationService {
|
||||||
return &NotificationService{client: c}
|
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.
|
// List returns all notifications for the authenticated user.
|
||||||
func (s *NotificationService) List(ctx context.Context, filters ...NotificationListOptions) ([]types.NotificationThread, error) {
|
func (s *NotificationService) List(ctx context.Context) ([]types.NotificationThread, error) {
|
||||||
return s.listAll(ctx, "/api/v1/notifications", filters...)
|
return ListAll[types.NotificationThread](ctx, s.client, "/api/v1/notifications", nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iter returns an iterator over all notifications for the authenticated user.
|
// 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] {
|
func (s *NotificationService) Iter(ctx context.Context) iter.Seq2[types.NotificationThread, error] {
|
||||||
return s.listIter(ctx, "/api/v1/notifications", filters...)
|
return ListIter[types.NotificationThread](ctx, s.client, "/api/v1/notifications", nil)
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRepo returns all notifications for a specific repository.
|
// ListRepo returns all notifications for a specific repository.
|
||||||
func (s *NotificationService) ListRepo(ctx context.Context, owner, repo string, filters ...NotificationListOptions) ([]types.NotificationThread, error) {
|
func (s *NotificationService) ListRepo(ctx context.Context, owner, repo string) ([]types.NotificationThread, error) {
|
||||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/notifications", pathParams("owner", owner, "repo", repo))
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo)
|
||||||
return s.listAll(ctx, path, filters...)
|
return ListAll[types.NotificationThread](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterRepo returns an iterator over all notifications for a specific repository.
|
// 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] {
|
func (s *NotificationService) IterRepo(ctx context.Context, owner, repo string) iter.Seq2[types.NotificationThread, error] {
|
||||||
path := ResolvePath("/api/v1/repos/{owner}/{repo}/notifications", pathParams("owner", owner, "repo", repo))
|
path := fmt.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo)
|
||||||
return s.listIter(ctx, path, filters...)
|
return ListIter[types.NotificationThread](ctx, s.client, path, nil)
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkRead marks all notifications as read.
|
// 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.
|
// GetThread returns a single notification thread by ID.
|
||||||
func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.NotificationThread, error) {
|
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
|
var out types.NotificationThread
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// MarkThreadRead marks a single notification thread as read.
|
||||||
func (s *NotificationService) MarkThreadRead(ctx context.Context, id int64) error {
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestNotificationService_Good_ListRepo(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestNotificationService_Good_GetThread(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestNotificationService_Good_MarkRead(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
t.Errorf("expected PUT, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPatch {
|
if r.Method != http.MethodPatch {
|
||||||
t.Errorf("expected PATCH, got %s", r.Method)
|
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) {
|
func TestNotificationService_Bad_NotFound(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "thread not found"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "thread not found"})
|
||||||
|
|
|
||||||
281
orgs.go
281
orgs.go
|
|
@ -2,49 +2,17 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"net/http"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// OrgService handles organisation operations.
|
// OrgService handles organisation operations.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
|
||||||
// _, err := f.Orgs.ListMembers(ctx, "core")
|
|
||||||
type OrgService struct {
|
type OrgService struct {
|
||||||
Resource[types.Organization, types.CreateOrgOption, types.EditOrgOption]
|
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 {
|
func newOrgService(c *Client) *OrgService {
|
||||||
return &OrgService{
|
return &OrgService{
|
||||||
Resource: *NewResource[types.Organization, types.CreateOrgOption, types.EditOrgOption](
|
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.
|
// ListMembers returns all members of an organisation.
|
||||||
func (s *OrgService) ListMembers(ctx context.Context, org string) ([]types.User, error) {
|
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)
|
return ListAll[types.User](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterMembers returns an iterator over all members of an organisation.
|
// IterMembers returns an iterator over all members of an organisation.
|
||||||
func (s *OrgService) IterMembers(ctx context.Context, org string) iter.Seq2[types.User, error] {
|
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)
|
return ListIter[types.User](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddMember adds a user to an organisation.
|
// AddMember adds a user to an organisation.
|
||||||
func (s *OrgService) AddMember(ctx context.Context, org, username string) error {
|
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)
|
return s.client.Put(ctx, path, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveMember removes a user from an organisation.
|
// RemoveMember removes a user from an organisation.
|
||||||
func (s *OrgService) RemoveMember(ctx context.Context, org, username string) error {
|
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)
|
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.
|
// ListUserOrgs returns all organisations for a user.
|
||||||
func (s *OrgService) ListUserOrgs(ctx context.Context, username string) ([]types.Organization, error) {
|
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)
|
return ListAll[types.Organization](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterUserOrgs returns an iterator over all organisations for a user.
|
// IterUserOrgs returns an iterator over all organisations for a user.
|
||||||
func (s *OrgService) IterUserOrgs(ctx context.Context, username string) iter.Seq2[types.Organization, error] {
|
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)
|
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] {
|
func (s *OrgService) IterMyOrgs(ctx context.Context) iter.Seq2[types.Organization, error] {
|
||||||
return ListIter[types.Organization](ctx, s.client, "/api/v1/user/orgs", nil)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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")
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
|
|
@ -9,11 +10,6 @@ import (
|
||||||
|
|
||||||
// PackageService handles package registry operations via the Forgejo API.
|
// PackageService handles package registry operations via the Forgejo API.
|
||||||
// No Resource embedding — paths vary by operation.
|
// 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 {
|
type PackageService struct {
|
||||||
client *Client
|
client *Client
|
||||||
}
|
}
|
||||||
|
|
@ -24,19 +20,19 @@ func newPackageService(c *Client) *PackageService {
|
||||||
|
|
||||||
// List returns all packages for a given owner.
|
// List returns all packages for a given owner.
|
||||||
func (s *PackageService) List(ctx context.Context, owner string) ([]types.Package, error) {
|
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)
|
return ListAll[types.Package](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iter returns an iterator over all packages for a given owner.
|
// 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] {
|
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)
|
return ListIter[types.Package](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a single package by owner, type, name, and version.
|
// 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) {
|
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
|
var out types.Package
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// Delete removes a package by owner, type, name, and version.
|
||||||
func (s *PackageService) Delete(ctx context.Context, owner, pkgType, name, version string) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListFiles returns all files for a specific package version.
|
// ListFiles returns all files for a specific package version.
|
||||||
func (s *PackageService) ListFiles(ctx context.Context, owner, pkgType, name, version string) ([]types.PackageFile, error) {
|
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)
|
return ListAll[types.PackageFile](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterFiles returns an iterator over all files for a specific package version.
|
// 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] {
|
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)
|
return ListIter[types.PackageFile](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusNotFound)
|
w.WriteHeader(http.StatusNotFound)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "package not found"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "package not found"})
|
||||||
|
|
|
||||||
100
pagination.go
100
pagination.go
|
|
@ -7,58 +7,19 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
coreerr "dappco.re/go/core/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const defaultPageLimit = 50
|
|
||||||
|
|
||||||
// ListOptions controls pagination.
|
// ListOptions controls pagination.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := forge.ListOptions{Page: 1, Limit: 50}
|
|
||||||
// _ = opts
|
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
Page int // 1-based page number
|
Page int // 1-based page number
|
||||||
Limit int // items per page (default 50)
|
Limit int // items per page (default 50)
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns a safe summary of the pagination options.
|
// DefaultList returns sensible default pagination.
|
||||||
//
|
var DefaultList = ListOptions{Page: 1, Limit: 50}
|
||||||
// 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}
|
|
||||||
|
|
||||||
// PagedResult holds a single page of results with metadata.
|
// 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 {
|
type PagedResult[T any] struct {
|
||||||
Items []T
|
Items []T
|
||||||
TotalCount int
|
TotalCount int
|
||||||
|
|
@ -66,55 +27,19 @@ type PagedResult[T any] struct {
|
||||||
HasMore bool
|
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.
|
// ListPage fetches a single page of results.
|
||||||
// Extra query params can be passed via the query map.
|
// 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) {
|
func ListPage[T any](ctx context.Context, c *Client, path string, query map[string]string, opts ListOptions) (*PagedResult[T], error) {
|
||||||
if opts.Page < 1 {
|
if opts.Page < 1 {
|
||||||
opts.Page = 1
|
opts.Page = 1
|
||||||
}
|
}
|
||||||
if opts.Limit < 1 {
|
if opts.Limit < 1 {
|
||||||
opts.Limit = defaultPageLimit
|
opts.Limit = 50
|
||||||
}
|
}
|
||||||
|
|
||||||
u, err := url.Parse(path)
|
u, err := url.Parse(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, core.E("ListPage", "forge: parse path", err)
|
return nil, coreerr.E("ListPage", "forge: parse path", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
q := u.Query()
|
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.
|
// 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) {
|
func ListAll[T any](ctx context.Context, c *Client, path string, query map[string]string) ([]T, error) {
|
||||||
var all []T
|
var all []T
|
||||||
page := 1
|
page := 1
|
||||||
|
|
||||||
for {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// 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] {
|
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) {
|
return func(yield func(T, error) bool) {
|
||||||
page := 1
|
page := 1
|
||||||
for {
|
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 {
|
if err != nil {
|
||||||
yield(*new(T), err)
|
yield(*new(T), err)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,13 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-Total-Count", "2")
|
w.Header().Set("X-Total-Count", "2")
|
||||||
json.NewEncoder(w).Encode([]map[string]int{{"id": 1}, {"id": 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
|
page := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
page++
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-Total-Count", "0")
|
w.Header().Set("X-Total-Count", "0")
|
||||||
json.NewEncoder(w).Encode([]map[string]int{})
|
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
|
page := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
page++
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
p := r.URL.Query().Get("page")
|
p := r.URL.Query().Get("page")
|
||||||
l := r.URL.Query().Get("limit")
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "fail"})
|
json.NewEncoder(w).Encode(map[string]string{"message": "fail"})
|
||||||
|
|
|
||||||
53
params.go
53
params.go
|
|
@ -2,68 +2,17 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Params maps path variable names to values.
|
// Params maps path variable names to values.
|
||||||
// Example: Params{"owner": "core", "repo": "go-forge"}
|
// Example: Params{"owner": "core", "repo": "go-forge"}
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// params := forge.Params{"owner": "core", "repo": "go-forge"}
|
|
||||||
// _ = params
|
|
||||||
type Params map[string]string
|
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.
|
// 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 {
|
func ResolvePath(path string, params Params) string {
|
||||||
for k, v := range params {
|
for k, v := range params {
|
||||||
path = core.Replace(path, "{"+k+"}", url.PathEscape(v))
|
path = strings.ReplaceAll(path, "{"+k+"}", url.PathEscape(v))
|
||||||
}
|
}
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package forge
|
||||||
|
|
||||||
import "testing"
|
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"})
|
got := ResolvePath("/api/v1/repos/{owner}/{repo}", Params{"owner": "core", "repo": "go-forge"})
|
||||||
want := "/api/v1/repos/core/go-forge"
|
want := "/api/v1/repos/core/go-forge"
|
||||||
if got != want {
|
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)
|
got := ResolvePath("/api/v1/user", nil)
|
||||||
if got != "/api/v1/user" {
|
if got != "/api/v1/user" {
|
||||||
t.Errorf("got %q", got)
|
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{
|
got := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}", Params{
|
||||||
"owner": "core", "repo": "go-forge", "index": "42",
|
"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"})
|
got := ResolvePath("/api/v1/repos/{owner}/{repo}", Params{"owner": "my org", "repo": "my repo"})
|
||||||
want := "/api/v1/repos/my%20org/my%20repo"
|
want := "/api/v1/repos/my%20org/my%20repo"
|
||||||
if got != want {
|
if got != want {
|
||||||
|
|
|
||||||
303
pulls.go
303
pulls.go
|
|
@ -2,71 +2,17 @@ package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"net/url"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// PullService handles pull request operations within a repository.
|
// 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 {
|
type PullService struct {
|
||||||
Resource[types.PullRequest, types.CreatePullRequestOption, types.EditPullRequestOption]
|
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 {
|
func newPullService(c *Client) *PullService {
|
||||||
return &PullService{
|
return &PullService{
|
||||||
Resource: *NewResource[types.PullRequest, types.CreatePullRequestOption, types.EditPullRequestOption](
|
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".
|
// 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 {
|
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}
|
body := map[string]string{"Do": method}
|
||||||
return s.client.Post(ctx, path, body, nil)
|
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.
|
// Update updates a pull request branch with the base branch.
|
||||||
func (s *PullService) Update(ctx context.Context, owner, repo string, index int64) error {
|
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)
|
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.
|
// ListReviews returns all reviews on a pull request.
|
||||||
func (s *PullService) ListReviews(ctx context.Context, owner, repo string, index int64) ([]types.PullReview, error) {
|
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)
|
return ListAll[types.PullReview](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterReviews returns an iterator over all reviews on a pull request.
|
// 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] {
|
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)
|
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.
|
// 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) {
|
func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, index int64, review map[string]any) (*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)
|
||||||
var out types.PullReview
|
var out types.PullReview
|
||||||
if err := s.client.Post(ctx, path, review, &out); err != nil {
|
if err := s.client.Post(ctx, path, review, &out); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -208,148 +56,15 @@ func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, inde
|
||||||
return &out, nil
|
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.
|
// DismissReview dismisses a pull request review.
|
||||||
func (s *PullService) DismissReview(ctx context.Context, owner, repo string, index, reviewID int64, msg string) error {
|
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}
|
body := map[string]string{"message": msg}
|
||||||
return s.client.Post(ctx, path, body, nil)
|
return s.client.Post(ctx, path, body, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UndismissReview undismisses a pull request review.
|
// UndismissReview undismisses a pull request review.
|
||||||
func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error {
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
func TestPullService_Good_Merge(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusConflict)
|
w.WriteHeader(http.StatusConflict)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "already merged"})
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
"strconv"
|
|
||||||
|
|
||||||
goio "io"
|
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReleaseService handles release operations within a repository.
|
// 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 {
|
type ReleaseService struct {
|
||||||
Resource[types.Release, types.CreateReleaseOption, types.EditReleaseOption]
|
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 {
|
func newReleaseService(c *Client) *ReleaseService {
|
||||||
return &ReleaseService{
|
return &ReleaseService{
|
||||||
Resource: *NewResource[types.Release, types.CreateReleaseOption, types.EditReleaseOption](
|
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.
|
// GetByTag returns a release by its tag name.
|
||||||
func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) (*types.Release, error) {
|
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))
|
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
|
|
||||||
}
|
|
||||||
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))
|
|
||||||
var out types.Release
|
var out types.Release
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// DeleteByTag deletes a release by its tag name.
|
||||||
func (s *ReleaseService) DeleteByTag(ctx context.Context, owner, repo, tag string) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAssets returns all assets for a release.
|
// ListAssets returns all assets for a release.
|
||||||
func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, releaseID int64) ([]types.Attachment, error) {
|
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)
|
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.
|
// 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] {
|
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)
|
return ListIter[types.Attachment](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAsset returns a single asset for a release.
|
// GetAsset returns a single asset for a release.
|
||||||
func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) (*types.Attachment, error) {
|
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
|
var out types.Attachment
|
||||||
if err := s.client.Get(ctx, path, &out); err != nil {
|
if err := s.client.Get(ctx, path, &out); err != nil {
|
||||||
return nil, err
|
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.
|
// DeleteAsset deletes a single asset from a release.
|
||||||
func (s *ReleaseService) DeleteAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) error {
|
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)
|
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
|
package forge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"io"
|
|
||||||
"mime"
|
|
||||||
"mime/multipart"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"reflect"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func readMultipartReleaseAttachment(t *testing.T, r *http.Request) (map[string]string, string, string) {
|
func TestReleaseService_Good_List(t *testing.T) {
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
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)
|
|
||||||
}
|
|
||||||
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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"iter"
|
"iter"
|
||||||
"strconv"
|
"strings"
|
||||||
|
|
||||||
core "dappco.re/go/core"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Resource provides generic CRUD operations for a Forgejo API resource.
|
// 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.
|
// 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 {
|
type Resource[T any, C any, U any] struct {
|
||||||
client *Client
|
client *Client
|
||||||
path string // item path: /api/v1/repos/{owner}/{repo}/issues/{index}
|
path string // item path: /api/v1/repos/{owner}/{repo}/issues/{index}
|
||||||
collection string // collection path: /api/v1/repos/{owner}/{repo}/issues
|
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.
|
// 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 path should be the item path (e.g., /repos/{owner}/{repo}/issues/{index}).
|
||||||
// The collection path is derived by stripping the last /{placeholder} segment.
|
// 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] {
|
func NewResource[T any, C any, U any](c *Client, path string) *Resource[T, C, U] {
|
||||||
collection := path
|
collection := path
|
||||||
// Strip last segment if it's a pure placeholder like /{index}
|
// Strip last segment if it's a pure placeholder like /{index}
|
||||||
// Don't strip if mixed like /repos or /{org}/repos
|
// 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:]
|
lastSeg := path[i+1:]
|
||||||
if core.HasPrefix(lastSeg, "{") && core.HasSuffix(lastSeg, "}") {
|
if strings.HasPrefix(lastSeg, "{") && strings.HasSuffix(lastSeg, "}") {
|
||||||
collection = path[:i]
|
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.
|
// 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) {
|
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)
|
return ListPage[T](ctx, r.client, ResolvePath(r.collection, params), nil, opts)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListAll returns all resources across all pages.
|
// 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) {
|
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)
|
return ListAll[T](ctx, r.client, ResolvePath(r.collection, params), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iter returns an iterator over all resources across all pages.
|
// 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] {
|
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)
|
return ListIter[T](ctx, r.client, ResolvePath(r.collection, params), nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get returns a single resource by appending id to the path.
|
// 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) {
|
func (r *Resource[T, C, U]) Get(ctx context.Context, params Params) (*T, error) {
|
||||||
var out T
|
var out T
|
||||||
if err := r.client.Get(ctx, ResolvePath(r.path, params), &out); err != nil {
|
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.
|
// 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) {
|
func (r *Resource[T, C, U]) Create(ctx context.Context, params Params, body *C) (*T, error) {
|
||||||
var out T
|
var out T
|
||||||
if err := r.client.Post(ctx, ResolvePath(r.collection, params), body, &out); err != nil {
|
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.
|
// 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) {
|
func (r *Resource[T, C, U]) Update(ctx context.Context, params Params, body *U) (*T, error) {
|
||||||
var out T
|
var out T
|
||||||
if err := r.client.Patch(ctx, ResolvePath(r.path, params), body, &out); err != nil {
|
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.
|
// 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 {
|
func (r *Resource[T, C, U]) Delete(ctx context.Context, params Params) error {
|
||||||
return r.client.Delete(ctx, ResolvePath(r.path, params))
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -22,7 +22,7 @@ type testUpdate struct {
|
||||||
Name *string `json:"name,omitempty"`
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/api/v1/orgs/core/repos" {
|
if r.URL.Path != "/api/v1/orgs/core/repos" {
|
||||||
t.Errorf("wrong path: %s", r.URL.Path)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/api/v1/repos/core/go-forge" {
|
if r.URL.Path != "/api/v1/repos/core/go-forge" {
|
||||||
t.Errorf("wrong path: %s", r.URL.Path)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPost {
|
if r.Method != http.MethodPost {
|
||||||
t.Errorf("expected POST, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPatch {
|
if r.Method != http.MethodPatch {
|
||||||
t.Errorf("expected PATCH, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodDelete {
|
if r.Method != http.MethodDelete {
|
||||||
t.Errorf("expected DELETE, got %s", r.Method)
|
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
|
page := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
page++
|
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
|
page := 0
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
page++
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
json.NewEncoder(w).Encode(map[string]string{"message": "server error"})
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.Header().Set("X-Total-Count", "100")
|
w.Header().Set("X-Total-Count", "100")
|
||||||
json.NewEncoder(w).Encode([]testItem{{1, "a"}, {2, "b"}, {3, "c"}})
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"iter"
|
"iter"
|
||||||
|
|
||||||
"dappco.re/go/core/forge/types"
|
"dappco.re/go/core/forge/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TeamService handles team operations.
|
// TeamService handles team operations.
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// f := forge.NewForge("https://forge.lthn.ai", "token")
|
|
||||||
// _, err := f.Teams.ListMembers(ctx, 42)
|
|
||||||
type TeamService struct {
|
type TeamService struct {
|
||||||
Resource[types.Team, types.CreateTeamOption, types.EditTeamOption]
|
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.
|
// ListMembers returns all members of a team.
|
||||||
func (s *TeamService) ListMembers(ctx context.Context, teamID int64) ([]types.User, error) {
|
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)
|
return ListAll[types.User](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterMembers returns an iterator over all members of a team.
|
// IterMembers returns an iterator over all members of a team.
|
||||||
func (s *TeamService) IterMembers(ctx context.Context, teamID int64) iter.Seq2[types.User, error] {
|
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)
|
return ListIter[types.User](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddMember adds a user to a team.
|
// AddMember adds a user to a team.
|
||||||
func (s *TeamService) AddMember(ctx context.Context, teamID int64, username string) error {
|
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)
|
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.
|
// RemoveMember removes a user from a team.
|
||||||
func (s *TeamService) RemoveMember(ctx context.Context, teamID int64, username string) error {
|
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)
|
return s.client.Delete(ctx, path)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListRepos returns all repositories managed by a team.
|
// ListRepos returns all repositories managed by a team.
|
||||||
func (s *TeamService) ListRepos(ctx context.Context, teamID int64) ([]types.Repository, error) {
|
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)
|
return ListAll[types.Repository](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterRepos returns an iterator over all repositories managed by a team.
|
// 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] {
|
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)
|
return ListIter[types.Repository](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddRepo adds a repository to a team.
|
// AddRepo adds a repository to a team.
|
||||||
func (s *TeamService) AddRepo(ctx context.Context, teamID int64, org, repo string) error {
|
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)
|
return s.client.Put(ctx, path, nil, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveRepo removes a repository from a team.
|
// RemoveRepo removes a repository from a team.
|
||||||
func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error {
|
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)
|
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.
|
// ListOrgTeams returns all teams in an organisation.
|
||||||
func (s *TeamService) ListOrgTeams(ctx context.Context, org string) ([]types.Team, error) {
|
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)
|
return ListAll[types.Team](ctx, s.client, path, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IterOrgTeams returns an iterator over all teams in an organisation.
|
// IterOrgTeams returns an iterator over all teams in an organisation.
|
||||||
func (s *TeamService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[types.Team, error] {
|
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)
|
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 (
|
import (
|
||||||
"context"
|
"context"
|
||||||
json "github.com/goccy/go-json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"dappco.re/go/core/forge/types"
|
"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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
func TestTeamService_Good_ListMembers(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) {
|
|
||||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodGet {
|
if r.Method != http.MethodGet {
|
||||||
t.Errorf("expected GET, got %s", r.Method)
|
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) {
|
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.Method != http.MethodPut {
|
if r.Method != http.MethodPut {
|
||||||
t.Errorf("expected PUT, got %s", r.Method)
|
t.Errorf("expected PUT, got %s", r.Method)
|
||||||
|
|
@ -110,25 +79,3 @@ func TestTeamService_AddMember_Good(t *testing.T) {
|
||||||
t.Fatal(err)
|
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"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// ActionTask — ActionTask represents a ActionTask
|
// ActionTask — ActionTask represents a ActionTask
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ActionTask{DisplayTitle: "example"}
|
|
||||||
type ActionTask struct {
|
type ActionTask struct {
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
DisplayTitle string `json:"display_title,omitempty"`
|
DisplayTitle string `json:"display_title,omitempty"`
|
||||||
Event string `json:"event,omitempty"`
|
Event string `json:"event,omitempty"`
|
||||||
HeadBranch string `json:"head_branch,omitempty"`
|
HeadBranch string `json:"head_branch,omitempty"`
|
||||||
HeadSHA string `json:"head_sha,omitempty"`
|
HeadSHA string `json:"head_sha,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
RunNumber int64 `json:"run_number,omitempty"`
|
RunNumber int64 `json:"run_number,omitempty"`
|
||||||
RunStartedAt time.Time `json:"run_started_at,omitempty"`
|
RunStartedAt time.Time `json:"run_started_at,omitempty"`
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
WorkflowID string `json:"workflow_id,omitempty"`
|
WorkflowID string `json:"workflow_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionTaskResponse — ActionTaskResponse returns a ActionTask
|
// ActionTaskResponse — ActionTaskResponse returns a ActionTask
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ActionTaskResponse{TotalCount: 1}
|
|
||||||
type ActionTaskResponse struct {
|
type ActionTaskResponse struct {
|
||||||
Entries []*ActionTask `json:"workflow_runs,omitempty"`
|
Entries []*ActionTask `json:"workflow_runs,omitempty"`
|
||||||
TotalCount int64 `json:"total_count,omitempty"`
|
TotalCount int64 `json:"total_count,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActionVariable — ActionVariable return value of the query API
|
// ActionVariable — ActionVariable return value of the query API
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ActionVariable{Name: "example"}
|
|
||||||
type ActionVariable struct {
|
type ActionVariable struct {
|
||||||
Data string `json:"data,omitempty"` // the value of the variable
|
Data string `json:"data,omitempty"` // the value of the variable
|
||||||
Name string `json:"name,omitempty"` // the name 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
|
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
|
RepoID int64 `json:"repo_id,omitempty"` // the repository to which the variable belongs
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateVariableOption — CreateVariableOption the option when creating variable
|
// CreateVariableOption — CreateVariableOption the option when creating variable
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateVariableOption{Value: "example"}
|
|
||||||
type CreateVariableOption struct {
|
type CreateVariableOption struct {
|
||||||
Value string `json:"value"` // Value of the variable to create
|
Value string `json:"value"` // Value of the variable to create
|
||||||
}
|
}
|
||||||
|
|
||||||
// DispatchWorkflowOption — DispatchWorkflowOption options when dispatching a workflow
|
// DispatchWorkflowOption — DispatchWorkflowOption options when dispatching a workflow
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := DispatchWorkflowOption{Ref: "main"}
|
|
||||||
type DispatchWorkflowOption struct {
|
type DispatchWorkflowOption struct {
|
||||||
Inputs map[string]string `json:"inputs,omitempty"` // Input keys and values configured in the workflow file.
|
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
|
Ref string `json:"ref"` // Git reference for the workflow
|
||||||
}
|
}
|
||||||
|
|
||||||
// Secret — Secret represents a secret
|
// Secret — Secret represents a secret
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Secret{Name: "example"}
|
|
||||||
type Secret struct {
|
type Secret struct {
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
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
|
// UpdateVariableOption — UpdateVariableOption the option when updating variable
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := UpdateVariableOption{Value: "example"}
|
|
||||||
type UpdateVariableOption struct {
|
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.
|
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
|
Value string `json:"value"` // Value of the variable to update
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,30 +4,25 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Activity{RefName: "main"}
|
|
||||||
type Activity struct {
|
type Activity struct {
|
||||||
ActUser *User `json:"act_user,omitempty"`
|
ActUser *User `json:"act_user,omitempty"`
|
||||||
ActUserID int64 `json:"act_user_id,omitempty"`
|
ActUserID int64 `json:"act_user_id,omitempty"`
|
||||||
Comment *Comment `json:"comment,omitempty"`
|
Comment *Comment `json:"comment,omitempty"`
|
||||||
CommentID int64 `json:"comment_id,omitempty"`
|
CommentID int64 `json:"comment_id,omitempty"`
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
Created time.Time `json:"created,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
IsPrivate bool `json:"is_private,omitempty"`
|
IsPrivate bool `json:"is_private,omitempty"`
|
||||||
OpType string `json:"op_type,omitempty"` // the type of action
|
OpType string `json:"op_type,omitempty"` // the type of action
|
||||||
RefName string `json:"ref_name,omitempty"`
|
RefName string `json:"ref_name,omitempty"`
|
||||||
Repo *Repository `json:"repo,omitempty"`
|
Repo *Repository `json:"repo,omitempty"`
|
||||||
RepoID int64 `json:"repo_id,omitempty"`
|
RepoID int64 `json:"repo_id,omitempty"`
|
||||||
UserID int64 `json:"user_id,omitempty"`
|
UserID int64 `json:"user_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ActivityPub — ActivityPub type
|
// ActivityPub — ActivityPub type
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ActivityPub{Context: "example"}
|
|
||||||
type ActivityPub struct {
|
type ActivityPub struct {
|
||||||
Context string `json:"@context,omitempty"`
|
Context string `json:"@context,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,18 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// Cron — Cron represents a Cron task
|
// Cron — Cron represents a Cron task
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Cron{Name: "example"}
|
|
||||||
type Cron struct {
|
type Cron struct {
|
||||||
ExecTimes int64 `json:"exec_times,omitempty"`
|
ExecTimes int64 `json:"exec_times,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Next time.Time `json:"next,omitempty"`
|
Next time.Time `json:"next,omitempty"`
|
||||||
Prev time.Time `json:"prev,omitempty"`
|
Prev time.Time `json:"prev,omitempty"`
|
||||||
Schedule string `json:"schedule,omitempty"`
|
Schedule string `json:"schedule,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RenameUserOption — RenameUserOption options when renaming a user
|
// RenameUserOption — RenameUserOption options when renaming a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := RenameUserOption{NewName: "example"}
|
|
||||||
type RenameUserOption struct {
|
type RenameUserOption struct {
|
||||||
NewName string `json:"new_username"` // New username for this user. This name cannot be in use yet by any other user.
|
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"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// Branch — Branch represents a repository branch
|
// Branch — Branch represents a repository branch
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Branch{EffectiveBranchProtectionName: "main"}
|
|
||||||
type Branch struct {
|
type Branch struct {
|
||||||
Commit *PayloadCommit `json:"commit,omitempty"`
|
Commit *PayloadCommit `json:"commit,omitempty"`
|
||||||
EffectiveBranchProtectionName string `json:"effective_branch_protection_name,omitempty"`
|
EffectiveBranchProtectionName string `json:"effective_branch_protection_name,omitempty"`
|
||||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Protected bool `json:"protected,omitempty"`
|
Protected bool `json:"protected,omitempty"`
|
||||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||||
UserCanMerge bool `json:"user_can_merge,omitempty"`
|
UserCanMerge bool `json:"user_can_merge,omitempty"`
|
||||||
UserCanPush bool `json:"user_can_push,omitempty"`
|
UserCanPush bool `json:"user_can_push,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// BranchProtection — BranchProtection represents a branch protection for a repository
|
// BranchProtection — BranchProtection represents a branch protection for a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := BranchProtection{BranchName: "main"}
|
|
||||||
type BranchProtection struct {
|
type BranchProtection struct {
|
||||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||||
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||||
EnablePush bool `json:"enable_push,omitempty"`
|
EnablePush bool `json:"enable_push,omitempty"`
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||||
RuleName string `json:"rule_name,omitempty"`
|
RuleName string `json:"rule_name,omitempty"`
|
||||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBranchProtectionOption — CreateBranchProtectionOption options for creating a branch protection
|
// CreateBranchProtectionOption — CreateBranchProtectionOption options for creating a branch protection
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateBranchProtectionOption{BranchName: "main"}
|
|
||||||
type CreateBranchProtectionOption struct {
|
type CreateBranchProtectionOption struct {
|
||||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||||
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
BranchName string `json:"branch_name,omitempty"` // Deprecated: true
|
||||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||||
EnablePush bool `json:"enable_push,omitempty"`
|
EnablePush bool `json:"enable_push,omitempty"`
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||||
RuleName string `json:"rule_name,omitempty"`
|
RuleName string `json:"rule_name,omitempty"`
|
||||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateBranchRepoOption — CreateBranchRepoOption options when creating a branch in a repository
|
// CreateBranchRepoOption — CreateBranchRepoOption options when creating a branch in a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateBranchRepoOption{BranchName: "main"}
|
|
||||||
type CreateBranchRepoOption struct {
|
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
|
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
|
// EditBranchProtectionOption — EditBranchProtectionOption options for editing a branch protection
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditBranchProtectionOption{ApprovalsWhitelistTeams: []string{"example"}}
|
|
||||||
type EditBranchProtectionOption struct {
|
type EditBranchProtectionOption struct {
|
||||||
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
ApplyToAdmins bool `json:"apply_to_admins,omitempty"`
|
||||||
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
ApprovalsWhitelistTeams []string `json:"approvals_whitelist_teams,omitempty"`
|
||||||
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
ApprovalsWhitelistUsernames []string `json:"approvals_whitelist_username,omitempty"`
|
||||||
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
BlockOnOfficialReviewRequests bool `json:"block_on_official_review_requests,omitempty"`
|
||||||
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
BlockOnOutdatedBranch bool `json:"block_on_outdated_branch,omitempty"`
|
||||||
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
BlockOnRejectedReviews bool `json:"block_on_rejected_reviews,omitempty"`
|
||||||
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
DismissStaleApprovals bool `json:"dismiss_stale_approvals,omitempty"`
|
||||||
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
EnableApprovalsWhitelist bool `json:"enable_approvals_whitelist,omitempty"`
|
||||||
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
EnableMergeWhitelist bool `json:"enable_merge_whitelist,omitempty"`
|
||||||
EnablePush bool `json:"enable_push,omitempty"`
|
EnablePush bool `json:"enable_push,omitempty"`
|
||||||
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
EnablePushWhitelist bool `json:"enable_push_whitelist,omitempty"`
|
||||||
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
EnableStatusCheck bool `json:"enable_status_check,omitempty"`
|
||||||
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
IgnoreStaleApprovals bool `json:"ignore_stale_approvals,omitempty"`
|
||||||
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
MergeWhitelistTeams []string `json:"merge_whitelist_teams,omitempty"`
|
||||||
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
MergeWhitelistUsernames []string `json:"merge_whitelist_usernames,omitempty"`
|
||||||
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
ProtectedFilePatterns string `json:"protected_file_patterns,omitempty"`
|
||||||
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
PushWhitelistDeployKeys bool `json:"push_whitelist_deploy_keys,omitempty"`
|
||||||
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
PushWhitelistTeams []string `json:"push_whitelist_teams,omitempty"`
|
||||||
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
PushWhitelistUsernames []string `json:"push_whitelist_usernames,omitempty"`
|
||||||
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
RequireSignedCommits bool `json:"require_signed_commits,omitempty"`
|
||||||
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
RequiredApprovals int64 `json:"required_approvals,omitempty"`
|
||||||
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
StatusCheckContexts []string `json:"status_check_contexts,omitempty"`
|
||||||
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
UnprotectedFilePatterns string `json:"unprotected_file_patterns,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateBranchRepoOption — UpdateBranchRepoOption options when updating a branch in a repository
|
// UpdateBranchRepoOption — UpdateBranchRepoOption options when updating a branch in a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := UpdateBranchRepoOption{Name: "example"}
|
|
||||||
type UpdateBranchRepoOption struct {
|
type UpdateBranchRepoOption struct {
|
||||||
Name string `json:"name"` // New branch name
|
Name string `json:"name"` // New branch name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,21 +4,19 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// Comment — Comment represents a comment on a commit or issue
|
// Comment — Comment represents a comment on a commit or issue
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Comment{Body: "example"}
|
|
||||||
type Comment struct {
|
type Comment struct {
|
||||||
Attachments []*Attachment `json:"assets,omitempty"`
|
Attachments []*Attachment `json:"assets,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
IssueURL string `json:"issue_url,omitempty"`
|
IssueURL string `json:"issue_url,omitempty"`
|
||||||
OriginalAuthor string `json:"original_author,omitempty"`
|
OriginalAuthor string `json:"original_author,omitempty"`
|
||||||
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
||||||
PRURL string `json:"pull_request_url,omitempty"`
|
PRURL string `json:"pull_request_url,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,91 +4,65 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Commit{HTMLURL: "https://example.com"}
|
|
||||||
type Commit struct {
|
type Commit struct {
|
||||||
Author *User `json:"author,omitempty"`
|
Author *User `json:"author,omitempty"`
|
||||||
Commit *RepoCommit `json:"commit,omitempty"`
|
Commit *RepoCommit `json:"commit,omitempty"`
|
||||||
Committer *User `json:"committer,omitempty"`
|
Committer *User `json:"committer,omitempty"`
|
||||||
Created time.Time `json:"created,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
Files []*CommitAffectedFiles `json:"files,omitempty"`
|
Files []*CommitAffectedFiles `json:"files,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
Parents []*CommitMeta `json:"parents,omitempty"`
|
Parents []*CommitMeta `json:"parents,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Stats *CommitStats `json:"stats,omitempty"`
|
Stats *CommitStats `json:"stats,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitAffectedFiles — CommitAffectedFiles store information about files affected by the commit
|
// CommitAffectedFiles — CommitAffectedFiles store information about files affected by the commit
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CommitAffectedFiles{Filename: "example"}
|
|
||||||
type CommitAffectedFiles struct {
|
type CommitAffectedFiles struct {
|
||||||
Filename string `json:"filename,omitempty"`
|
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
|
// CommitDateOptions — CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CommitDateOptions{Author: time.Now()}
|
|
||||||
type CommitDateOptions struct {
|
type CommitDateOptions struct {
|
||||||
Author time.Time `json:"author,omitempty"`
|
Author time.Time `json:"author,omitempty"`
|
||||||
Committer time.Time `json:"committer,omitempty"`
|
Committer time.Time `json:"committer,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CommitMeta{SHA: "example"}
|
|
||||||
type CommitMeta struct {
|
type CommitMeta struct {
|
||||||
Created time.Time `json:"created,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitStats — CommitStats is statistics for a RepoCommit
|
// CommitStats — CommitStats is statistics for a RepoCommit
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CommitStats{Additions: 1}
|
|
||||||
type CommitStats struct {
|
type CommitStats struct {
|
||||||
Additions int64 `json:"additions,omitempty"`
|
Additions int64 `json:"additions,omitempty"`
|
||||||
Deletions int64 `json:"deletions,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
|
// CommitStatus — CommitStatus holds a single status of a single Commit
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CommitStatus{Description: "example"}
|
|
||||||
type CommitStatus struct {
|
type CommitStatus struct {
|
||||||
Context string `json:"context,omitempty"`
|
Context string `json:"context,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Creator *User `json:"creator,omitempty"`
|
Creator *User `json:"creator,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Status CommitStatusState `json:"status,omitempty"`
|
Status *CommitStatusState `json:"status,omitempty"`
|
||||||
TargetURL string `json:"target_url,omitempty"`
|
TargetURL string `json:"target_url,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CommitStatusState — CommitStatusState holds the state of a CommitStatus It can be "pending", "success", "error" and "failure"
|
// CommitStatusState — CommitStatusState holds the state of a CommitStatus It can be "pending", "success", "error" and "failure"
|
||||||
//
|
// CommitStatusState has no fields in the swagger spec.
|
||||||
// Usage:
|
type CommitStatusState struct{}
|
||||||
//
|
|
||||||
// opts := CommitStatusState("example")
|
|
||||||
type CommitStatusState string
|
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CommitUser{Name: "example"}
|
|
||||||
type CommitUser struct {
|
type CommitUser struct {
|
||||||
Date string `json:"date,omitempty"`
|
Date string `json:"date,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,53 +4,35 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// Attachment — Attachment a generic attachment
|
// Attachment — Attachment a generic attachment
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Attachment{Name: "example"}
|
|
||||||
type Attachment struct {
|
type Attachment struct {
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
DownloadCount int64 `json:"download_count,omitempty"`
|
DownloadCount int64 `json:"download_count,omitempty"`
|
||||||
DownloadURL string `json:"browser_download_url,omitempty"`
|
DownloadURL string `json:"browser_download_url,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
UUID string `json:"uuid,omitempty"`
|
UUID string `json:"uuid,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditAttachmentOptions — EditAttachmentOptions options for editing attachments
|
// EditAttachmentOptions — EditAttachmentOptions options for editing attachments
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditAttachmentOptions{Name: "example"}
|
|
||||||
type EditAttachmentOptions struct {
|
type EditAttachmentOptions struct {
|
||||||
DownloadURL string `json:"browser_download_url,omitempty"` // (Can only be set if existing attachment is of external type)
|
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
|
// Permission — Permission represents a set of permissions
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Permission{Admin: true}
|
|
||||||
type Permission struct {
|
type Permission struct {
|
||||||
Admin bool `json:"admin,omitempty"`
|
Admin bool `json:"admin,omitempty"`
|
||||||
Pull bool `json:"pull,omitempty"`
|
Pull bool `json:"pull,omitempty"`
|
||||||
Push bool `json:"push,omitempty"`
|
Push bool `json:"push,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StateType — StateType issue state type
|
// StateType is the state of an issue or PR: "open", "closed".
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := StateType("example")
|
|
||||||
type StateType string
|
type StateType string
|
||||||
|
|
||||||
// TimeStamp — TimeStamp defines a timestamp
|
// TimeStamp is a Forgejo timestamp string.
|
||||||
//
|
type TimeStamp string
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := TimeStamp(1)
|
|
||||||
type TimeStamp int64
|
|
||||||
|
|
|
||||||
153
types/content.go
153
types/content.go
|
|
@ -4,134 +4,101 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// ContentsResponse — ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content
|
// 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 {
|
type ContentsResponse struct {
|
||||||
Content string `json:"content,omitempty"` // `content` is populated when `type` is `file`, otherwise null
|
Content string `json:"content,omitempty"` // `content` is populated when `type` is `file`, otherwise null
|
||||||
DownloadURL string `json:"download_url,omitempty"`
|
DownloadURL string `json:"download_url,omitempty"`
|
||||||
Encoding string `json:"encoding,omitempty"` // `encoding` is populated when `type` is `file`, otherwise null
|
Encoding string `json:"encoding,omitempty"` // `encoding` is populated when `type` is `file`, otherwise null
|
||||||
GitURL string `json:"git_url,omitempty"`
|
GitURL string `json:"git_url,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
LastCommitSHA string `json:"last_commit_sha,omitempty"`
|
LastCommitSHA string `json:"last_commit_sha,omitempty"`
|
||||||
Links *FileLinksResponse `json:"_links,omitempty"`
|
Links *FileLinksResponse `json:"_links,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
SubmoduleGitURL string `json:"submodule_git_url,omitempty"` // `submodule_git_url` is populated when `type` is `submodule`, otherwise null
|
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
|
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`
|
Type string `json:"type,omitempty"` // `type` will be `file`, `dir`, `symlink`, or `submodule`
|
||||||
URL string `json:"url,omitempty"`
|
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)
|
// 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 {
|
type CreateFileOptions struct {
|
||||||
Author *Identity `json:"author,omitempty"`
|
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
|
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"`
|
Committer *Identity `json:"committer,omitempty"`
|
||||||
ContentBase64 string `json:"content"` // content must be base64 encoded
|
ContentBase64 string `json:"content"` // content must be base64 encoded
|
||||||
Dates *CommitDateOptions `json:"dates,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
|
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
|
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.
|
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)
|
// 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 {
|
type DeleteFileOptions struct {
|
||||||
Author *Identity `json:"author,omitempty"`
|
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
|
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"`
|
Committer *Identity `json:"committer,omitempty"`
|
||||||
Dates *CommitDateOptions `json:"dates,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
|
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
|
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
|
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.
|
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 {
|
type FileCommitResponse struct {
|
||||||
Author *CommitUser `json:"author,omitempty"`
|
Author *CommitUser `json:"author,omitempty"`
|
||||||
Committer *CommitUser `json:"committer,omitempty"`
|
Committer *CommitUser `json:"committer,omitempty"`
|
||||||
Created time.Time `json:"created,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Parents []*CommitMeta `json:"parents,omitempty"`
|
Parents []*CommitMeta `json:"parents,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Tree *CommitMeta `json:"tree,omitempty"`
|
Tree *CommitMeta `json:"tree,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileDeleteResponse — FileDeleteResponse contains information about a repo's file that was deleted
|
// FileDeleteResponse — FileDeleteResponse contains information about a repo's file that was deleted
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := FileDeleteResponse{Commit: &FileCommitResponse{}}
|
|
||||||
type FileDeleteResponse struct {
|
type FileDeleteResponse struct {
|
||||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||||
Content any `json:"content,omitempty"`
|
Content any `json:"content,omitempty"`
|
||||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileLinksResponse — FileLinksResponse contains the links for a repo's file
|
// FileLinksResponse — FileLinksResponse contains the links for a repo's file
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := FileLinksResponse{GitURL: "https://example.com"}
|
|
||||||
type FileLinksResponse struct {
|
type FileLinksResponse struct {
|
||||||
GitURL string `json:"git,omitempty"`
|
GitURL string `json:"git,omitempty"`
|
||||||
HTMLURL string `json:"html,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
|
// FileResponse — FileResponse contains information about a repo's file
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := FileResponse{Commit: &FileCommitResponse{}}
|
|
||||||
type FileResponse struct {
|
type FileResponse struct {
|
||||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||||
Content *ContentsResponse `json:"content,omitempty"`
|
Content *ContentsResponse `json:"content,omitempty"`
|
||||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FilesResponse — FilesResponse contains information about multiple files from a repo
|
// FilesResponse — FilesResponse contains information about multiple files from a repo
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := FilesResponse{Files: {}}
|
|
||||||
type FilesResponse struct {
|
type FilesResponse struct {
|
||||||
Commit *FileCommitResponse `json:"commit,omitempty"`
|
Commit *FileCommitResponse `json:"commit,omitempty"`
|
||||||
Files []*ContentsResponse `json:"files,omitempty"`
|
Files []*ContentsResponse `json:"files,omitempty"`
|
||||||
Verification *PayloadCommitVerification `json:"verification,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)
|
// 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 {
|
type UpdateFileOptions struct {
|
||||||
Author *Identity `json:"author,omitempty"`
|
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
|
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"`
|
Committer *Identity `json:"committer,omitempty"`
|
||||||
ContentBase64 string `json:"content"` // content must be base64 encoded
|
ContentBase64 string `json:"content"` // content must be base64 encoded
|
||||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
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
|
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
|
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
|
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
|
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.
|
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
|
package types
|
||||||
|
|
||||||
|
|
||||||
// APIError — APIError is an api error with a message
|
// APIError — APIError is an api error with a message
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := APIError{Message: "example"}
|
|
||||||
type APIError struct {
|
type APIError struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := APIForbiddenError{Message: "example"}
|
|
||||||
type APIForbiddenError struct {
|
type APIForbiddenError struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := APIInvalidTopicsError{InvalidTopics: []string{"example"}}
|
|
||||||
type APIInvalidTopicsError struct {
|
type APIInvalidTopicsError struct {
|
||||||
InvalidTopics []string `json:"invalidTopics,omitempty"`
|
InvalidTopics []string `json:"invalidTopics,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := APINotFound{Errors: []string{"example"}}
|
|
||||||
type APINotFound struct {
|
type APINotFound struct {
|
||||||
Errors []string `json:"errors,omitempty"`
|
Errors []string `json:"errors,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := APIRepoArchivedError{Message: "example"}
|
|
||||||
type APIRepoArchivedError struct {
|
type APIRepoArchivedError struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := APIUnauthorizedError{Message: "example"}
|
|
||||||
type APIUnauthorizedError struct {
|
type APIUnauthorizedError struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := APIValidationError{Message: "example"}
|
|
||||||
type APIValidationError struct {
|
type APIValidationError struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,61 +2,43 @@
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
|
||||||
// NodeInfo — NodeInfo contains standardized way of exposing metadata about a server running one of the distributed social networks
|
// 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 {
|
type NodeInfo struct {
|
||||||
Metadata map[string]any `json:"metadata,omitempty"`
|
Metadata map[string]any `json:"metadata,omitempty"`
|
||||||
OpenRegistrations bool `json:"openRegistrations,omitempty"`
|
OpenRegistrations bool `json:"openRegistrations,omitempty"`
|
||||||
Protocols []string `json:"protocols,omitempty"`
|
Protocols []string `json:"protocols,omitempty"`
|
||||||
Services *NodeInfoServices `json:"services,omitempty"`
|
Services *NodeInfoServices `json:"services,omitempty"`
|
||||||
Software *NodeInfoSoftware `json:"software,omitempty"`
|
Software *NodeInfoSoftware `json:"software,omitempty"`
|
||||||
Usage *NodeInfoUsage `json:"usage,omitempty"`
|
Usage *NodeInfoUsage `json:"usage,omitempty"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfoServices — NodeInfoServices contains the third party sites this server can connect to via their application API
|
// 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 {
|
type NodeInfoServices struct {
|
||||||
Inbound []string `json:"inbound,omitempty"`
|
Inbound []string `json:"inbound,omitempty"`
|
||||||
Outbound []string `json:"outbound,omitempty"`
|
Outbound []string `json:"outbound,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfoSoftware — NodeInfoSoftware contains Metadata about server software in use
|
// NodeInfoSoftware — NodeInfoSoftware contains Metadata about server software in use
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NodeInfoSoftware{Name: "example"}
|
|
||||||
type NodeInfoSoftware struct {
|
type NodeInfoSoftware struct {
|
||||||
Homepage string `json:"homepage,omitempty"`
|
Homepage string `json:"homepage,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Repository string `json:"repository,omitempty"`
|
Repository string `json:"repository,omitempty"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfoUsage — NodeInfoUsage contains usage statistics for this server
|
// NodeInfoUsage — NodeInfoUsage contains usage statistics for this server
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NodeInfoUsage{LocalComments: 1}
|
|
||||||
type NodeInfoUsage struct {
|
type NodeInfoUsage struct {
|
||||||
LocalComments int64 `json:"localComments,omitempty"`
|
LocalComments int64 `json:"localComments,omitempty"`
|
||||||
LocalPosts int64 `json:"localPosts,omitempty"`
|
LocalPosts int64 `json:"localPosts,omitempty"`
|
||||||
Users *NodeInfoUsageUsers `json:"users,omitempty"`
|
Users *NodeInfoUsageUsers `json:"users,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NodeInfoUsageUsers — NodeInfoUsageUsers contains statistics about the users of this server
|
// NodeInfoUsageUsers — NodeInfoUsageUsers contains statistics about the users of this server
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NodeInfoUsageUsers{ActiveHalfyear: 1}
|
|
||||||
type NodeInfoUsageUsers struct {
|
type NodeInfoUsageUsers struct {
|
||||||
ActiveHalfyear int64 `json:"activeHalfyear,omitempty"`
|
ActiveHalfyear int64 `json:"activeHalfyear,omitempty"`
|
||||||
ActiveMonth int64 `json:"activeMonth,omitempty"`
|
ActiveMonth int64 `json:"activeMonth,omitempty"`
|
||||||
Total int64 `json:"total,omitempty"`
|
Total int64 `json:"total,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
120
types/git.go
120
types/git.go
|
|
@ -2,133 +2,93 @@
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
|
||||||
// AnnotatedTag — AnnotatedTag represents an annotated tag
|
// AnnotatedTag — AnnotatedTag represents an annotated tag
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := AnnotatedTag{Message: "example"}
|
|
||||||
type AnnotatedTag struct {
|
type AnnotatedTag struct {
|
||||||
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count,omitempty"`
|
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Object *AnnotatedTagObject `json:"object,omitempty"`
|
Object *AnnotatedTagObject `json:"object,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Tag string `json:"tag,omitempty"`
|
Tag string `json:"tag,omitempty"`
|
||||||
Tagger *CommitUser `json:"tagger,omitempty"`
|
Tagger *CommitUser `json:"tagger,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnotatedTagObject — AnnotatedTagObject contains meta information of the tag object
|
// AnnotatedTagObject — AnnotatedTagObject contains meta information of the tag object
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := AnnotatedTagObject{SHA: "example"}
|
|
||||||
type AnnotatedTagObject struct {
|
type AnnotatedTagObject struct {
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Type string `json:"type,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
|
// ChangedFile — ChangedFile store information about files affected by the pull request
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ChangedFile{ContentsURL: "https://example.com"}
|
|
||||||
type ChangedFile struct {
|
type ChangedFile struct {
|
||||||
Additions int64 `json:"additions,omitempty"`
|
Additions int64 `json:"additions,omitempty"`
|
||||||
Changes int64 `json:"changes,omitempty"`
|
Changes int64 `json:"changes,omitempty"`
|
||||||
ContentsURL string `json:"contents_url,omitempty"`
|
ContentsURL string `json:"contents_url,omitempty"`
|
||||||
Deletions int64 `json:"deletions,omitempty"`
|
Deletions int64 `json:"deletions,omitempty"`
|
||||||
Filename string `json:"filename,omitempty"`
|
Filename string `json:"filename,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
PreviousFilename string `json:"previous_filename,omitempty"`
|
PreviousFilename string `json:"previous_filename,omitempty"`
|
||||||
RawURL string `json:"raw_url,omitempty"`
|
RawURL string `json:"raw_url,omitempty"`
|
||||||
Status string `json:"status,omitempty"`
|
Status string `json:"status,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditGitHookOption — EditGitHookOption options when modifying one Git hook
|
// EditGitHookOption — EditGitHookOption options when modifying one Git hook
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditGitHookOption{Content: "example"}
|
|
||||||
type EditGitHookOption struct {
|
type EditGitHookOption struct {
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitBlobResponse — GitBlobResponse represents a git blob
|
// GitBlobResponse — GitBlobResponse represents a git blob
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GitBlobResponse{Content: "example"}
|
|
||||||
type GitBlobResponse struct {
|
type GitBlobResponse struct {
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
Encoding string `json:"encoding,omitempty"`
|
Encoding string `json:"encoding,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitEntry — GitEntry represents a git tree
|
// GitEntry — GitEntry represents a git tree
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GitEntry{Mode: "example"}
|
|
||||||
type GitEntry struct {
|
type GitEntry struct {
|
||||||
Mode string `json:"mode,omitempty"`
|
Mode string `json:"mode,omitempty"`
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitHook — GitHook represents a Git repository hook
|
// GitHook — GitHook represents a Git repository hook
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GitHook{Name: "example"}
|
|
||||||
type GitHook struct {
|
type GitHook struct {
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
IsActive bool `json:"is_active,omitempty"`
|
IsActive bool `json:"is_active,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GitObject{SHA: "example"}
|
|
||||||
type GitObject struct {
|
type GitObject struct {
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitTreeResponse — GitTreeResponse returns a git tree
|
// GitTreeResponse — GitTreeResponse returns a git tree
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GitTreeResponse{SHA: "example"}
|
|
||||||
type GitTreeResponse struct {
|
type GitTreeResponse struct {
|
||||||
Entries []*GitEntry `json:"tree,omitempty"`
|
Entries []*GitEntry `json:"tree,omitempty"`
|
||||||
Page int64 `json:"page,omitempty"`
|
Page int64 `json:"page,omitempty"`
|
||||||
SHA string `json:"sha,omitempty"`
|
SHA string `json:"sha,omitempty"`
|
||||||
TotalCount int64 `json:"total_count,omitempty"`
|
TotalCount int64 `json:"total_count,omitempty"`
|
||||||
Truncated bool `json:"truncated,omitempty"`
|
Truncated bool `json:"truncated,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Note — Note contains information related to a git note
|
// Note — Note contains information related to a git note
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Note{Message: "example"}
|
|
||||||
type Note struct {
|
type Note struct {
|
||||||
Commit *Commit `json:"commit,omitempty"`
|
Commit *Commit `json:"commit,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NoteOptions{Message: "example"}
|
|
||||||
type NoteOptions struct {
|
type NoteOptions struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
103
types/hook.go
103
types/hook.go
|
|
@ -4,87 +4,66 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// CreateHookOption — CreateHookOption options when create a hook
|
// CreateHookOption — CreateHookOption options when create a hook
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateHookOption{Type: "example"}
|
|
||||||
type CreateHookOption struct {
|
type CreateHookOption struct {
|
||||||
Active bool `json:"active,omitempty"`
|
Active bool `json:"active,omitempty"`
|
||||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||||
BranchFilter string `json:"branch_filter,omitempty"`
|
BranchFilter string `json:"branch_filter,omitempty"`
|
||||||
Config *CreateHookOptionConfig `json:"config"`
|
Config *CreateHookOptionConfig `json:"config"`
|
||||||
Events []string `json:"events,omitempty"`
|
Events []string `json:"events,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateHookOptionConfig — CreateHookOptionConfig has all config options in it required are "content_type" and "url" Required
|
// CreateHookOptionConfig — CreateHookOptionConfig has all config options in it required are "content_type" and "url" Required
|
||||||
//
|
// CreateHookOptionConfig has no fields in the swagger spec.
|
||||||
// Usage:
|
type CreateHookOptionConfig struct{}
|
||||||
//
|
|
||||||
// opts := CreateHookOptionConfig(map[string]any{"key": "value"})
|
|
||||||
type CreateHookOptionConfig map[string]any
|
|
||||||
|
|
||||||
// EditHookOption — EditHookOption options when modify one hook
|
// EditHookOption — EditHookOption options when modify one hook
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditHookOption{AuthorizationHeader: "example"}
|
|
||||||
type EditHookOption struct {
|
type EditHookOption struct {
|
||||||
Active bool `json:"active,omitempty"`
|
Active bool `json:"active,omitempty"`
|
||||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||||
BranchFilter string `json:"branch_filter,omitempty"`
|
BranchFilter string `json:"branch_filter,omitempty"`
|
||||||
Config map[string]string `json:"config,omitempty"`
|
Config map[string]any `json:"config,omitempty"`
|
||||||
Events []string `json:"events,omitempty"`
|
Events []string `json:"events,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook — Hook a hook is a web hook when one repository changed
|
// Hook — Hook a hook is a web hook when one repository changed
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Hook{AuthorizationHeader: "example"}
|
|
||||||
type Hook struct {
|
type Hook struct {
|
||||||
Active bool `json:"active,omitempty"`
|
Active bool `json:"active,omitempty"`
|
||||||
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
AuthorizationHeader string `json:"authorization_header,omitempty"`
|
||||||
BranchFilter string `json:"branch_filter,omitempty"`
|
BranchFilter string `json:"branch_filter,omitempty"`
|
||||||
Config map[string]string `json:"config,omitempty"` // Deprecated: use Metadata instead
|
Config map[string]any `json:"config,omitempty"` // Deprecated: use Metadata instead
|
||||||
ContentType string `json:"content_type,omitempty"`
|
ContentType string `json:"content_type,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Events []string `json:"events,omitempty"`
|
Events []string `json:"events,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Metadata any `json:"metadata,omitempty"`
|
Metadata any `json:"metadata,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayloadCommit — PayloadCommit represents a commit
|
// PayloadCommit — PayloadCommit represents a commit
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PayloadCommit{Added: []string{"example"}}
|
|
||||||
type PayloadCommit struct {
|
type PayloadCommit struct {
|
||||||
Added []string `json:"added,omitempty"`
|
Added []string `json:"added,omitempty"`
|
||||||
Author *PayloadUser `json:"author,omitempty"`
|
Author *PayloadUser `json:"author,omitempty"`
|
||||||
Committer *PayloadUser `json:"committer,omitempty"`
|
Committer *PayloadUser `json:"committer,omitempty"`
|
||||||
ID string `json:"id,omitempty"` // sha1 hash of the commit
|
ID string `json:"id,omitempty"` // sha1 hash of the commit
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Modified []string `json:"modified,omitempty"`
|
Modified []string `json:"modified,omitempty"`
|
||||||
Removed []string `json:"removed,omitempty"`
|
Removed []string `json:"removed,omitempty"`
|
||||||
Timestamp time.Time `json:"timestamp,omitempty"`
|
Timestamp time.Time `json:"timestamp,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayloadCommitVerification — PayloadCommitVerification represents the GPG verification of a commit
|
// PayloadCommitVerification — PayloadCommitVerification represents the GPG verification of a commit
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PayloadCommitVerification{Payload: "example"}
|
|
||||||
type PayloadCommitVerification struct {
|
type PayloadCommitVerification struct {
|
||||||
Payload string `json:"payload,omitempty"`
|
Payload string `json:"payload,omitempty"`
|
||||||
Reason string `json:"reason,omitempty"`
|
Reason string `json:"reason,omitempty"`
|
||||||
Signature string `json:"signature,omitempty"`
|
Signature string `json:"signature,omitempty"`
|
||||||
Signer *PayloadUser `json:"signer,omitempty"`
|
Signer *PayloadUser `json:"signer,omitempty"`
|
||||||
Verified bool `json:"verified,omitempty"`
|
Verified bool `json:"verified,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
210
types/issue.go
210
types/issue.go
|
|
@ -4,200 +4,142 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// CreateIssueCommentOption — CreateIssueCommentOption options for creating a comment on an issue
|
// CreateIssueCommentOption — CreateIssueCommentOption options for creating a comment on an issue
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateIssueCommentOption{Body: "example"}
|
|
||||||
type CreateIssueCommentOption struct {
|
type CreateIssueCommentOption struct {
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated *time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateIssueOption — CreateIssueOption options to create one issue
|
// CreateIssueOption — CreateIssueOption options to create one issue
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateIssueOption{Title: "example"}
|
|
||||||
type CreateIssueOption struct {
|
type CreateIssueOption struct {
|
||||||
Assignee string `json:"assignee,omitempty"` // deprecated
|
Assignee string `json:"assignee,omitempty"` // deprecated
|
||||||
Assignees []string `json:"assignees,omitempty"`
|
Assignees []string `json:"assignees,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Closed bool `json:"closed,omitempty"`
|
Closed bool `json:"closed,omitempty"`
|
||||||
Deadline time.Time `json:"due_date,omitempty"`
|
Deadline time.Time `json:"due_date,omitempty"`
|
||||||
Labels []int64 `json:"labels,omitempty"` // list of label ids
|
Labels []int64 `json:"labels,omitempty"` // list of label ids
|
||||||
Milestone int64 `json:"milestone,omitempty"` // milestone id
|
Milestone int64 `json:"milestone,omitempty"` // milestone id
|
||||||
Ref string `json:"ref,omitempty"`
|
Ref string `json:"ref,omitempty"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditDeadlineOption — EditDeadlineOption options for creating a deadline
|
// EditDeadlineOption — EditDeadlineOption options for creating a deadline
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditDeadlineOption{Deadline: time.Now()}
|
|
||||||
type EditDeadlineOption struct {
|
type EditDeadlineOption struct {
|
||||||
Deadline time.Time `json:"due_date"`
|
Deadline time.Time `json:"due_date"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditIssueCommentOption — EditIssueCommentOption options for editing a comment
|
// EditIssueCommentOption — EditIssueCommentOption options for editing a comment
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditIssueCommentOption{Body: "example"}
|
|
||||||
type EditIssueCommentOption struct {
|
type EditIssueCommentOption struct {
|
||||||
Body string `json:"body"`
|
Body string `json:"body"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditIssueOption — EditIssueOption options for editing an issue
|
// EditIssueOption — EditIssueOption options for editing an issue
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditIssueOption{Body: "example"}
|
|
||||||
type EditIssueOption struct {
|
type EditIssueOption struct {
|
||||||
Assignee string `json:"assignee,omitempty"` // deprecated
|
Assignee string `json:"assignee,omitempty"` // deprecated
|
||||||
Assignees []string `json:"assignees,omitempty"`
|
Assignees []string `json:"assignees,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Deadline time.Time `json:"due_date,omitempty"`
|
Deadline time.Time `json:"due_date,omitempty"`
|
||||||
Milestone int64 `json:"milestone,omitempty"`
|
Milestone int64 `json:"milestone,omitempty"`
|
||||||
Ref string `json:"ref,omitempty"`
|
Ref string `json:"ref,omitempty"`
|
||||||
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
||||||
State string `json:"state,omitempty"`
|
State string `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Issue — Issue represents an issue in a repository
|
// Issue — Issue represents an issue in a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Issue{Body: "example"}
|
|
||||||
type Issue struct {
|
type Issue struct {
|
||||||
Assignee *User `json:"assignee,omitempty"`
|
Assignee *User `json:"assignee,omitempty"`
|
||||||
Assignees []*User `json:"assignees,omitempty"`
|
Assignees []*User `json:"assignees,omitempty"`
|
||||||
Attachments []*Attachment `json:"assets,omitempty"`
|
Attachments []*Attachment `json:"assets,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Closed time.Time `json:"closed_at,omitempty"`
|
Closed time.Time `json:"closed_at,omitempty"`
|
||||||
Comments int64 `json:"comments,omitempty"`
|
Comments int64 `json:"comments,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Deadline time.Time `json:"due_date,omitempty"`
|
Deadline time.Time `json:"due_date,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Index int64 `json:"number,omitempty"`
|
Index int64 `json:"number,omitempty"`
|
||||||
IsLocked bool `json:"is_locked,omitempty"`
|
IsLocked bool `json:"is_locked,omitempty"`
|
||||||
Labels []*Label `json:"labels,omitempty"`
|
Labels []*Label `json:"labels,omitempty"`
|
||||||
Milestone *Milestone `json:"milestone,omitempty"`
|
Milestone *Milestone `json:"milestone,omitempty"`
|
||||||
OriginalAuthor string `json:"original_author,omitempty"`
|
OriginalAuthor string `json:"original_author,omitempty"`
|
||||||
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
OriginalAuthorID int64 `json:"original_author_id,omitempty"`
|
||||||
PinOrder int64 `json:"pin_order,omitempty"`
|
PinOrder int64 `json:"pin_order,omitempty"`
|
||||||
PullRequest *PullRequestMeta `json:"pull_request,omitempty"`
|
PullRequest *PullRequestMeta `json:"pull_request,omitempty"`
|
||||||
Ref string `json:"ref,omitempty"`
|
Ref string `json:"ref,omitempty"`
|
||||||
Repository *RepositoryMeta `json:"repository,omitempty"`
|
Repository *RepositoryMeta `json:"repository,omitempty"`
|
||||||
State StateType `json:"state,omitempty"`
|
State StateType `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueConfig{BlankIssuesEnabled: true}
|
|
||||||
type IssueConfig struct {
|
type IssueConfig struct {
|
||||||
BlankIssuesEnabled bool `json:"blank_issues_enabled,omitempty"`
|
BlankIssuesEnabled bool `json:"blank_issues_enabled,omitempty"`
|
||||||
ContactLinks []*IssueConfigContactLink `json:"contact_links,omitempty"`
|
ContactLinks []*IssueConfigContactLink `json:"contact_links,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueConfigContactLink{Name: "example"}
|
|
||||||
type IssueConfigContactLink struct {
|
type IssueConfigContactLink struct {
|
||||||
About string `json:"about,omitempty"`
|
About string `json:"about,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueConfigValidation{Message: "example"}
|
|
||||||
type IssueConfigValidation struct {
|
type IssueConfigValidation struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Valid bool `json:"valid,omitempty"`
|
Valid bool `json:"valid,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssueDeadline — IssueDeadline represents an issue deadline
|
// IssueDeadline — IssueDeadline represents an issue deadline
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueDeadline{Deadline: time.Now()}
|
|
||||||
type IssueDeadline struct {
|
type IssueDeadline struct {
|
||||||
Deadline time.Time `json:"due_date,omitempty"`
|
Deadline time.Time `json:"due_date,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssueFormField — IssueFormField represents a form field
|
// IssueFormField — IssueFormField represents a form field
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueFormField{ID: "example"}
|
|
||||||
type IssueFormField struct {
|
type IssueFormField struct {
|
||||||
Attributes map[string]any `json:"attributes,omitempty"`
|
Attributes map[string]any `json:"attributes,omitempty"`
|
||||||
ID string `json:"id,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Type IssueFormFieldType `json:"type,omitempty"`
|
Type *IssueFormFieldType `json:"type,omitempty"`
|
||||||
Validations map[string]any `json:"validations,omitempty"`
|
Validations map[string]any `json:"validations,omitempty"`
|
||||||
Visible []IssueFormFieldVisible `json:"visible,omitempty"`
|
Visible []*IssueFormFieldVisible `json:"visible,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
// IssueFormFieldType has no fields in the swagger spec.
|
||||||
//
|
type IssueFormFieldType struct{}
|
||||||
// opts := IssueFormFieldType("example")
|
|
||||||
type IssueFormFieldType string
|
|
||||||
|
|
||||||
// IssueFormFieldVisible — IssueFormFieldVisible defines issue form field visible
|
// IssueFormFieldVisible — IssueFormFieldVisible defines issue form field visible
|
||||||
//
|
// IssueFormFieldVisible has no fields in the swagger spec.
|
||||||
// Usage:
|
type IssueFormFieldVisible struct{}
|
||||||
//
|
|
||||||
// opts := IssueFormFieldVisible("example")
|
|
||||||
type IssueFormFieldVisible string
|
|
||||||
|
|
||||||
// IssueLabelsOption — IssueLabelsOption a collection of labels
|
// IssueLabelsOption — IssueLabelsOption a collection of labels
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueLabelsOption{Updated: time.Now()}
|
|
||||||
type IssueLabelsOption struct {
|
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"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssueMeta — IssueMeta basic issue information
|
// IssueMeta — IssueMeta basic issue information
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueMeta{Name: "example"}
|
|
||||||
type IssueMeta struct {
|
type IssueMeta struct {
|
||||||
Index int64 `json:"index,omitempty"`
|
Index int64 `json:"index,omitempty"`
|
||||||
Name string `json:"repo,omitempty"`
|
Name string `json:"repo,omitempty"`
|
||||||
Owner string `json:"owner,omitempty"`
|
Owner string `json:"owner,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IssueTemplate — IssueTemplate represents an issue template for a repository
|
// IssueTemplate — IssueTemplate represents an issue template for a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := IssueTemplate{FileName: "example"}
|
|
||||||
type IssueTemplate struct {
|
type IssueTemplate struct {
|
||||||
About string `json:"about,omitempty"`
|
About string `json:"about,omitempty"`
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
Fields []*IssueFormField `json:"body,omitempty"`
|
Fields []*IssueFormField `json:"body,omitempty"`
|
||||||
FileName string `json:"file_name,omitempty"`
|
FileName string `json:"file_name,omitempty"`
|
||||||
Labels IssueTemplateLabels `json:"labels,omitempty"`
|
Labels *IssueTemplateLabels `json:"labels,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Ref string `json:"ref,omitempty"`
|
Ref string `json:"ref,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
// IssueTemplateLabels has no fields in the swagger spec.
|
||||||
//
|
type IssueTemplateLabels struct{}
|
||||||
// opts := IssueTemplateLabels([]string{"example"})
|
|
||||||
type IssueTemplateLabels []string
|
|
||||||
|
|
|
||||||
100
types/key.go
100
types/key.go
|
|
@ -4,88 +4,66 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// CreateGPGKeyOption — CreateGPGKeyOption options create user GPG key
|
// CreateGPGKeyOption — CreateGPGKeyOption options create user GPG key
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateGPGKeyOption{ArmoredKey: "example"}
|
|
||||||
type CreateGPGKeyOption struct {
|
type CreateGPGKeyOption struct {
|
||||||
ArmoredKey string `json:"armored_public_key"` // An armored GPG key to add
|
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
|
// CreateKeyOption — CreateKeyOption options when creating a key
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateKeyOption{Title: "example"}
|
|
||||||
type CreateKeyOption struct {
|
type CreateKeyOption struct {
|
||||||
Key string `json:"key"` // An armored SSH 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
|
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
|
Title string `json:"title"` // Title of the key to add
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeployKey — DeployKey a deploy key
|
// DeployKey — DeployKey a deploy key
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := DeployKey{Title: "example"}
|
|
||||||
type DeployKey struct {
|
type DeployKey struct {
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
KeyID int64 `json:"key_id,omitempty"`
|
KeyID int64 `json:"key_id,omitempty"`
|
||||||
ReadOnly bool `json:"read_only,omitempty"`
|
ReadOnly bool `json:"read_only,omitempty"`
|
||||||
Repository *Repository `json:"repository,omitempty"`
|
Repository *Repository `json:"repository,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GPGKey — GPGKey a user GPG key to sign commit and tag in repository
|
// GPGKey — GPGKey a user GPG key to sign commit and tag in repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GPGKey{KeyID: "example"}
|
|
||||||
type GPGKey struct {
|
type GPGKey struct {
|
||||||
CanCertify bool `json:"can_certify,omitempty"`
|
CanCertify bool `json:"can_certify,omitempty"`
|
||||||
CanEncryptComms bool `json:"can_encrypt_comms,omitempty"`
|
CanEncryptComms bool `json:"can_encrypt_comms,omitempty"`
|
||||||
CanEncryptStorage bool `json:"can_encrypt_storage,omitempty"`
|
CanEncryptStorage bool `json:"can_encrypt_storage,omitempty"`
|
||||||
CanSign bool `json:"can_sign,omitempty"`
|
CanSign bool `json:"can_sign,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Emails []*GPGKeyEmail `json:"emails,omitempty"`
|
Emails []*GPGKeyEmail `json:"emails,omitempty"`
|
||||||
Expires time.Time `json:"expires_at,omitempty"`
|
Expires time.Time `json:"expires_at,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
KeyID string `json:"key_id,omitempty"`
|
KeyID string `json:"key_id,omitempty"`
|
||||||
PrimaryKeyID string `json:"primary_key_id,omitempty"`
|
PrimaryKeyID string `json:"primary_key_id,omitempty"`
|
||||||
PublicKey string `json:"public_key,omitempty"`
|
PublicKey string `json:"public_key,omitempty"`
|
||||||
SubsKey []*GPGKey `json:"subkeys,omitempty"`
|
SubsKey []*GPGKey `json:"subkeys,omitempty"`
|
||||||
Verified bool `json:"verified,omitempty"`
|
Verified bool `json:"verified,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GPGKeyEmail — GPGKeyEmail an email attached to a GPGKey
|
// GPGKeyEmail — GPGKeyEmail an email attached to a GPGKey
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GPGKeyEmail{Email: "alice@example.com"}
|
|
||||||
type GPGKeyEmail struct {
|
type GPGKeyEmail struct {
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
Verified bool `json:"verified,omitempty"`
|
Verified bool `json:"verified,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PublicKey — PublicKey publickey is a user key to push code to repository
|
// PublicKey — PublicKey publickey is a user key to push code to repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PublicKey{Title: "example"}
|
|
||||||
type PublicKey struct {
|
type PublicKey struct {
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Fingerprint string `json:"fingerprint,omitempty"`
|
Fingerprint string `json:"fingerprint,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
KeyType string `json:"key_type,omitempty"`
|
KeyType string `json:"key_type,omitempty"`
|
||||||
ReadOnly bool `json:"read_only,omitempty"`
|
ReadOnly bool `json:"read_only,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,64 +4,46 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// CreateLabelOption — CreateLabelOption options for creating a label
|
// CreateLabelOption — CreateLabelOption options for creating a label
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateLabelOption{Name: "example"}
|
|
||||||
type CreateLabelOption struct {
|
type CreateLabelOption struct {
|
||||||
Color string `json:"color"`
|
Color string `json:"color"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Exclusive bool `json:"exclusive,omitempty"`
|
Exclusive bool `json:"exclusive,omitempty"`
|
||||||
IsArchived bool `json:"is_archived,omitempty"`
|
IsArchived bool `json:"is_archived,omitempty"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteLabelsOption — DeleteLabelOption options for deleting a label
|
// DeleteLabelsOption — DeleteLabelOption options for deleting a label
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := DeleteLabelsOption{Updated: time.Now()}
|
|
||||||
type DeleteLabelsOption struct {
|
type DeleteLabelsOption struct {
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditLabelOption — EditLabelOption options for editing a label
|
// EditLabelOption — EditLabelOption options for editing a label
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditLabelOption{Description: "example"}
|
|
||||||
type EditLabelOption struct {
|
type EditLabelOption struct {
|
||||||
Color string `json:"color,omitempty"`
|
Color string `json:"color,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Exclusive bool `json:"exclusive,omitempty"`
|
Exclusive bool `json:"exclusive,omitempty"`
|
||||||
IsArchived bool `json:"is_archived,omitempty"`
|
IsArchived bool `json:"is_archived,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Label — Label a label to an issue or a pr
|
// Label — Label a label to an issue or a pr
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Label{Description: "example"}
|
|
||||||
type Label struct {
|
type Label struct {
|
||||||
Color string `json:"color,omitempty"`
|
Color string `json:"color,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Exclusive bool `json:"exclusive,omitempty"`
|
Exclusive bool `json:"exclusive,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
IsArchived bool `json:"is_archived,omitempty"`
|
IsArchived bool `json:"is_archived,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LabelTemplate — LabelTemplate info of a Label template
|
// LabelTemplate — LabelTemplate info of a Label template
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := LabelTemplate{Description: "example"}
|
|
||||||
type LabelTemplate struct {
|
type LabelTemplate struct {
|
||||||
Color string `json:"color,omitempty"`
|
Color string `json:"color,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Exclusive bool `json:"exclusive,omitempty"`
|
Exclusive bool `json:"exclusive,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,44 +4,34 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// CreateMilestoneOption — CreateMilestoneOption options for creating a milestone
|
// CreateMilestoneOption — CreateMilestoneOption options for creating a milestone
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateMilestoneOption{Description: "example"}
|
|
||||||
type CreateMilestoneOption struct {
|
type CreateMilestoneOption struct {
|
||||||
Deadline time.Time `json:"due_on,omitempty"`
|
Deadline time.Time `json:"due_on,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
State string `json:"state,omitempty"`
|
State string `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditMilestoneOption — EditMilestoneOption options for editing a milestone
|
// EditMilestoneOption — EditMilestoneOption options for editing a milestone
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditMilestoneOption{Description: "example"}
|
|
||||||
type EditMilestoneOption struct {
|
type EditMilestoneOption struct {
|
||||||
Deadline time.Time `json:"due_on,omitempty"`
|
Deadline time.Time `json:"due_on,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
State string `json:"state,omitempty"`
|
State string `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Milestone — Milestone milestone is a collection of issues on one repository
|
// Milestone — Milestone milestone is a collection of issues on one repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Milestone{Description: "example"}
|
|
||||||
type Milestone struct {
|
type Milestone struct {
|
||||||
Closed time.Time `json:"closed_at,omitempty"`
|
Closed time.Time `json:"closed_at,omitempty"`
|
||||||
ClosedIssues int64 `json:"closed_issues,omitempty"`
|
ClosedIssues int64 `json:"closed_issues,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Deadline time.Time `json:"due_on,omitempty"`
|
Deadline time.Time `json:"due_on,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
OpenIssues int64 `json:"open_issues,omitempty"`
|
OpenIssues int64 `json:"open_issues,omitempty"`
|
||||||
State StateType `json:"state,omitempty"`
|
State StateType `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
356
types/misc.go
356
types/misc.go
|
|
@ -4,360 +4,252 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// AddCollaboratorOption — AddCollaboratorOption options when adding a user as a collaborator of a repository
|
// AddCollaboratorOption — AddCollaboratorOption options when adding a user as a collaborator of a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := AddCollaboratorOption{Permission: "example"}
|
|
||||||
type AddCollaboratorOption struct {
|
type AddCollaboratorOption struct {
|
||||||
Permission string `json:"permission,omitempty"`
|
Permission string `json:"permission,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddTimeOption — AddTimeOption options for adding time to an issue
|
// AddTimeOption — AddTimeOption options for adding time to an issue
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := AddTimeOption{Time: 1}
|
|
||||||
type AddTimeOption struct {
|
type AddTimeOption struct {
|
||||||
Created time.Time `json:"created,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
Time int64 `json:"time"` // time in seconds
|
Time int64 `json:"time"` // time in seconds
|
||||||
User string `json:"user_name,omitempty"` // User who spent the time (optional)
|
User string `json:"user_name,omitempty"` // User who spent the time (optional)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChangeFileOperation — ChangeFileOperation for creating, updating or deleting a file
|
// ChangeFileOperation — ChangeFileOperation for creating, updating or deleting a file
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ChangeFileOperation{Operation: "example"}
|
|
||||||
type ChangeFileOperation struct {
|
type ChangeFileOperation struct {
|
||||||
ContentBase64 string `json:"content,omitempty"` // new or updated file content, must be base64 encoded
|
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
|
FromPath string `json:"from_path,omitempty"` // old path of the file to move
|
||||||
Operation string `json:"operation"` // indicates what to do with the file
|
Operation string `json:"operation"` // indicates what to do with the file
|
||||||
Path string `json:"path"` // path to the existing or new 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
|
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)
|
// 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 {
|
type ChangeFilesOptions struct {
|
||||||
Author *Identity `json:"author,omitempty"`
|
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
|
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"`
|
Committer *Identity `json:"committer,omitempty"`
|
||||||
Dates *CommitDateOptions `json:"dates,omitempty"`
|
Dates *CommitDateOptions `json:"dates,omitempty"`
|
||||||
Files []*ChangeFileOperation `json:"files"` // list of file operations
|
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
|
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
|
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.
|
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 {
|
type Compare struct {
|
||||||
Commits []*Commit `json:"commits,omitempty"`
|
Commits []*Commit `json:"commits,omitempty"`
|
||||||
TotalCommits int64 `json:"total_commits,omitempty"`
|
TotalCommits int64 `json:"total_commits,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateForkOption — CreateForkOption options for creating a fork
|
// CreateForkOption — CreateForkOption options for creating a fork
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateForkOption{Name: "example"}
|
|
||||||
type CreateForkOption struct {
|
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
|
Organization string `json:"organization,omitempty"` // organization name, if forking into an organization
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOrUpdateSecretOption — CreateOrUpdateSecretOption options when creating or updating secret
|
// CreateOrUpdateSecretOption — CreateOrUpdateSecretOption options when creating or updating secret
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateOrUpdateSecretOption{Data: "example"}
|
|
||||||
type CreateOrUpdateSecretOption struct {
|
type CreateOrUpdateSecretOption struct {
|
||||||
Data string `json:"data"` // Data of the secret to update
|
Data string `json:"data"` // Data of the secret to update
|
||||||
}
|
}
|
||||||
|
|
||||||
// DismissPullReviewOptions — DismissPullReviewOptions are options to dismiss a pull review
|
// DismissPullReviewOptions — DismissPullReviewOptions are options to dismiss a pull review
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := DismissPullReviewOptions{Message: "example"}
|
|
||||||
type DismissPullReviewOptions struct {
|
type DismissPullReviewOptions struct {
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Priors bool `json:"priors,omitempty"`
|
Priors bool `json:"priors,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForgeLike — ForgeLike activity data type
|
// ForgeLike — ForgeLike activity data type
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ForgeLike{}
|
|
||||||
//
|
|
||||||
// ForgeLike has no fields in the swagger spec.
|
// ForgeLike has no fields in the swagger spec.
|
||||||
type ForgeLike struct{}
|
type ForgeLike struct{}
|
||||||
|
|
||||||
// GenerateRepoOption — GenerateRepoOption options when creating repository using a template
|
// GenerateRepoOption — GenerateRepoOption options when creating repository using a template
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GenerateRepoOption{Name: "example"}
|
|
||||||
type GenerateRepoOption struct {
|
type GenerateRepoOption struct {
|
||||||
Avatar bool `json:"avatar,omitempty"` // include avatar of the 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
|
DefaultBranch string `json:"default_branch,omitempty"` // Default branch of the new repository
|
||||||
Description string `json:"description,omitempty"` // Description of the repository to create
|
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
|
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
|
GitHooks bool `json:"git_hooks,omitempty"` // include git hooks in template repo
|
||||||
Labels bool `json:"labels,omitempty"` // include labels in template repo
|
Labels bool `json:"labels,omitempty"` // include labels in template repo
|
||||||
Name string `json:"name"` // Name of the repository to create
|
Name string `json:"name"` // Name of the repository to create
|
||||||
Owner string `json:"owner"` // The organization or person who will own the new repository
|
Owner string `json:"owner"` // The organization or person who will own the new repository
|
||||||
Private bool `json:"private,omitempty"` // Whether the repository is private
|
Private bool `json:"private,omitempty"` // Whether the repository is private
|
||||||
ProtectedBranch bool `json:"protected_branch,omitempty"` // include protected branches in template repo
|
ProtectedBranch bool `json:"protected_branch,omitempty"` // include protected branches in template repo
|
||||||
Topics bool `json:"topics,omitempty"` // include topics in template repo
|
Topics bool `json:"topics,omitempty"` // include topics in template repo
|
||||||
Webhooks bool `json:"webhooks,omitempty"` // include webhooks in template repo
|
Webhooks bool `json:"webhooks,omitempty"` // include webhooks in template repo
|
||||||
}
|
}
|
||||||
|
|
||||||
// GitignoreTemplateInfo — GitignoreTemplateInfo name and text of a gitignore template
|
// GitignoreTemplateInfo — GitignoreTemplateInfo name and text of a gitignore template
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GitignoreTemplateInfo{Name: "example"}
|
|
||||||
type GitignoreTemplateInfo struct {
|
type GitignoreTemplateInfo struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Source string `json:"source,omitempty"`
|
Source string `json:"source,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Identity — Identity for a person's identity like an author or committer
|
// Identity — Identity for a person's identity like an author or committer
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Identity{Name: "example"}
|
|
||||||
type Identity struct {
|
type Identity struct {
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LicenseTemplateInfo — LicensesInfo contains information about a License
|
// LicenseTemplateInfo — LicensesInfo contains information about a License
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := LicenseTemplateInfo{Body: "example"}
|
|
||||||
type LicenseTemplateInfo struct {
|
type LicenseTemplateInfo struct {
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Implementation string `json:"implementation,omitempty"`
|
Implementation string `json:"implementation,omitempty"`
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LicensesTemplateListEntry — LicensesListEntry is used for the API
|
// LicensesTemplateListEntry — LicensesListEntry is used for the API
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := LicensesTemplateListEntry{Name: "example"}
|
|
||||||
type LicensesTemplateListEntry struct {
|
type LicensesTemplateListEntry struct {
|
||||||
Key string `json:"key,omitempty"`
|
Key string `json:"key,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkdownOption — MarkdownOption markdown options
|
// MarkdownOption — MarkdownOption markdown options
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := MarkdownOption{Context: "example"}
|
|
||||||
type MarkdownOption struct {
|
type MarkdownOption struct {
|
||||||
Context string `json:"Context,omitempty"` // Context to render in: body
|
Context string `json:"Context,omitempty"` // Context to render in: body
|
||||||
Mode string `json:"Mode,omitempty"` // Mode to render (comment, gfm, markdown) 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
|
Text string `json:"Text,omitempty"` // Text markdown to render in: body
|
||||||
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarkupOption — MarkupOption markup options
|
// MarkupOption — MarkupOption markup options
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := MarkupOption{BranchPath: "main"}
|
|
||||||
type MarkupOption struct {
|
type MarkupOption struct {
|
||||||
BranchPath string `json:"BranchPath,omitempty"` // The current branch path where the form gets posted in: body
|
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
|
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
|
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
|
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
|
Text string `json:"Text,omitempty"` // Text markup to render in: body
|
||||||
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
Wiki bool `json:"Wiki,omitempty"` // Is it a wiki page ? in: body
|
||||||
}
|
}
|
||||||
|
|
||||||
// MergePullRequestOption — MergePullRequestForm form for merging Pull Request
|
// MergePullRequestOption — MergePullRequestForm form for merging Pull Request
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := MergePullRequestOption{Do: "example"}
|
|
||||||
type MergePullRequestOption struct {
|
type MergePullRequestOption struct {
|
||||||
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge,omitempty"`
|
DeleteBranchAfterMerge bool `json:"delete_branch_after_merge,omitempty"`
|
||||||
Do string `json:"Do"`
|
Do string `json:"Do"`
|
||||||
ForceMerge bool `json:"force_merge,omitempty"`
|
ForceMerge bool `json:"force_merge,omitempty"`
|
||||||
HeadCommitID string `json:"head_commit_id,omitempty"`
|
HeadCommitID string `json:"head_commit_id,omitempty"`
|
||||||
MergeCommitID string `json:"MergeCommitID,omitempty"`
|
MergeCommitID string `json:"MergeCommitID,omitempty"`
|
||||||
MergeMessageField string `json:"MergeMessageField,omitempty"`
|
MergeMessageField string `json:"MergeMessageField,omitempty"`
|
||||||
MergeTitleField string `json:"MergeTitleField,omitempty"`
|
MergeTitleField string `json:"MergeTitleField,omitempty"`
|
||||||
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"`
|
MergeWhenChecksSucceed bool `json:"merge_when_checks_succeed,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MigrateRepoOptions — MigrateRepoOptions options for migrating repository's this is used to interact with api v1
|
// MigrateRepoOptions — MigrateRepoOptions options for migrating repository's this is used to interact with api v1
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := MigrateRepoOptions{RepoName: "example"}
|
|
||||||
type MigrateRepoOptions struct {
|
type MigrateRepoOptions struct {
|
||||||
AuthPassword string `json:"auth_password,omitempty"`
|
AuthPassword string `json:"auth_password,omitempty"`
|
||||||
AuthToken string `json:"auth_token,omitempty"`
|
AuthToken string `json:"auth_token,omitempty"`
|
||||||
AuthUsername string `json:"auth_username,omitempty"`
|
AuthUsername string `json:"auth_username,omitempty"`
|
||||||
CloneAddr string `json:"clone_addr"`
|
CloneAddr string `json:"clone_addr"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Issues bool `json:"issues,omitempty"`
|
Issues bool `json:"issues,omitempty"`
|
||||||
LFS bool `json:"lfs,omitempty"`
|
LFS bool `json:"lfs,omitempty"`
|
||||||
LFSEndpoint string `json:"lfs_endpoint,omitempty"`
|
LFSEndpoint string `json:"lfs_endpoint,omitempty"`
|
||||||
Labels bool `json:"labels,omitempty"`
|
Labels bool `json:"labels,omitempty"`
|
||||||
Milestones bool `json:"milestones,omitempty"`
|
Milestones bool `json:"milestones,omitempty"`
|
||||||
Mirror bool `json:"mirror,omitempty"`
|
Mirror bool `json:"mirror,omitempty"`
|
||||||
MirrorInterval string `json:"mirror_interval,omitempty"`
|
MirrorInterval string `json:"mirror_interval,omitempty"`
|
||||||
Private bool `json:"private,omitempty"`
|
Private bool `json:"private,omitempty"`
|
||||||
PullRequests bool `json:"pull_requests,omitempty"`
|
PullRequests bool `json:"pull_requests,omitempty"`
|
||||||
Releases bool `json:"releases,omitempty"`
|
Releases bool `json:"releases,omitempty"`
|
||||||
RepoName string `json:"repo_name"`
|
RepoName string `json:"repo_name"`
|
||||||
RepoOwner string `json:"repo_owner,omitempty"` // Name of User or Organisation who will own Repo after migration
|
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)
|
RepoOwnerID int64 `json:"uid,omitempty"` // deprecated (only for backwards compatibility)
|
||||||
Service string `json:"service,omitempty"`
|
Service string `json:"service,omitempty"`
|
||||||
Wiki bool `json:"wiki,omitempty"`
|
Wiki bool `json:"wiki,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewIssuePinsAllowed — NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed
|
// NewIssuePinsAllowed — NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NewIssuePinsAllowed{Issues: true}
|
|
||||||
type NewIssuePinsAllowed struct {
|
type NewIssuePinsAllowed struct {
|
||||||
Issues bool `json:"issues,omitempty"`
|
Issues bool `json:"issues,omitempty"`
|
||||||
PullRequests bool `json:"pull_requests,omitempty"`
|
PullRequests bool `json:"pull_requests,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifySubjectType — NotifySubjectType represent type of notification subject
|
// NotifySubjectType — NotifySubjectType represent type of notification subject
|
||||||
//
|
// NotifySubjectType has no fields in the swagger spec.
|
||||||
// Usage:
|
type NotifySubjectType struct{}
|
||||||
//
|
|
||||||
// opts := NotifySubjectType("example")
|
|
||||||
type NotifySubjectType string
|
|
||||||
|
|
||||||
// PRBranchInfo — PRBranchInfo information about a branch
|
// PRBranchInfo — PRBranchInfo information about a branch
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PRBranchInfo{Name: "example"}
|
|
||||||
type PRBranchInfo struct {
|
type PRBranchInfo struct {
|
||||||
Name string `json:"label,omitempty"`
|
Name string `json:"label,omitempty"`
|
||||||
Ref string `json:"ref,omitempty"`
|
Ref string `json:"ref,omitempty"`
|
||||||
Repo *Repository `json:"repo,omitempty"`
|
Repo *Repository `json:"repo,omitempty"`
|
||||||
RepoID int64 `json:"repo_id,omitempty"`
|
RepoID int64 `json:"repo_id,omitempty"`
|
||||||
Sha string `json:"sha,omitempty"`
|
Sha string `json:"sha,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PayloadUser — PayloadUser represents the author or committer of a commit
|
// PayloadUser — PayloadUser represents the author or committer of a commit
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PayloadUser{Name: "example"}
|
|
||||||
type PayloadUser struct {
|
type PayloadUser struct {
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
Name string `json:"name,omitempty"` // Full name of the commit author
|
Name string `json:"name,omitempty"` // Full name of the commit author
|
||||||
UserName string `json:"username,omitempty"`
|
UserName string `json:"username,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Reference{Ref: "main"}
|
|
||||||
type Reference struct {
|
type Reference struct {
|
||||||
Object *GitObject `json:"object,omitempty"`
|
Object *GitObject `json:"object,omitempty"`
|
||||||
Ref string `json:"ref,omitempty"`
|
Ref string `json:"ref,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReplaceFlagsOption — ReplaceFlagsOption options when replacing the flags of a repository
|
// ReplaceFlagsOption — ReplaceFlagsOption options when replacing the flags of a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ReplaceFlagsOption{Flags: []string{"example"}}
|
|
||||||
type ReplaceFlagsOption struct {
|
type ReplaceFlagsOption struct {
|
||||||
Flags []string `json:"flags,omitempty"`
|
Flags []string `json:"flags,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchResults — SearchResults results of a successful search
|
// SearchResults — SearchResults results of a successful search
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := SearchResults{OK: true}
|
|
||||||
type SearchResults struct {
|
type SearchResults struct {
|
||||||
Data []*Repository `json:"data,omitempty"`
|
Data []*Repository `json:"data,omitempty"`
|
||||||
OK bool `json:"ok,omitempty"`
|
OK bool `json:"ok,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ServerVersion — ServerVersion wraps the version of the server
|
// ServerVersion — ServerVersion wraps the version of the server
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ServerVersion{Version: "example"}
|
|
||||||
type ServerVersion struct {
|
type ServerVersion struct {
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TimelineComment — TimelineComment represents a timeline comment (comment of any type) on a commit or issue
|
// TimelineComment — TimelineComment represents a timeline comment (comment of any type) on a commit or issue
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := TimelineComment{Body: "example"}
|
|
||||||
type TimelineComment struct {
|
type TimelineComment struct {
|
||||||
Assignee *User `json:"assignee,omitempty"`
|
Assignee *User `json:"assignee,omitempty"`
|
||||||
AssigneeTeam *Team `json:"assignee_team,omitempty"`
|
AssigneeTeam *Team `json:"assignee_team,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
DependentIssue *Issue `json:"dependent_issue,omitempty"`
|
DependentIssue *Issue `json:"dependent_issue,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
IssueURL string `json:"issue_url,omitempty"`
|
IssueURL string `json:"issue_url,omitempty"`
|
||||||
Label *Label `json:"label,omitempty"`
|
Label *Label `json:"label,omitempty"`
|
||||||
Milestone *Milestone `json:"milestone,omitempty"`
|
Milestone *Milestone `json:"milestone,omitempty"`
|
||||||
NewRef string `json:"new_ref,omitempty"`
|
NewRef string `json:"new_ref,omitempty"`
|
||||||
NewTitle string `json:"new_title,omitempty"`
|
NewTitle string `json:"new_title,omitempty"`
|
||||||
OldMilestone *Milestone `json:"old_milestone,omitempty"`
|
OldMilestone *Milestone `json:"old_milestone,omitempty"`
|
||||||
OldProjectID int64 `json:"old_project_id,omitempty"`
|
OldProjectID int64 `json:"old_project_id,omitempty"`
|
||||||
OldRef string `json:"old_ref,omitempty"`
|
OldRef string `json:"old_ref,omitempty"`
|
||||||
OldTitle string `json:"old_title,omitempty"`
|
OldTitle string `json:"old_title,omitempty"`
|
||||||
PRURL string `json:"pull_request_url,omitempty"`
|
PRURL string `json:"pull_request_url,omitempty"`
|
||||||
ProjectID int64 `json:"project_id,omitempty"`
|
ProjectID int64 `json:"project_id,omitempty"`
|
||||||
RefAction string `json:"ref_action,omitempty"`
|
RefAction string `json:"ref_action,omitempty"`
|
||||||
RefComment *Comment `json:"ref_comment,omitempty"`
|
RefComment *Comment `json:"ref_comment,omitempty"`
|
||||||
RefCommitSHA string `json:"ref_commit_sha,omitempty"` // commit SHA where issue/PR was referenced
|
RefCommitSHA string `json:"ref_commit_sha,omitempty"` // commit SHA where issue/PR was referenced
|
||||||
RefIssue *Issue `json:"ref_issue,omitempty"`
|
RefIssue *Issue `json:"ref_issue,omitempty"`
|
||||||
RemovedAssignee bool `json:"removed_assignee,omitempty"` // whether the assignees were removed or added
|
RemovedAssignee bool `json:"removed_assignee,omitempty"` // whether the assignees were removed or added
|
||||||
ResolveDoer *User `json:"resolve_doer,omitempty"`
|
ResolveDoer *User `json:"resolve_doer,omitempty"`
|
||||||
ReviewID int64 `json:"review_id,omitempty"`
|
ReviewID int64 `json:"review_id,omitempty"`
|
||||||
TrackedTime *TrackedTime `json:"tracked_time,omitempty"`
|
TrackedTime *TrackedTime `json:"tracked_time,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// WatchInfo — WatchInfo represents an API watch status of one repository
|
// WatchInfo — WatchInfo represents an API watch status of one repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := WatchInfo{RepositoryURL: "https://example.com"}
|
|
||||||
type WatchInfo struct {
|
type WatchInfo struct {
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
Ignored bool `json:"ignored,omitempty"`
|
Ignored bool `json:"ignored,omitempty"`
|
||||||
Reason any `json:"reason,omitempty"`
|
Reason any `json:"reason,omitempty"`
|
||||||
RepositoryURL string `json:"repository_url,omitempty"`
|
RepositoryURL string `json:"repository_url,omitempty"`
|
||||||
Subscribed bool `json:"subscribed,omitempty"`
|
Subscribed bool `json:"subscribed,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,41 +4,31 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// NotificationCount — NotificationCount number of unread notifications
|
// NotificationCount — NotificationCount number of unread notifications
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NotificationCount{New: 1}
|
|
||||||
type NotificationCount struct {
|
type NotificationCount struct {
|
||||||
New int64 `json:"new,omitempty"`
|
New int64 `json:"new,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotificationSubject — NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
// NotificationSubject — NotificationSubject contains the notification subject (Issue/Pull/Commit)
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NotificationSubject{Title: "example"}
|
|
||||||
type NotificationSubject struct {
|
type NotificationSubject struct {
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
LatestCommentHTMLURL string `json:"latest_comment_html_url,omitempty"`
|
LatestCommentHTMLURL string `json:"latest_comment_html_url,omitempty"`
|
||||||
LatestCommentURL string `json:"latest_comment_url,omitempty"`
|
LatestCommentURL string `json:"latest_comment_url,omitempty"`
|
||||||
State StateType `json:"state,omitempty"`
|
State StateType `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
Type NotifySubjectType `json:"type,omitempty"`
|
Type *NotifySubjectType `json:"type,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotificationThread — NotificationThread expose Notification on API
|
// NotificationThread — NotificationThread expose Notification on API
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := NotificationThread{URL: "https://example.com"}
|
|
||||||
type NotificationThread struct {
|
type NotificationThread struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Pinned bool `json:"pinned,omitempty"`
|
Pinned bool `json:"pinned,omitempty"`
|
||||||
Repository *Repository `json:"repository,omitempty"`
|
Repository *Repository `json:"repository,omitempty"`
|
||||||
Subject *NotificationSubject `json:"subject,omitempty"`
|
Subject *NotificationSubject `json:"subject,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Unread bool `json:"unread,omitempty"`
|
Unread bool `json:"unread,omitempty"`
|
||||||
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
UpdatedAt time.Time `json:"updated_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,47 +4,35 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := AccessToken{Name: "example"}
|
|
||||||
type AccessToken struct {
|
type AccessToken struct {
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Scopes []string `json:"scopes,omitempty"`
|
Scopes []string `json:"scopes,omitempty"`
|
||||||
Token string `json:"sha1,omitempty"`
|
Token string `json:"sha1,omitempty"`
|
||||||
TokenLastEight string `json:"token_last_eight,omitempty"`
|
TokenLastEight string `json:"token_last_eight,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateAccessTokenOption — CreateAccessTokenOption options when create access token
|
// CreateAccessTokenOption — CreateAccessTokenOption options when create access token
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateAccessTokenOption{Name: "example"}
|
|
||||||
type CreateAccessTokenOption struct {
|
type CreateAccessTokenOption struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Scopes []string `json:"scopes,omitempty"`
|
Scopes []string `json:"scopes,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateOAuth2ApplicationOptions — CreateOAuth2ApplicationOptions holds options to create an oauth2 application
|
// CreateOAuth2ApplicationOptions — CreateOAuth2ApplicationOptions holds options to create an oauth2 application
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateOAuth2ApplicationOptions{Name: "example"}
|
|
||||||
type CreateOAuth2ApplicationOptions struct {
|
type CreateOAuth2ApplicationOptions struct {
|
||||||
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := OAuth2Application{Name: "example"}
|
|
||||||
type OAuth2Application struct {
|
type OAuth2Application struct {
|
||||||
ClientID string `json:"client_id,omitempty"`
|
ClientID string `json:"client_id,omitempty"`
|
||||||
ClientSecret string `json:"client_secret,omitempty"`
|
ClientSecret string `json:"client_secret,omitempty"`
|
||||||
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
ConfidentialClient bool `json:"confidential_client,omitempty"`
|
||||||
Created time.Time `json:"created,omitempty"`
|
Created time.Time `json:"created,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
RedirectURIs []string `json:"redirect_uris,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
78
types/org.go
78
types/org.go
|
|
@ -2,65 +2,51 @@
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
|
||||||
// CreateOrgOption — CreateOrgOption options for creating an organization
|
// CreateOrgOption — CreateOrgOption options for creating an organization
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateOrgOption{UserName: "example"}
|
|
||||||
type CreateOrgOption struct {
|
type CreateOrgOption struct {
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
FullName string `json:"full_name,omitempty"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
Location string `json:"location,omitempty"`
|
Location string `json:"location,omitempty"`
|
||||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||||
UserName string `json:"username"`
|
UserName string `json:"username"`
|
||||||
Visibility string `json:"visibility,omitempty"` // possible values are `public` (default), `limited` or `private`
|
Visibility string `json:"visibility,omitempty"` // possible values are `public` (default), `limited` or `private`
|
||||||
Website string `json:"website,omitempty"`
|
Website string `json:"website,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditOrgOption — EditOrgOption options for editing an organization
|
// EditOrgOption — EditOrgOption options for editing an organization
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditOrgOption{Description: "example"}
|
|
||||||
type EditOrgOption struct {
|
type EditOrgOption struct {
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
FullName string `json:"full_name,omitempty"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
Location string `json:"location,omitempty"`
|
Location string `json:"location,omitempty"`
|
||||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||||
Visibility string `json:"visibility,omitempty"` // possible values are `public`, `limited` or `private`
|
Visibility string `json:"visibility,omitempty"` // possible values are `public`, `limited` or `private`
|
||||||
Website string `json:"website,omitempty"`
|
Website string `json:"website,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Organization — Organization represents an organization
|
// Organization — Organization represents an organization
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Organization{Description: "example"}
|
|
||||||
type Organization struct {
|
type Organization struct {
|
||||||
AvatarURL string `json:"avatar_url,omitempty"`
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Email string `json:"email,omitempty"`
|
Email string `json:"email,omitempty"`
|
||||||
FullName string `json:"full_name,omitempty"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Location string `json:"location,omitempty"`
|
Location string `json:"location,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
RepoAdminChangeTeamAccess bool `json:"repo_admin_change_team_access,omitempty"`
|
||||||
UserName string `json:"username,omitempty"` // deprecated
|
UserName string `json:"username,omitempty"` // deprecated
|
||||||
Visibility string `json:"visibility,omitempty"`
|
Visibility string `json:"visibility,omitempty"`
|
||||||
Website string `json:"website,omitempty"`
|
Website string `json:"website,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// OrganizationPermissions — OrganizationPermissions list different users permissions on an organization
|
// OrganizationPermissions — OrganizationPermissions list different users permissions on an organization
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := OrganizationPermissions{CanCreateRepository: true}
|
|
||||||
type OrganizationPermissions struct {
|
type OrganizationPermissions struct {
|
||||||
CanCreateRepository bool `json:"can_create_repository,omitempty"`
|
CanCreateRepository bool `json:"can_create_repository,omitempty"`
|
||||||
CanRead bool `json:"can_read,omitempty"`
|
CanRead bool `json:"can_read,omitempty"`
|
||||||
CanWrite bool `json:"can_write,omitempty"`
|
CanWrite bool `json:"can_write,omitempty"`
|
||||||
IsAdmin bool `json:"is_admin,omitempty"`
|
IsAdmin bool `json:"is_admin,omitempty"`
|
||||||
IsOwner bool `json:"is_owner,omitempty"`
|
IsOwner bool `json:"is_owner,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,34 +4,28 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// Package — Package represents a package
|
// Package — Package represents a package
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Package{Name: "example"}
|
|
||||||
type Package struct {
|
type Package struct {
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
Creator *User `json:"creator,omitempty"`
|
Creator *User `json:"creator,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Owner *User `json:"owner,omitempty"`
|
Owner *User `json:"owner,omitempty"`
|
||||||
Repository *Repository `json:"repository,omitempty"`
|
Repository *Repository `json:"repository,omitempty"`
|
||||||
Type string `json:"type,omitempty"`
|
Type string `json:"type,omitempty"`
|
||||||
Version string `json:"version,omitempty"`
|
Version string `json:"version,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PackageFile — PackageFile represents a package file
|
// PackageFile — PackageFile represents a package file
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PackageFile{Name: "example"}
|
|
||||||
type PackageFile struct {
|
type PackageFile struct {
|
||||||
HashMD5 string `json:"md5,omitempty"`
|
HashMD5 string `json:"md5,omitempty"`
|
||||||
HashSHA1 string `json:"sha1,omitempty"`
|
HashSHA1 string `json:"sha1,omitempty"`
|
||||||
HashSHA256 string `json:"sha256,omitempty"`
|
HashSHA256 string `json:"sha256,omitempty"`
|
||||||
HashSHA512 string `json:"sha512,omitempty"`
|
HashSHA512 string `json:"sha512,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Size int64 `json:"Size,omitempty"`
|
Size int64 `json:"Size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
249
types/pr.go
249
types/pr.go
|
|
@ -4,191 +4,150 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// CreatePullRequestOption — CreatePullRequestOption options when creating a pull request
|
// CreatePullRequestOption — CreatePullRequestOption options when creating a pull request
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreatePullRequestOption{Body: "example"}
|
|
||||||
type CreatePullRequestOption struct {
|
type CreatePullRequestOption struct {
|
||||||
Assignee string `json:"assignee,omitempty"`
|
Assignee string `json:"assignee,omitempty"`
|
||||||
Assignees []string `json:"assignees,omitempty"`
|
Assignees []string `json:"assignees,omitempty"`
|
||||||
Base string `json:"base,omitempty"`
|
Base string `json:"base,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Deadline time.Time `json:"due_date,omitempty"`
|
Deadline time.Time `json:"due_date,omitempty"`
|
||||||
Head string `json:"head,omitempty"`
|
Head string `json:"head,omitempty"`
|
||||||
Labels []int64 `json:"labels,omitempty"`
|
Labels []int64 `json:"labels,omitempty"`
|
||||||
Milestone int64 `json:"milestone,omitempty"`
|
Milestone int64 `json:"milestone,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePullReviewComment — CreatePullReviewComment represent a review comment for creation api
|
// CreatePullReviewComment — CreatePullReviewComment represent a review comment for creation api
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreatePullReviewComment{Body: "example"}
|
|
||||||
type CreatePullReviewComment struct {
|
type CreatePullReviewComment struct {
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
NewLineNum int64 `json:"new_position,omitempty"` // if comment to new file line or 0
|
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
|
OldLineNum int64 `json:"old_position,omitempty"` // if comment to old file line or 0
|
||||||
Path string `json:"path,omitempty"` // the tree path
|
Path string `json:"path,omitempty"` // the tree path
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreatePullReviewCommentOptions — CreatePullReviewCommentOptions are options to create a pull review comment
|
// CreatePullReviewCommentOptions — CreatePullReviewCommentOptions are options to create a pull review comment
|
||||||
//
|
// CreatePullReviewCommentOptions has no fields in the swagger spec.
|
||||||
// Usage:
|
type CreatePullReviewCommentOptions struct{}
|
||||||
//
|
|
||||||
// opts := CreatePullReviewCommentOptions(CreatePullReviewComment{})
|
|
||||||
type CreatePullReviewCommentOptions CreatePullReviewComment
|
|
||||||
|
|
||||||
// CreatePullReviewOptions — CreatePullReviewOptions are options to create a pull review
|
// CreatePullReviewOptions — CreatePullReviewOptions are options to create a pull review
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreatePullReviewOptions{Body: "example"}
|
|
||||||
type CreatePullReviewOptions struct {
|
type CreatePullReviewOptions struct {
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Comments []*CreatePullReviewComment `json:"comments,omitempty"`
|
Comments []*CreatePullReviewComment `json:"comments,omitempty"`
|
||||||
CommitID string `json:"commit_id,omitempty"`
|
CommitID string `json:"commit_id,omitempty"`
|
||||||
Event ReviewStateType `json:"event,omitempty"`
|
Event *ReviewStateType `json:"event,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditPullRequestOption — EditPullRequestOption options when modify pull request
|
// EditPullRequestOption — EditPullRequestOption options when modify pull request
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditPullRequestOption{Body: "example"}
|
|
||||||
type EditPullRequestOption struct {
|
type EditPullRequestOption struct {
|
||||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
||||||
Assignee string `json:"assignee,omitempty"`
|
Assignee string `json:"assignee,omitempty"`
|
||||||
Assignees []string `json:"assignees,omitempty"`
|
Assignees []string `json:"assignees,omitempty"`
|
||||||
Base string `json:"base,omitempty"`
|
Base string `json:"base,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Deadline time.Time `json:"due_date,omitempty"`
|
Deadline time.Time `json:"due_date,omitempty"`
|
||||||
Labels []int64 `json:"labels,omitempty"`
|
Labels []int64 `json:"labels,omitempty"`
|
||||||
Milestone int64 `json:"milestone,omitempty"`
|
Milestone int64 `json:"milestone,omitempty"`
|
||||||
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
RemoveDeadline bool `json:"unset_due_date,omitempty"`
|
||||||
State string `json:"state,omitempty"`
|
State string `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullRequest — PullRequest represents a pull request
|
// PullRequest — PullRequest represents a pull request
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PullRequest{Body: "example"}
|
|
||||||
type PullRequest struct {
|
type PullRequest struct {
|
||||||
Additions int64 `json:"additions,omitempty"`
|
Additions int64 `json:"additions,omitempty"`
|
||||||
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
AllowMaintainerEdit bool `json:"allow_maintainer_edit,omitempty"`
|
||||||
Assignee *User `json:"assignee,omitempty"`
|
Assignee *User `json:"assignee,omitempty"`
|
||||||
Assignees []*User `json:"assignees,omitempty"`
|
Assignees []*User `json:"assignees,omitempty"`
|
||||||
Base *PRBranchInfo `json:"base,omitempty"`
|
Base *PRBranchInfo `json:"base,omitempty"`
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
ChangedFiles int64 `json:"changed_files,omitempty"`
|
ChangedFiles int64 `json:"changed_files,omitempty"`
|
||||||
Closed time.Time `json:"closed_at,omitempty"`
|
Closed time.Time `json:"closed_at,omitempty"`
|
||||||
Comments int64 `json:"comments,omitempty"`
|
Comments int64 `json:"comments,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Deadline time.Time `json:"due_date,omitempty"`
|
Deadline time.Time `json:"due_date,omitempty"`
|
||||||
Deletions int64 `json:"deletions,omitempty"`
|
Deletions int64 `json:"deletions,omitempty"`
|
||||||
DiffURL string `json:"diff_url,omitempty"`
|
DiffURL string `json:"diff_url,omitempty"`
|
||||||
Draft bool `json:"draft,omitempty"`
|
Draft bool `json:"draft,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
HasMerged bool `json:"merged,omitempty"`
|
HasMerged bool `json:"merged,omitempty"`
|
||||||
Head *PRBranchInfo `json:"head,omitempty"`
|
Head *PRBranchInfo `json:"head,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Index int64 `json:"number,omitempty"`
|
Index int64 `json:"number,omitempty"`
|
||||||
IsLocked bool `json:"is_locked,omitempty"`
|
IsLocked bool `json:"is_locked,omitempty"`
|
||||||
Labels []*Label `json:"labels,omitempty"`
|
Labels []*Label `json:"labels,omitempty"`
|
||||||
MergeBase string `json:"merge_base,omitempty"`
|
MergeBase string `json:"merge_base,omitempty"`
|
||||||
Mergeable bool `json:"mergeable,omitempty"`
|
Mergeable bool `json:"mergeable,omitempty"`
|
||||||
Merged time.Time `json:"merged_at,omitempty"`
|
Merged time.Time `json:"merged_at,omitempty"`
|
||||||
MergedBy *User `json:"merged_by,omitempty"`
|
MergedBy *User `json:"merged_by,omitempty"`
|
||||||
MergedCommitID string `json:"merge_commit_sha,omitempty"`
|
MergedCommitID string `json:"merge_commit_sha,omitempty"`
|
||||||
Milestone *Milestone `json:"milestone,omitempty"`
|
Milestone *Milestone `json:"milestone,omitempty"`
|
||||||
PatchURL string `json:"patch_url,omitempty"`
|
PatchURL string `json:"patch_url,omitempty"`
|
||||||
PinOrder int64 `json:"pin_order,omitempty"`
|
PinOrder int64 `json:"pin_order,omitempty"`
|
||||||
RequestedReviewers []*User `json:"requested_reviewers,omitempty"`
|
RequestedReviewers []*User `json:"requested_reviewers,omitempty"`
|
||||||
RequestedReviewersTeams []*Team `json:"requested_reviewers_teams,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)
|
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"`
|
State StateType `json:"state,omitempty"`
|
||||||
Title string `json:"title,omitempty"`
|
Title string `json:"title,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullRequestMeta — PullRequestMeta PR info if an issue is a PR
|
// PullRequestMeta — PullRequestMeta PR info if an issue is a PR
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PullRequestMeta{HTMLURL: "https://example.com"}
|
|
||||||
type PullRequestMeta struct {
|
type PullRequestMeta struct {
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
HasMerged bool `json:"merged,omitempty"`
|
HasMerged bool `json:"merged,omitempty"`
|
||||||
IsWorkInProgress bool `json:"draft,omitempty"`
|
IsWorkInProgress bool `json:"draft,omitempty"`
|
||||||
Merged time.Time `json:"merged_at,omitempty"`
|
Merged time.Time `json:"merged_at,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullReview — PullReview represents a pull request review
|
// PullReview — PullReview represents a pull request review
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PullReview{Body: "example"}
|
|
||||||
type PullReview struct {
|
type PullReview struct {
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
CodeCommentsCount int64 `json:"comments_count,omitempty"`
|
CodeCommentsCount int64 `json:"comments_count,omitempty"`
|
||||||
CommitID string `json:"commit_id,omitempty"`
|
CommitID string `json:"commit_id,omitempty"`
|
||||||
Dismissed bool `json:"dismissed,omitempty"`
|
Dismissed bool `json:"dismissed,omitempty"`
|
||||||
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Official bool `json:"official,omitempty"`
|
Official bool `json:"official,omitempty"`
|
||||||
Stale bool `json:"stale,omitempty"`
|
Stale bool `json:"stale,omitempty"`
|
||||||
State ReviewStateType `json:"state,omitempty"`
|
State *ReviewStateType `json:"state,omitempty"`
|
||||||
Submitted time.Time `json:"submitted_at,omitempty"`
|
Submitted time.Time `json:"submitted_at,omitempty"`
|
||||||
Team *Team `json:"team,omitempty"`
|
Team *Team `json:"team,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullReviewComment — PullReviewComment represents a comment on a pull request review
|
// PullReviewComment — PullReviewComment represents a comment on a pull request review
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PullReviewComment{Body: "example"}
|
|
||||||
type PullReviewComment struct {
|
type PullReviewComment struct {
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
CommitID string `json:"commit_id,omitempty"`
|
CommitID string `json:"commit_id,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
DiffHunk string `json:"diff_hunk,omitempty"`
|
DiffHunk string `json:"diff_hunk,omitempty"`
|
||||||
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
HTMLPullURL string `json:"pull_request_url,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
LineNum int `json:"position,omitempty"`
|
LineNum int `json:"position,omitempty"`
|
||||||
OldLineNum int `json:"original_position,omitempty"`
|
OldLineNum int `json:"original_position,omitempty"`
|
||||||
OrigCommitID string `json:"original_commit_id,omitempty"`
|
OrigCommitID string `json:"original_commit_id,omitempty"`
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
Resolver *User `json:"resolver,omitempty"`
|
Resolver *User `json:"resolver,omitempty"`
|
||||||
ReviewID int64 `json:"pull_request_review_id,omitempty"`
|
ReviewID int64 `json:"pull_request_review_id,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PullReviewRequestOptions — PullReviewRequestOptions are options to add or remove pull review requests
|
// PullReviewRequestOptions — PullReviewRequestOptions are options to add or remove pull review requests
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PullReviewRequestOptions{Reviewers: []string{"example"}}
|
|
||||||
type PullReviewRequestOptions struct {
|
type PullReviewRequestOptions struct {
|
||||||
Reviewers []string `json:"reviewers,omitempty"`
|
Reviewers []string `json:"reviewers,omitempty"`
|
||||||
TeamReviewers []string `json:"team_reviewers,omitempty"`
|
TeamReviewers []string `json:"team_reviewers,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitPullReviewOptions — SubmitPullReviewOptions are options to submit a pending pull review
|
// SubmitPullReviewOptions — SubmitPullReviewOptions are options to submit a pending pull review
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := SubmitPullReviewOptions{Body: "example"}
|
|
||||||
type SubmitPullReviewOptions struct {
|
type SubmitPullReviewOptions struct {
|
||||||
Body string `json:"body,omitempty"`
|
Body string `json:"body,omitempty"`
|
||||||
Event ReviewStateType `json:"event,omitempty"`
|
Event *ReviewStateType `json:"event,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
142
types/quota.go
142
types/quota.go
|
|
@ -2,197 +2,123 @@
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
|
||||||
// CreateQuotaGroupOptions — CreateQutaGroupOptions represents the options for creating a quota group
|
// CreateQuotaGroupOptions — CreateQutaGroupOptions represents the options for creating a quota group
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateQuotaGroupOptions{Name: "example"}
|
|
||||||
type CreateQuotaGroupOptions struct {
|
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.
|
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
|
// CreateQuotaRuleOptions — CreateQuotaRuleOptions represents the options for creating a quota rule
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateQuotaRuleOptions{Name: "example"}
|
|
||||||
type CreateQuotaRuleOptions struct {
|
type CreateQuotaRuleOptions struct {
|
||||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||||
Name string `json:"name,omitempty"` // Name of the rule to create
|
Name string `json:"name,omitempty"` // Name of the rule to create
|
||||||
Subjects []string `json:"subjects,omitempty"` // The subjects affected by the rule
|
Subjects []string `json:"subjects,omitempty"` // The subjects affected by the rule
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditQuotaRuleOptions — EditQuotaRuleOptions represents the options for editing a quota rule
|
// EditQuotaRuleOptions — EditQuotaRuleOptions represents the options for editing a quota rule
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditQuotaRuleOptions{Subjects: []string{"example"}}
|
|
||||||
type EditQuotaRuleOptions struct {
|
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
|
Subjects []string `json:"subjects,omitempty"` // The subjects affected by the rule
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaGroup — QuotaGroup represents a quota group
|
// QuotaGroup — QuotaGroup represents a quota group
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaGroup{Name: "example"}
|
|
||||||
type QuotaGroup struct {
|
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
|
Rules []*QuotaRuleInfo `json:"rules,omitempty"` // Rules associated with the group
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaGroupList — QuotaGroupList represents a list of quota groups
|
// QuotaGroupList — QuotaGroupList represents a list of quota groups
|
||||||
//
|
// QuotaGroupList has no fields in the swagger spec.
|
||||||
// Usage:
|
type QuotaGroupList struct{}
|
||||||
//
|
|
||||||
// opts := QuotaGroupList([]*QuotaGroup{})
|
|
||||||
type QuotaGroupList []*QuotaGroup
|
|
||||||
|
|
||||||
// QuotaInfo — QuotaInfo represents information about a user's quota
|
// QuotaInfo — QuotaInfo represents information about a user's quota
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaInfo{Groups: {}}
|
|
||||||
type QuotaInfo struct {
|
type QuotaInfo struct {
|
||||||
Groups QuotaGroupList `json:"groups,omitempty"`
|
Groups *QuotaGroupList `json:"groups,omitempty"`
|
||||||
Used *QuotaUsed `json:"used,omitempty"`
|
Used *QuotaUsed `json:"used,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaRuleInfo — QuotaRuleInfo contains information about a quota rule
|
// QuotaRuleInfo — QuotaRuleInfo contains information about a quota rule
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaRuleInfo{Name: "example"}
|
|
||||||
type QuotaRuleInfo struct {
|
type QuotaRuleInfo struct {
|
||||||
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
Limit int64 `json:"limit,omitempty"` // The limit set by the rule
|
||||||
Name string `json:"name,omitempty"` // Name of the rule (only shown to admins)
|
Name string `json:"name,omitempty"` // Name of the rule (only shown to admins)
|
||||||
Subjects []string `json:"subjects,omitempty"` // Subjects the rule affects
|
Subjects []string `json:"subjects,omitempty"` // Subjects the rule affects
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaUsed — QuotaUsed represents the quota usage of a user
|
// QuotaUsed — QuotaUsed represents the quota usage of a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsed{Size: &QuotaUsedSize{}}
|
|
||||||
type QuotaUsed struct {
|
type QuotaUsed struct {
|
||||||
Size *QuotaUsedSize `json:"size,omitempty"`
|
Size *QuotaUsedSize `json:"size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaUsedArtifact — QuotaUsedArtifact represents an artifact counting towards a user's quota
|
// QuotaUsedArtifact — QuotaUsedArtifact represents an artifact counting towards a user's quota
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedArtifact{Name: "example"}
|
|
||||||
type QuotaUsedArtifact struct {
|
type QuotaUsedArtifact struct {
|
||||||
HTMLURL string `json:"html_url,omitempty"` // HTML URL to the action run containing the artifact
|
HTMLURL string `json:"html_url,omitempty"` // HTML URL to the action run containing the artifact
|
||||||
Name string `json:"name,omitempty"` // Name of the artifact
|
Name string `json:"name,omitempty"` // Name of the artifact
|
||||||
Size int64 `json:"size,omitempty"` // Size of the artifact (compressed)
|
Size int64 `json:"size,omitempty"` // Size of the artifact (compressed)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaUsedArtifactList — QuotaUsedArtifactList represents a list of artifacts counting towards a user's quota
|
// QuotaUsedArtifactList — QuotaUsedArtifactList represents a list of artifacts counting towards a user's quota
|
||||||
//
|
// QuotaUsedArtifactList has no fields in the swagger spec.
|
||||||
// Usage:
|
type QuotaUsedArtifactList struct{}
|
||||||
//
|
|
||||||
// opts := QuotaUsedArtifactList([]*QuotaUsedArtifact{})
|
|
||||||
type QuotaUsedArtifactList []*QuotaUsedArtifact
|
|
||||||
|
|
||||||
// QuotaUsedAttachment — QuotaUsedAttachment represents an attachment counting towards a user's quota
|
// QuotaUsedAttachment — QuotaUsedAttachment represents an attachment counting towards a user's quota
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedAttachment{Name: "example"}
|
|
||||||
type QuotaUsedAttachment struct {
|
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
|
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
|
Name string `json:"name,omitempty"` // Filename of the attachment
|
||||||
Size int64 `json:"size,omitempty"` // Size of the attachment (in bytes)
|
Size int64 `json:"size,omitempty"` // Size of the attachment (in bytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaUsedAttachmentList — QuotaUsedAttachmentList represents a list of attachment counting towards a user's quota
|
// QuotaUsedAttachmentList — QuotaUsedAttachmentList represents a list of attachment counting towards a user's quota
|
||||||
//
|
// QuotaUsedAttachmentList has no fields in the swagger spec.
|
||||||
// Usage:
|
type QuotaUsedAttachmentList struct{}
|
||||||
//
|
|
||||||
// opts := QuotaUsedAttachmentList([]*QuotaUsedAttachment{})
|
|
||||||
type QuotaUsedAttachmentList []*QuotaUsedAttachment
|
|
||||||
|
|
||||||
// QuotaUsedPackage — QuotaUsedPackage represents a package counting towards a user's quota
|
// QuotaUsedPackage — QuotaUsedPackage represents a package counting towards a user's quota
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedPackage{Name: "example"}
|
|
||||||
type QuotaUsedPackage struct {
|
type QuotaUsedPackage struct {
|
||||||
HTMLURL string `json:"html_url,omitempty"` // HTML URL to the package version
|
HTMLURL string `json:"html_url,omitempty"` // HTML URL to the package version
|
||||||
Name string `json:"name,omitempty"` // Name of the package
|
Name string `json:"name,omitempty"` // Name of the package
|
||||||
Size int64 `json:"size,omitempty"` // Size of the package version
|
Size int64 `json:"size,omitempty"` // Size of the package version
|
||||||
Type string `json:"type,omitempty"` // Type of the package
|
Type string `json:"type,omitempty"` // Type of the package
|
||||||
Version string `json:"version,omitempty"` // Version 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
|
// QuotaUsedPackageList — QuotaUsedPackageList represents a list of packages counting towards a user's quota
|
||||||
//
|
// QuotaUsedPackageList has no fields in the swagger spec.
|
||||||
// Usage:
|
type QuotaUsedPackageList struct{}
|
||||||
//
|
|
||||||
// opts := QuotaUsedPackageList([]*QuotaUsedPackage{})
|
|
||||||
type QuotaUsedPackageList []*QuotaUsedPackage
|
|
||||||
|
|
||||||
// QuotaUsedSize — QuotaUsedSize represents the size-based quota usage of a user
|
// QuotaUsedSize — QuotaUsedSize represents the size-based quota usage of a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedSize{Assets: &QuotaUsedSizeAssets{}}
|
|
||||||
type QuotaUsedSize struct {
|
type QuotaUsedSize struct {
|
||||||
Assets *QuotaUsedSizeAssets `json:"assets,omitempty"`
|
Assets *QuotaUsedSizeAssets `json:"assets,omitempty"`
|
||||||
Git *QuotaUsedSizeGit `json:"git,omitempty"`
|
Git *QuotaUsedSizeGit `json:"git,omitempty"`
|
||||||
Repos *QuotaUsedSizeRepos `json:"repos,omitempty"`
|
Repos *QuotaUsedSizeRepos `json:"repos,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// QuotaUsedSizeAssets — QuotaUsedSizeAssets represents the size-based asset usage of a user
|
// QuotaUsedSizeAssets — QuotaUsedSizeAssets represents the size-based asset usage of a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedSizeAssets{Artifacts: 1}
|
|
||||||
type QuotaUsedSizeAssets struct {
|
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"`
|
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
|
// QuotaUsedSizeAssetsAttachments — QuotaUsedSizeAssetsAttachments represents the size-based attachment quota usage of a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedSizeAssetsAttachments{Issues: 1}
|
|
||||||
type QuotaUsedSizeAssetsAttachments struct {
|
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
|
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
|
// QuotaUsedSizeAssetsPackages — QuotaUsedSizeAssetsPackages represents the size-based package quota usage of a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedSizeAssetsPackages{All: 1}
|
|
||||||
type QuotaUsedSizeAssetsPackages struct {
|
type QuotaUsedSizeAssetsPackages struct {
|
||||||
All int64 `json:"all,omitempty"` // Storage suze used for the user's packages
|
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
|
// QuotaUsedSizeGit — QuotaUsedSizeGit represents the size-based git (lfs) quota usage of a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedSizeGit{LFS: 1}
|
|
||||||
type QuotaUsedSizeGit struct {
|
type QuotaUsedSizeGit struct {
|
||||||
LFS int64 `json:"LFS,omitempty"` // Storage size of the user's Git LFS objects
|
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
|
// QuotaUsedSizeRepos — QuotaUsedSizeRepos represents the size-based repository quota usage of a user
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := QuotaUsedSizeRepos{Private: 1}
|
|
||||||
type QuotaUsedSizeRepos struct {
|
type QuotaUsedSizeRepos struct {
|
||||||
Private int64 `json:"private,omitempty"` // Storage size of the user's private repositories
|
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"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// EditReactionOption — EditReactionOption contain the reaction type
|
// EditReactionOption — EditReactionOption contain the reaction type
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditReactionOption{Reaction: "example"}
|
|
||||||
type EditReactionOption struct {
|
type EditReactionOption struct {
|
||||||
Reaction string `json:"content,omitempty"`
|
Reaction string `json:"content,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reaction — Reaction contain one reaction
|
// Reaction — Reaction contain one reaction
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Reaction{Reaction: "example"}
|
|
||||||
type Reaction struct {
|
type Reaction struct {
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
Reaction string `json:"content,omitempty"`
|
Reaction string `json:"content,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,58 +4,48 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
|
|
||||||
// CreateReleaseOption — CreateReleaseOption options when creating a release
|
// CreateReleaseOption — CreateReleaseOption options when creating a release
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateReleaseOption{TagName: "v1.0.0"}
|
|
||||||
type CreateReleaseOption struct {
|
type CreateReleaseOption struct {
|
||||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||||
IsDraft bool `json:"draft,omitempty"`
|
IsDraft bool `json:"draft,omitempty"`
|
||||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||||
Note string `json:"body,omitempty"`
|
Note string `json:"body,omitempty"`
|
||||||
TagName string `json:"tag_name"`
|
TagName string `json:"tag_name"`
|
||||||
Target string `json:"target_commitish,omitempty"`
|
Target string `json:"target_commitish,omitempty"`
|
||||||
Title string `json:"name,omitempty"`
|
Title string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditReleaseOption — EditReleaseOption options when editing a release
|
// EditReleaseOption — EditReleaseOption options when editing a release
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditReleaseOption{TagName: "v1.0.0"}
|
|
||||||
type EditReleaseOption struct {
|
type EditReleaseOption struct {
|
||||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||||
IsDraft bool `json:"draft,omitempty"`
|
IsDraft bool `json:"draft,omitempty"`
|
||||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||||
Note string `json:"body,omitempty"`
|
Note string `json:"body,omitempty"`
|
||||||
TagName string `json:"tag_name,omitempty"`
|
TagName string `json:"tag_name,omitempty"`
|
||||||
Target string `json:"target_commitish,omitempty"`
|
Target string `json:"target_commitish,omitempty"`
|
||||||
Title string `json:"name,omitempty"`
|
Title string `json:"name,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Release — Release represents a repository release
|
// Release — Release represents a repository release
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Release{TagName: "v1.0.0"}
|
|
||||||
type Release struct {
|
type Release struct {
|
||||||
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count,omitempty"`
|
ArchiveDownloadCount *TagArchiveDownloadCount `json:"archive_download_count,omitempty"`
|
||||||
Attachments []*Attachment `json:"assets,omitempty"`
|
Attachments []*Attachment `json:"assets,omitempty"`
|
||||||
Author *User `json:"author,omitempty"`
|
Author *User `json:"author,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at,omitempty"`
|
CreatedAt time.Time `json:"created_at,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
HideArchiveLinks bool `json:"hide_archive_links,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
IsDraft bool `json:"draft,omitempty"`
|
IsDraft bool `json:"draft,omitempty"`
|
||||||
IsPrerelease bool `json:"prerelease,omitempty"`
|
IsPrerelease bool `json:"prerelease,omitempty"`
|
||||||
Note string `json:"body,omitempty"`
|
Note string `json:"body,omitempty"`
|
||||||
PublishedAt time.Time `json:"published_at,omitempty"`
|
PublishedAt time.Time `json:"published_at,omitempty"`
|
||||||
TagName string `json:"tag_name,omitempty"`
|
TagName string `json:"tag_name,omitempty"`
|
||||||
TarURL string `json:"tarball_url,omitempty"`
|
TarURL string `json:"tarball_url,omitempty"`
|
||||||
Target string `json:"target_commitish,omitempty"`
|
Target string `json:"target_commitish,omitempty"`
|
||||||
Title string `json:"name,omitempty"`
|
Title string `json:"name,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
UploadURL string `json:"upload_url,omitempty"`
|
UploadURL string `json:"upload_url,omitempty"`
|
||||||
ZipURL string `json:"zipball_url,omitempty"`
|
ZipURL string `json:"zipball_url,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
340
types/repo.go
340
types/repo.go
|
|
@ -4,270 +4,214 @@ package types
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreatePushMirrorOption{Interval: "example"}
|
|
||||||
type CreatePushMirrorOption struct {
|
type CreatePushMirrorOption struct {
|
||||||
Interval string `json:"interval,omitempty"`
|
Interval string `json:"interval,omitempty"`
|
||||||
RemoteAddress string `json:"remote_address,omitempty"`
|
RemoteAddress string `json:"remote_address,omitempty"`
|
||||||
RemotePassword string `json:"remote_password,omitempty"`
|
RemotePassword string `json:"remote_password,omitempty"`
|
||||||
RemoteUsername string `json:"remote_username,omitempty"`
|
RemoteUsername string `json:"remote_username,omitempty"`
|
||||||
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
||||||
UseSSH bool `json:"use_ssh,omitempty"`
|
UseSSH bool `json:"use_ssh,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateRepoOption — CreateRepoOption options when creating repository
|
// CreateRepoOption — CreateRepoOption options when creating repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := CreateRepoOption{Name: "example"}
|
|
||||||
type CreateRepoOption struct {
|
type CreateRepoOption struct {
|
||||||
AutoInit bool `json:"auto_init,omitempty"` // Whether the repository should be auto-initialized?
|
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)
|
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
|
Description string `json:"description,omitempty"` // Description of the repository to create
|
||||||
Gitignores string `json:"gitignores,omitempty"` // Gitignores to use
|
Gitignores string `json:"gitignores,omitempty"` // Gitignores to use
|
||||||
IssueLabels string `json:"issue_labels,omitempty"` // Label-Set to use
|
IssueLabels string `json:"issue_labels,omitempty"` // Label-Set to use
|
||||||
License string `json:"license,omitempty"` // License to use
|
License string `json:"license,omitempty"` // License to use
|
||||||
Name string `json:"name"` // Name of the repository to create
|
Name string `json:"name"` // Name of the repository to create
|
||||||
ObjectFormatName string `json:"object_format_name,omitempty"` // ObjectFormatName of the underlying git repository
|
ObjectFormatName string `json:"object_format_name,omitempty"` // ObjectFormatName of the underlying git repository
|
||||||
Private bool `json:"private,omitempty"` // Whether the repository is private
|
Private bool `json:"private,omitempty"` // Whether the repository is private
|
||||||
Readme string `json:"readme,omitempty"` // Readme of the repository to create
|
Readme string `json:"readme,omitempty"` // Readme of the repository to create
|
||||||
Template bool `json:"template,omitempty"` // Whether the repository is template
|
Template bool `json:"template,omitempty"` // Whether the repository is template
|
||||||
TrustModel string `json:"trust_model,omitempty"` // TrustModel of the repository
|
TrustModel string `json:"trust_model,omitempty"` // TrustModel of the repository
|
||||||
}
|
}
|
||||||
|
|
||||||
// EditRepoOption — EditRepoOption options when editing a repository's properties
|
// EditRepoOption — EditRepoOption options when editing a repository's properties
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := EditRepoOption{Description: "example"}
|
|
||||||
type EditRepoOption struct {
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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
|
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.
|
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
|
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".
|
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"
|
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.
|
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
|
EnablePrune bool `json:"enable_prune,omitempty"` // enable prune - remove obsolete remote-tracking references when mirroring
|
||||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||||
GloballyEditableWiki bool `json:"globally_editable_wiki,omitempty"` // set the globally editable state of the wiki
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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.
|
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"`
|
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||||
MirrorInterval string `json:"mirror_interval,omitempty"` // set to a string like `8h30m0s` to set the mirror interval time
|
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
|
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.
|
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
|
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.
|
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.
|
WikiBranch string `json:"wiki_branch,omitempty"` // sets the branch used for this repository's wiki.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExternalTracker — ExternalTracker represents settings for external tracker
|
// ExternalTracker — ExternalTracker represents settings for external tracker
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ExternalTracker{ExternalTrackerFormat: "example"}
|
|
||||||
type ExternalTracker struct {
|
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
|
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`
|
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.
|
ExternalTrackerURL string `json:"external_tracker_url,omitempty"` // URL of external issue tracker.
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExternalWiki — ExternalWiki represents setting for external wiki
|
// ExternalWiki — ExternalWiki represents setting for external wiki
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := ExternalWiki{ExternalWikiURL: "https://example.com"}
|
|
||||||
type ExternalWiki struct {
|
type ExternalWiki struct {
|
||||||
ExternalWikiURL string `json:"external_wiki_url,omitempty"` // URL of external wiki.
|
ExternalWikiURL string `json:"external_wiki_url,omitempty"` // URL of external wiki.
|
||||||
}
|
}
|
||||||
|
|
||||||
// InternalTracker — InternalTracker represents settings for internal tracker
|
// InternalTracker — InternalTracker represents settings for internal tracker
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := InternalTracker{AllowOnlyContributorsToTrackTime: true}
|
|
||||||
type InternalTracker struct {
|
type InternalTracker struct {
|
||||||
AllowOnlyContributorsToTrackTime bool `json:"allow_only_contributors_to_track_time,omitempty"` // Let only contributors track time (Built-in issue tracker)
|
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)
|
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)
|
EnableTimeTracker bool `json:"enable_time_tracker,omitempty"` // Enable time tracking (Built-in issue tracker)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushMirror — PushMirror represents information of a push mirror
|
// PushMirror — PushMirror represents information of a push mirror
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := PushMirror{RemoteName: "example"}
|
|
||||||
type PushMirror struct {
|
type PushMirror struct {
|
||||||
CreatedUnix time.Time `json:"created,omitempty"`
|
CreatedUnix time.Time `json:"created,omitempty"`
|
||||||
Interval string `json:"interval,omitempty"`
|
Interval string `json:"interval,omitempty"`
|
||||||
LastError string `json:"last_error,omitempty"`
|
LastError string `json:"last_error,omitempty"`
|
||||||
LastUpdateUnix time.Time `json:"last_update,omitempty"`
|
LastUpdateUnix time.Time `json:"last_update,omitempty"`
|
||||||
PublicKey string `json:"public_key,omitempty"`
|
PublicKey string `json:"public_key,omitempty"`
|
||||||
RemoteAddress string `json:"remote_address,omitempty"`
|
RemoteAddress string `json:"remote_address,omitempty"`
|
||||||
RemoteName string `json:"remote_name,omitempty"`
|
RemoteName string `json:"remote_name,omitempty"`
|
||||||
RepoName string `json:"repo_name,omitempty"`
|
RepoName string `json:"repo_name,omitempty"`
|
||||||
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
SyncOnCommit bool `json:"sync_on_commit,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoCollaboratorPermission — RepoCollaboratorPermission to get repository permission for a collaborator
|
// RepoCollaboratorPermission — RepoCollaboratorPermission to get repository permission for a collaborator
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := RepoCollaboratorPermission{RoleName: "example"}
|
|
||||||
type RepoCollaboratorPermission struct {
|
type RepoCollaboratorPermission struct {
|
||||||
Permission string `json:"permission,omitempty"`
|
Permission string `json:"permission,omitempty"`
|
||||||
RoleName string `json:"role_name,omitempty"`
|
RoleName string `json:"role_name,omitempty"`
|
||||||
User *User `json:"user,omitempty"`
|
User *User `json:"user,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := RepoCommit{Message: "example"}
|
|
||||||
type RepoCommit struct {
|
type RepoCommit struct {
|
||||||
Author *CommitUser `json:"author,omitempty"`
|
Author *CommitUser `json:"author,omitempty"`
|
||||||
Committer *CommitUser `json:"committer,omitempty"`
|
Committer *CommitUser `json:"committer,omitempty"`
|
||||||
Message string `json:"message,omitempty"`
|
Message string `json:"message,omitempty"`
|
||||||
Tree *CommitMeta `json:"tree,omitempty"`
|
Tree *CommitMeta `json:"tree,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
Verification *PayloadCommitVerification `json:"verification,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoTopicOptions — RepoTopicOptions a collection of repo topic names
|
// RepoTopicOptions — RepoTopicOptions a collection of repo topic names
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := RepoTopicOptions{Topics: []string{"example"}}
|
|
||||||
type RepoTopicOptions struct {
|
type RepoTopicOptions struct {
|
||||||
Topics []string `json:"topics,omitempty"` // list of topic names
|
Topics []string `json:"topics,omitempty"` // list of topic names
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepoTransfer — RepoTransfer represents a pending repo transfer
|
// RepoTransfer — RepoTransfer represents a pending repo transfer
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := RepoTransfer{Teams: {}}
|
|
||||||
type RepoTransfer struct {
|
type RepoTransfer struct {
|
||||||
Doer *User `json:"doer,omitempty"`
|
Doer *User `json:"doer,omitempty"`
|
||||||
Recipient *User `json:"recipient,omitempty"`
|
Recipient *User `json:"recipient,omitempty"`
|
||||||
Teams []*Team `json:"teams,omitempty"`
|
Teams []*Team `json:"teams,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Repository — Repository represents a repository
|
// Repository — Repository represents a repository
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := Repository{Description: "example"}
|
|
||||||
type Repository struct {
|
type Repository struct {
|
||||||
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge,omitempty"`
|
AllowFastForwardOnly bool `json:"allow_fast_forward_only_merge,omitempty"`
|
||||||
AllowMerge bool `json:"allow_merge_commits,omitempty"`
|
AllowMerge bool `json:"allow_merge_commits,omitempty"`
|
||||||
AllowRebase bool `json:"allow_rebase,omitempty"`
|
AllowRebase bool `json:"allow_rebase,omitempty"`
|
||||||
AllowRebaseMerge bool `json:"allow_rebase_explicit,omitempty"`
|
AllowRebaseMerge bool `json:"allow_rebase_explicit,omitempty"`
|
||||||
AllowRebaseUpdate bool `json:"allow_rebase_update,omitempty"`
|
AllowRebaseUpdate bool `json:"allow_rebase_update,omitempty"`
|
||||||
AllowSquash bool `json:"allow_squash_merge,omitempty"`
|
AllowSquash bool `json:"allow_squash_merge,omitempty"`
|
||||||
Archived bool `json:"archived,omitempty"`
|
Archived bool `json:"archived,omitempty"`
|
||||||
ArchivedAt time.Time `json:"archived_at,omitempty"`
|
ArchivedAt time.Time `json:"archived_at,omitempty"`
|
||||||
AvatarURL string `json:"avatar_url,omitempty"`
|
AvatarURL string `json:"avatar_url,omitempty"`
|
||||||
CloneURL string `json:"clone_url,omitempty"`
|
CloneURL string `json:"clone_url,omitempty"`
|
||||||
Created time.Time `json:"created_at,omitempty"`
|
Created time.Time `json:"created_at,omitempty"`
|
||||||
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit,omitempty"`
|
DefaultAllowMaintainerEdit bool `json:"default_allow_maintainer_edit,omitempty"`
|
||||||
DefaultBranch string `json:"default_branch,omitempty"`
|
DefaultBranch string `json:"default_branch,omitempty"`
|
||||||
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge,omitempty"`
|
DefaultDeleteBranchAfterMerge bool `json:"default_delete_branch_after_merge,omitempty"`
|
||||||
DefaultMergeStyle string `json:"default_merge_style,omitempty"`
|
DefaultMergeStyle string `json:"default_merge_style,omitempty"`
|
||||||
DefaultUpdateStyle string `json:"default_update_style,omitempty"`
|
DefaultUpdateStyle string `json:"default_update_style,omitempty"`
|
||||||
Description string `json:"description,omitempty"`
|
Description string `json:"description,omitempty"`
|
||||||
Empty bool `json:"empty,omitempty"`
|
Empty bool `json:"empty,omitempty"`
|
||||||
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
ExternalTracker *ExternalTracker `json:"external_tracker,omitempty"`
|
||||||
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
ExternalWiki *ExternalWiki `json:"external_wiki,omitempty"`
|
||||||
Fork bool `json:"fork,omitempty"`
|
Fork bool `json:"fork,omitempty"`
|
||||||
Forks int64 `json:"forks_count,omitempty"`
|
Forks int64 `json:"forks_count,omitempty"`
|
||||||
FullName string `json:"full_name,omitempty"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
GloballyEditableWiki bool `json:"globally_editable_wiki,omitempty"`
|
GloballyEditableWiki bool `json:"globally_editable_wiki,omitempty"`
|
||||||
HTMLURL string `json:"html_url,omitempty"`
|
HTMLURL string `json:"html_url,omitempty"`
|
||||||
HasActions bool `json:"has_actions,omitempty"`
|
HasActions bool `json:"has_actions,omitempty"`
|
||||||
HasIssues bool `json:"has_issues,omitempty"`
|
HasIssues bool `json:"has_issues,omitempty"`
|
||||||
HasPackages bool `json:"has_packages,omitempty"`
|
HasPackages bool `json:"has_packages,omitempty"`
|
||||||
HasProjects bool `json:"has_projects,omitempty"`
|
HasProjects bool `json:"has_projects,omitempty"`
|
||||||
HasPullRequests bool `json:"has_pull_requests,omitempty"`
|
HasPullRequests bool `json:"has_pull_requests,omitempty"`
|
||||||
HasReleases bool `json:"has_releases,omitempty"`
|
HasReleases bool `json:"has_releases,omitempty"`
|
||||||
HasWiki bool `json:"has_wiki,omitempty"`
|
HasWiki bool `json:"has_wiki,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts,omitempty"`
|
IgnoreWhitespaceConflicts bool `json:"ignore_whitespace_conflicts,omitempty"`
|
||||||
Internal bool `json:"internal,omitempty"`
|
Internal bool `json:"internal,omitempty"`
|
||||||
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
InternalTracker *InternalTracker `json:"internal_tracker,omitempty"`
|
||||||
Language string `json:"language,omitempty"`
|
Language string `json:"language,omitempty"`
|
||||||
LanguagesURL string `json:"languages_url,omitempty"`
|
LanguagesURL string `json:"languages_url,omitempty"`
|
||||||
Link string `json:"link,omitempty"`
|
Link string `json:"link,omitempty"`
|
||||||
Mirror bool `json:"mirror,omitempty"`
|
Mirror bool `json:"mirror,omitempty"`
|
||||||
MirrorInterval string `json:"mirror_interval,omitempty"`
|
MirrorInterval string `json:"mirror_interval,omitempty"`
|
||||||
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
MirrorUpdated time.Time `json:"mirror_updated,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
ObjectFormatName string `json:"object_format_name,omitempty"` // ObjectFormatName of the underlying git repository
|
ObjectFormatName string `json:"object_format_name,omitempty"` // ObjectFormatName of the underlying git repository
|
||||||
OpenIssues int64 `json:"open_issues_count,omitempty"`
|
OpenIssues int64 `json:"open_issues_count,omitempty"`
|
||||||
OpenPulls int64 `json:"open_pr_counter,omitempty"`
|
OpenPulls int64 `json:"open_pr_counter,omitempty"`
|
||||||
OriginalURL string `json:"original_url,omitempty"`
|
OriginalURL string `json:"original_url,omitempty"`
|
||||||
Owner *User `json:"owner,omitempty"`
|
Owner *User `json:"owner,omitempty"`
|
||||||
Parent *Repository `json:"parent,omitempty"`
|
Parent *Repository `json:"parent,omitempty"`
|
||||||
Permissions *Permission `json:"permissions,omitempty"`
|
Permissions *Permission `json:"permissions,omitempty"`
|
||||||
Private bool `json:"private,omitempty"`
|
Private bool `json:"private,omitempty"`
|
||||||
Releases int64 `json:"release_counter,omitempty"`
|
Releases int64 `json:"release_counter,omitempty"`
|
||||||
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
|
RepoTransfer *RepoTransfer `json:"repo_transfer,omitempty"`
|
||||||
SSHURL string `json:"ssh_url,omitempty"`
|
SSHURL string `json:"ssh_url,omitempty"`
|
||||||
Size int64 `json:"size,omitempty"`
|
Size int64 `json:"size,omitempty"`
|
||||||
Stars int64 `json:"stars_count,omitempty"`
|
Stars int64 `json:"stars_count,omitempty"`
|
||||||
Template bool `json:"template,omitempty"`
|
Template bool `json:"template,omitempty"`
|
||||||
Topics []string `json:"topics,omitempty"`
|
Topics []string `json:"topics,omitempty"`
|
||||||
URL string `json:"url,omitempty"`
|
URL string `json:"url,omitempty"`
|
||||||
Updated time.Time `json:"updated_at,omitempty"`
|
Updated time.Time `json:"updated_at,omitempty"`
|
||||||
Watchers int64 `json:"watchers_count,omitempty"`
|
Watchers int64 `json:"watchers_count,omitempty"`
|
||||||
Website string `json:"website,omitempty"`
|
Website string `json:"website,omitempty"`
|
||||||
WikiBranch string `json:"wiki_branch,omitempty"`
|
WikiBranch string `json:"wiki_branch,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// RepositoryMeta — RepositoryMeta basic repository information
|
// RepositoryMeta — RepositoryMeta basic repository information
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := RepositoryMeta{FullName: "example"}
|
|
||||||
type RepositoryMeta struct {
|
type RepositoryMeta struct {
|
||||||
FullName string `json:"full_name,omitempty"`
|
FullName string `json:"full_name,omitempty"`
|
||||||
ID int64 `json:"id,omitempty"`
|
ID int64 `json:"id,omitempty"`
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Owner string `json:"owner,omitempty"`
|
Owner string `json:"owner,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransferRepoOption — TransferRepoOption options when transfer a repository's ownership
|
// TransferRepoOption — TransferRepoOption options when transfer a repository's ownership
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := TransferRepoOption{NewOwner: "example"}
|
|
||||||
type TransferRepoOption struct {
|
type TransferRepoOption struct {
|
||||||
NewOwner string `json:"new_owner"`
|
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.
|
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
|
// UpdateRepoAvatarOption — UpdateRepoAvatarUserOption options when updating the repo avatar
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := UpdateRepoAvatarOption{Image: "example"}
|
|
||||||
type UpdateRepoAvatarOption struct {
|
type UpdateRepoAvatarOption struct {
|
||||||
Image string `json:"image,omitempty"` // image must be base64 encoded
|
Image string `json:"image,omitempty"` // image must be base64 encoded
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,8 @@
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
|
||||||
// ReviewStateType — ReviewStateType review state type
|
// ReviewStateType — ReviewStateType review state type
|
||||||
//
|
// ReviewStateType has no fields in the swagger spec.
|
||||||
// Usage:
|
type ReviewStateType struct{}
|
||||||
//
|
|
||||||
// opts := ReviewStateType("example")
|
|
||||||
type ReviewStateType string
|
|
||||||
|
|
|
||||||
|
|
@ -2,52 +2,38 @@
|
||||||
|
|
||||||
package types
|
package types
|
||||||
|
|
||||||
|
|
||||||
// GeneralAPISettings — GeneralAPISettings contains global api settings exposed by it
|
// GeneralAPISettings — GeneralAPISettings contains global api settings exposed by it
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GeneralAPISettings{DefaultGitTreesPerPage: 1}
|
|
||||||
type GeneralAPISettings struct {
|
type GeneralAPISettings struct {
|
||||||
DefaultGitTreesPerPage int64 `json:"default_git_trees_per_page,omitempty"`
|
DefaultGitTreesPerPage int64 `json:"default_git_trees_per_page,omitempty"`
|
||||||
DefaultMaxBlobSize int64 `json:"default_max_blob_size,omitempty"`
|
DefaultMaxBlobSize int64 `json:"default_max_blob_size,omitempty"`
|
||||||
DefaultPagingNum int64 `json:"default_paging_num,omitempty"`
|
DefaultPagingNum int64 `json:"default_paging_num,omitempty"`
|
||||||
MaxResponseItems int64 `json:"max_response_items,omitempty"`
|
MaxResponseItems int64 `json:"max_response_items,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneralAttachmentSettings — GeneralAttachmentSettings contains global Attachment settings exposed by API
|
// GeneralAttachmentSettings — GeneralAttachmentSettings contains global Attachment settings exposed by API
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GeneralAttachmentSettings{AllowedTypes: "example"}
|
|
||||||
type GeneralAttachmentSettings struct {
|
type GeneralAttachmentSettings struct {
|
||||||
AllowedTypes string `json:"allowed_types,omitempty"`
|
AllowedTypes string `json:"allowed_types,omitempty"`
|
||||||
Enabled bool `json:"enabled,omitempty"`
|
Enabled bool `json:"enabled,omitempty"`
|
||||||
MaxFiles int64 `json:"max_files,omitempty"`
|
MaxFiles int64 `json:"max_files,omitempty"`
|
||||||
MaxSize int64 `json:"max_size,omitempty"`
|
MaxSize int64 `json:"max_size,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneralRepoSettings — GeneralRepoSettings contains global repository settings exposed by API
|
// GeneralRepoSettings — GeneralRepoSettings contains global repository settings exposed by API
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GeneralRepoSettings{ForksDisabled: true}
|
|
||||||
type GeneralRepoSettings struct {
|
type GeneralRepoSettings struct {
|
||||||
ForksDisabled bool `json:"forks_disabled,omitempty"`
|
ForksDisabled bool `json:"forks_disabled,omitempty"`
|
||||||
HTTPGitDisabled bool `json:"http_git_disabled,omitempty"`
|
HTTPGitDisabled bool `json:"http_git_disabled,omitempty"`
|
||||||
LFSDisabled bool `json:"lfs_disabled,omitempty"`
|
LFSDisabled bool `json:"lfs_disabled,omitempty"`
|
||||||
MigrationsDisabled bool `json:"migrations_disabled,omitempty"`
|
MigrationsDisabled bool `json:"migrations_disabled,omitempty"`
|
||||||
MirrorsDisabled bool `json:"mirrors_disabled,omitempty"`
|
MirrorsDisabled bool `json:"mirrors_disabled,omitempty"`
|
||||||
StarsDisabled bool `json:"stars_disabled,omitempty"`
|
StarsDisabled bool `json:"stars_disabled,omitempty"`
|
||||||
TimeTrackingDisabled bool `json:"time_tracking_disabled,omitempty"`
|
TimeTrackingDisabled bool `json:"time_tracking_disabled,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneralUISettings — GeneralUISettings contains global ui settings exposed by API
|
// GeneralUISettings — GeneralUISettings contains global ui settings exposed by API
|
||||||
//
|
|
||||||
// Usage:
|
|
||||||
//
|
|
||||||
// opts := GeneralUISettings{AllowedReactions: []string{"example"}}
|
|
||||||
type GeneralUISettings struct {
|
type GeneralUISettings struct {
|
||||||
AllowedReactions []string `json:"allowed_reactions,omitempty"`
|
AllowedReactions []string `json:"allowed_reactions,omitempty"`
|
||||||
CustomEmojis []string `json:"custom_emojis,omitempty"`
|
CustomEmojis []string `json:"custom_emojis,omitempty"`
|
||||||
DefaultTheme string `json:"default_theme,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