From 06dce3c98ecbad61c63e3fdd2f98d821e68e64eb Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 26 Mar 2026 10:38:45 +0000 Subject: [PATCH 1/5] docs(package): fix stale usage example Co-Authored-By: Virgil --- doc.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc.go b/doc.go index e0bd38c..1a3193d 100644 --- a/doc.go +++ b/doc.go @@ -2,8 +2,9 @@ // // Usage: // +// ctx := context.Background() // f := forge.NewForge("https://forge.lthn.ai", "your-token") -// repos, err := f.Repos.List(ctx, forge.Params{"org": "core"}, forge.DefaultList) +// repos, err := f.Repos.ListOrgRepos(ctx, "core") // // Types are generated from Forgejo's swagger.v1.json spec via cmd/forgegen/. // Run `go generate ./types/...` to regenerate after a Forgejo upgrade. -- 2.45.3 From 0513b94d87ea503b153314d7f762ff26318811e4 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 26 Mar 2026 18:00:20 +0000 Subject: [PATCH 2/5] refactor(ax): enforce v0.8.0 polish rules Co-Authored-By: Virgil --- actions.go | 32 ++++++------ actions_test.go | 22 ++++----- admin.go | 5 ++ admin_test.go | 26 +++++----- branches.go | 18 ++++--- branches_test.go | 8 +-- client.go | 90 ++++++++++++++++++++++++++-------- client_test.go | 37 +++++++------- cmd/forgegen/generator.go | 36 ++++++++------ cmd/forgegen/generator_test.go | 40 +++++++-------- cmd/forgegen/helpers.go | 41 ++++++++++++++++ cmd/forgegen/main.go | 14 +++--- cmd/forgegen/parser.go | 81 ++++++++++++++++++++++-------- cmd/forgegen/parser_test.go | 8 +-- commits.go | 14 ++++-- commits_test.go | 16 +++--- config.go | 25 ++++++++-- config_test.go | 17 +++---- contents.go | 16 +++--- contents_test.go | 16 +++--- forge.go | 10 ++++ forge_test.go | 18 +++---- go.mod | 3 +- go.sum | 6 ++- helpers.go | 35 +++++++++++++ issues.go | 30 +++++++----- issues_test.go | 20 ++++---- labels.go | 24 +++++---- labels_test.go | 18 +++---- milestones.go | 12 +++-- misc.go | 10 ++-- misc_test.go | 18 +++---- notifications.go | 14 ++++-- notifications_test.go | 14 +++--- orgs.go | 18 ++++--- orgs_test.go | 8 +-- packages.go | 18 ++++--- packages_test.go | 12 ++--- pagination.go | 35 ++++++++++++- pagination_test.go | 14 +++--- params.go | 15 +++++- params_test.go | 8 +-- pulls.go | 20 +++++--- pulls_test.go | 12 ++--- releases.go | 18 ++++--- releases_test.go | 8 +-- repos.go | 5 ++ resource.go | 17 +++++-- resource_test.go | 20 ++++---- teams.go | 26 +++++----- teams_test.go | 8 +-- users.go | 26 +++++----- users_test.go | 8 +-- webhooks.go | 12 +++-- webhooks_test.go | 14 +++--- wiki.go | 16 +++--- wiki_test.go | 14 +++--- 57 files changed, 735 insertions(+), 411 deletions(-) create mode 100644 cmd/forgegen/helpers.go create mode 100644 helpers.go diff --git a/actions.go b/actions.go index ba110b8..0e9a39f 100644 --- a/actions.go +++ b/actions.go @@ -2,7 +2,6 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" @@ -11,6 +10,11 @@ import ( // 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. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Actions.ListRepoSecrets(ctx, "core", "go-forge") type ActionsService struct { client *Client } @@ -21,82 +25,82 @@ func newActionsService(c *Client) *ActionsService { // ListRepoSecrets returns all secrets for a repository. func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string) ([]types.Secret, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets", pathParams("owner", owner, "repo", repo)) return ListAll[types.Secret](ctx, s.client, path, nil) } // IterRepoSecrets returns an iterator over all secrets for a repository. func (s *ActionsService) IterRepoSecrets(ctx context.Context, owner, repo string) iter.Seq2[types.Secret, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets", pathParams("owner", owner, "repo", repo)) return ListIter[types.Secret](ctx, s.client, path, nil) } // CreateRepoSecret creates or updates a secret in a repository. // Forgejo expects a PUT with {"data": "secret-value"} body. func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{name}", pathParams("owner", owner, "repo", repo, "name", name)) body := map[string]string{"data": data} return s.client.Put(ctx, path, body, nil) } // DeleteRepoSecret removes a secret from a repository. func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{name}", pathParams("owner", owner, "repo", repo, "name", name)) return s.client.Delete(ctx, path) } // ListRepoVariables returns all action variables for a repository. func (s *ActionsService) ListRepoVariables(ctx context.Context, owner, repo string) ([]types.ActionVariable, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables", pathParams("owner", owner, "repo", repo)) return ListAll[types.ActionVariable](ctx, s.client, path, nil) } // IterRepoVariables returns an iterator over all action variables for a repository. func (s *ActionsService) IterRepoVariables(ctx context.Context, owner, repo string) iter.Seq2[types.ActionVariable, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables", pathParams("owner", owner, "repo", repo)) return ListIter[types.ActionVariable](ctx, s.client, path, nil) } // CreateRepoVariable creates a new action variable in a repository. // Forgejo expects a POST with {"value": "var-value"} body. func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/%s", owner, repo, name) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{name}", pathParams("owner", owner, "repo", repo, "name", name)) body := types.CreateVariableOption{Value: value} return s.client.Post(ctx, path, body, nil) } // DeleteRepoVariable removes an action variable from a repository. func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/%s", owner, repo, name) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{name}", pathParams("owner", owner, "repo", repo, "name", name)) return s.client.Delete(ctx, path) } // ListOrgSecrets returns all secrets for an organisation. func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string) ([]types.Secret, error) { - path := fmt.Sprintf("/api/v1/orgs/%s/actions/secrets", org) + path := ResolvePath("/api/v1/orgs/{org}/actions/secrets", pathParams("org", org)) return ListAll[types.Secret](ctx, s.client, path, nil) } // IterOrgSecrets returns an iterator over all secrets for an organisation. func (s *ActionsService) IterOrgSecrets(ctx context.Context, org string) iter.Seq2[types.Secret, error] { - path := fmt.Sprintf("/api/v1/orgs/%s/actions/secrets", org) + path := ResolvePath("/api/v1/orgs/{org}/actions/secrets", pathParams("org", org)) return ListIter[types.Secret](ctx, s.client, path, nil) } // ListOrgVariables returns all action variables for an organisation. func (s *ActionsService) ListOrgVariables(ctx context.Context, org string) ([]types.ActionVariable, error) { - path := fmt.Sprintf("/api/v1/orgs/%s/actions/variables", org) + path := ResolvePath("/api/v1/orgs/{org}/actions/variables", pathParams("org", org)) return ListAll[types.ActionVariable](ctx, s.client, path, nil) } // IterOrgVariables returns an iterator over all action variables for an organisation. func (s *ActionsService) IterOrgVariables(ctx context.Context, org string) iter.Seq2[types.ActionVariable, error] { - path := fmt.Sprintf("/api/v1/orgs/%s/actions/variables", org) + path := ResolvePath("/api/v1/orgs/{org}/actions/variables", pathParams("org", org)) return ListIter[types.ActionVariable](ctx, s.client, path, nil) } // DispatchWorkflow triggers a workflow run. func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/dispatches", owner, repo, workflow) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/workflows/{workflow}/dispatches", pathParams("owner", owner, "repo", repo, "workflow", workflow)) return s.client.Post(ctx, path, opts, nil) } diff --git a/actions_test.go b/actions_test.go index 9304440..17b826b 100644 --- a/actions_test.go +++ b/actions_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestActionsService_Good_ListRepoSecrets(t *testing.T) { +func TestActionsService_ListRepoSecrets_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) @@ -42,7 +42,7 @@ func TestActionsService_Good_ListRepoSecrets(t *testing.T) { } } -func TestActionsService_Good_CreateRepoSecret(t *testing.T) { +func TestActionsService_CreateRepoSecret_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) @@ -68,7 +68,7 @@ func TestActionsService_Good_CreateRepoSecret(t *testing.T) { } } -func TestActionsService_Good_DeleteRepoSecret(t *testing.T) { +func TestActionsService_DeleteRepoSecret_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) @@ -87,7 +87,7 @@ func TestActionsService_Good_DeleteRepoSecret(t *testing.T) { } } -func TestActionsService_Good_ListRepoVariables(t *testing.T) { +func TestActionsService_ListRepoVariables_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) @@ -115,7 +115,7 @@ func TestActionsService_Good_ListRepoVariables(t *testing.T) { } } -func TestActionsService_Good_CreateRepoVariable(t *testing.T) { +func TestActionsService_CreateRepoVariable_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) @@ -141,7 +141,7 @@ func TestActionsService_Good_CreateRepoVariable(t *testing.T) { } } -func TestActionsService_Good_DeleteRepoVariable(t *testing.T) { +func TestActionsService_DeleteRepoVariable_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) @@ -160,7 +160,7 @@ func TestActionsService_Good_DeleteRepoVariable(t *testing.T) { } } -func TestActionsService_Good_ListOrgSecrets(t *testing.T) { +func TestActionsService_ListOrgSecrets_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) @@ -188,7 +188,7 @@ func TestActionsService_Good_ListOrgSecrets(t *testing.T) { } } -func TestActionsService_Good_ListOrgVariables(t *testing.T) { +func TestActionsService_ListOrgVariables_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) @@ -216,7 +216,7 @@ func TestActionsService_Good_ListOrgVariables(t *testing.T) { } } -func TestActionsService_Good_DispatchWorkflow(t *testing.T) { +func TestActionsService_DispatchWorkflow_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) @@ -244,7 +244,7 @@ func TestActionsService_Good_DispatchWorkflow(t *testing.T) { } } -func TestActionsService_Bad_NotFound(t *testing.T) { +func TestActionsService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "not found"}) diff --git a/admin.go b/admin.go index a887316..825abef 100644 --- a/admin.go +++ b/admin.go @@ -10,6 +10,11 @@ import ( // AdminService handles site administration operations. // Unlike other services, AdminService does not embed Resource[T,C,U] // because admin endpoints are heterogeneous. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Admin.ListUsers(ctx) type AdminService struct { client *Client } diff --git a/admin_test.go b/admin_test.go index 8901d7f..0f21d9f 100644 --- a/admin_test.go +++ b/admin_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestAdminService_Good_ListUsers(t *testing.T) { +func TestAdminService_ListUsers_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) @@ -39,7 +39,7 @@ func TestAdminService_Good_ListUsers(t *testing.T) { } } -func TestAdminService_Good_CreateUser(t *testing.T) { +func TestAdminService_CreateUser_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) @@ -78,7 +78,7 @@ func TestAdminService_Good_CreateUser(t *testing.T) { } } -func TestAdminService_Good_DeleteUser(t *testing.T) { +func TestAdminService_DeleteUser_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) @@ -96,7 +96,7 @@ func TestAdminService_Good_DeleteUser(t *testing.T) { } } -func TestAdminService_Good_RunCron(t *testing.T) { +func TestAdminService_RunCron_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) @@ -114,7 +114,7 @@ func TestAdminService_Good_RunCron(t *testing.T) { } } -func TestAdminService_Good_EditUser(t *testing.T) { +func TestAdminService_EditUser_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) @@ -142,7 +142,7 @@ func TestAdminService_Good_EditUser(t *testing.T) { } } -func TestAdminService_Good_RenameUser(t *testing.T) { +func TestAdminService_RenameUser_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) @@ -167,7 +167,7 @@ func TestAdminService_Good_RenameUser(t *testing.T) { } } -func TestAdminService_Good_ListOrgs(t *testing.T) { +func TestAdminService_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) @@ -195,7 +195,7 @@ func TestAdminService_Good_ListOrgs(t *testing.T) { } } -func TestAdminService_Good_ListCron(t *testing.T) { +func TestAdminService_ListCron_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) @@ -223,7 +223,7 @@ func TestAdminService_Good_ListCron(t *testing.T) { } } -func TestAdminService_Good_AdoptRepo(t *testing.T) { +func TestAdminService_AdoptRepo_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) @@ -241,7 +241,7 @@ func TestAdminService_Good_AdoptRepo(t *testing.T) { } } -func TestAdminService_Good_GenerateRunnerToken(t *testing.T) { +func TestAdminService_GenerateRunnerToken_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) @@ -263,7 +263,7 @@ func TestAdminService_Good_GenerateRunnerToken(t *testing.T) { } } -func TestAdminService_Bad_DeleteUser_NotFound(t *testing.T) { +func TestAdminService_DeleteUser_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "user not found"}) @@ -277,7 +277,7 @@ func TestAdminService_Bad_DeleteUser_NotFound(t *testing.T) { } } -func TestAdminService_Bad_CreateUser_Forbidden(t *testing.T) { +func TestAdminService_CreateUser_Forbidden_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusForbidden) json.NewEncoder(w).Encode(map[string]string{"message": "only admins can create users"}) diff --git a/branches.go b/branches.go index 66af9a4..b7d4fd1 100644 --- a/branches.go +++ b/branches.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" ) // BranchService handles branch operations within a repository. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Branches.ListBranchProtections(ctx, "core", "go-forge") type BranchService struct { Resource[types.Branch, types.CreateBranchRepoOption, struct{}] } @@ -23,19 +27,19 @@ func newBranchService(c *Client) *BranchService { // ListBranchProtections returns all branch protections for a repository. func (s *BranchService) ListBranchProtections(ctx context.Context, owner, repo string) ([]types.BranchProtection, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections", pathParams("owner", owner, "repo", repo)) return ListAll[types.BranchProtection](ctx, s.client, path, nil) } // IterBranchProtections returns an iterator over all branch protections for a repository. func (s *BranchService) IterBranchProtections(ctx context.Context, owner, repo string) iter.Seq2[types.BranchProtection, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections", pathParams("owner", owner, "repo", repo)) return ListIter[types.BranchProtection](ctx, s.client, path, nil) } // GetBranchProtection returns a single branch protection by name. func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, name string) (*types.BranchProtection, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections/{name}", pathParams("owner", owner, "repo", repo, "name", name)) var out types.BranchProtection if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -45,7 +49,7 @@ func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, na // CreateBranchProtection creates a new branch protection rule. func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo string, opts *types.CreateBranchProtectionOption) (*types.BranchProtection, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections", pathParams("owner", owner, "repo", repo)) var out types.BranchProtection if err := s.client.Post(ctx, path, opts, &out); err != nil { return nil, err @@ -55,7 +59,7 @@ func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo // EditBranchProtection updates an existing branch protection rule. func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, name string, opts *types.EditBranchProtectionOption) (*types.BranchProtection, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections/{name}", pathParams("owner", owner, "repo", repo, "name", name)) var out types.BranchProtection if err := s.client.Patch(ctx, path, opts, &out); err != nil { return nil, err @@ -65,6 +69,6 @@ func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, n // DeleteBranchProtection deletes a branch protection rule. func (s *BranchService) DeleteBranchProtection(ctx context.Context, owner, repo, name string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections/{name}", pathParams("owner", owner, "repo", repo, "name", name)) return s.client.Delete(ctx, path) } diff --git a/branches_test.go b/branches_test.go index 22d1302..ca1f99e 100644 --- a/branches_test.go +++ b/branches_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestBranchService_Good_List(t *testing.T) { +func TestBranchService_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) @@ -36,7 +36,7 @@ func TestBranchService_Good_List(t *testing.T) { } } -func TestBranchService_Good_Get(t *testing.T) { +func TestBranchService_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) @@ -61,7 +61,7 @@ func TestBranchService_Good_Get(t *testing.T) { } } -func TestBranchService_Good_CreateProtection(t *testing.T) { +func TestBranchService_CreateProtection_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) diff --git a/client.go b/client.go index cb10233..8b4ca90 100644 --- a/client.go +++ b/client.go @@ -3,18 +3,21 @@ package forge import ( "bytes" "context" - "encoding/json" - "errors" - "fmt" + json "github.com/goccy/go-json" "io" "net/http" "strconv" - "strings" - coreerr "dappco.re/go/core/log" + core "dappco.re/go/core" ) // APIError represents an error response from the Forgejo API. +// +// Usage: +// +// if apiErr, ok := err.(*forge.APIError); ok { +// _ = apiErr.StatusCode +// } type APIError struct { StatusCode int Message string @@ -22,41 +25,76 @@ type APIError struct { } func (e *APIError) Error() string { - return fmt.Sprintf("forge: %s %d: %s", e.URL, e.StatusCode, e.Message) + return core.Concat("forge: ", e.URL, " ", strconv.Itoa(e.StatusCode), ": ", e.Message) } // IsNotFound returns true if the error is a 404 response. +// +// Usage: +// +// if forge.IsNotFound(err) { +// return nil +// } func IsNotFound(err error) bool { var apiErr *APIError - return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound + return core.As(err, &apiErr) && apiErr.StatusCode == http.StatusNotFound } // IsForbidden returns true if the error is a 403 response. +// +// Usage: +// +// if forge.IsForbidden(err) { +// return nil +// } func IsForbidden(err error) bool { var apiErr *APIError - return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusForbidden + return core.As(err, &apiErr) && apiErr.StatusCode == http.StatusForbidden } // IsConflict returns true if the error is a 409 response. +// +// Usage: +// +// if forge.IsConflict(err) { +// return nil +// } func IsConflict(err error) bool { var apiErr *APIError - return errors.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict + return core.As(err, &apiErr) && apiErr.StatusCode == http.StatusConflict } // Option configures the Client. +// +// Usage: +// +// opts := []forge.Option{forge.WithUserAgent("go-forge/1.0")} type Option func(*Client) // WithHTTPClient sets a custom http.Client. +// +// Usage: +// +// c := forge.NewClient(url, token, forge.WithHTTPClient(http.DefaultClient)) func WithHTTPClient(hc *http.Client) Option { return func(c *Client) { c.httpClient = hc } } // WithUserAgent sets the User-Agent header. +// +// Usage: +// +// c := forge.NewClient(url, token, forge.WithUserAgent("go-forge/1.0")) func WithUserAgent(ua string) Option { return func(c *Client) { c.userAgent = ua } } // RateLimit represents the rate limit information from the Forgejo API. +// +// Usage: +// +// rl := client.RateLimit() +// _ = rl.Remaining type RateLimit struct { Limit int Remaining int @@ -64,6 +102,11 @@ type RateLimit struct { } // Client is a low-level HTTP client for the Forgejo API. +// +// Usage: +// +// c := forge.NewClient("https://forge.lthn.ai", "token") +// _ = c type Client struct { baseURL string token string @@ -78,9 +121,14 @@ func (c *Client) RateLimit() RateLimit { } // NewClient creates a new Forgejo API client. +// +// Usage: +// +// c := forge.NewClient("https://forge.lthn.ai", "token") +// _ = c func NewClient(url, token string, opts ...Option) *Client { c := &Client{ - baseURL: strings.TrimRight(url, "/"), + baseURL: trimTrailingSlashes(url), token: token, httpClient: &http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { @@ -141,14 +189,14 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er if body != nil { data, err := json.Marshal(body) if err != nil { - return nil, coreerr.E("Client.PostRaw", "forge: marshal body", err) + return nil, core.E("Client.PostRaw", "forge: marshal body", err) } bodyReader = bytes.NewReader(data) } req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader) if err != nil { - return nil, coreerr.E("Client.PostRaw", "forge: create request", err) + return nil, core.E("Client.PostRaw", "forge: create request", err) } req.Header.Set("Authorization", "token "+c.token) @@ -159,7 +207,7 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er resp, err := c.httpClient.Do(req) if err != nil { - return nil, coreerr.E("Client.PostRaw", "forge: request POST "+path, err) + return nil, core.E("Client.PostRaw", "forge: request POST "+path, err) } defer resp.Body.Close() @@ -169,7 +217,7 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er data, err := io.ReadAll(resp.Body) if err != nil { - return nil, coreerr.E("Client.PostRaw", "forge: read response body", err) + return nil, core.E("Client.PostRaw", "forge: read response body", err) } return data, nil @@ -182,7 +230,7 @@ func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) { req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) if err != nil { - return nil, coreerr.E("Client.GetRaw", "forge: create request", err) + return nil, core.E("Client.GetRaw", "forge: create request", err) } req.Header.Set("Authorization", "token "+c.token) @@ -192,7 +240,7 @@ func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) { resp, err := c.httpClient.Do(req) if err != nil { - return nil, coreerr.E("Client.GetRaw", "forge: request GET "+path, err) + return nil, core.E("Client.GetRaw", "forge: request GET "+path, err) } defer resp.Body.Close() @@ -202,7 +250,7 @@ func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) { data, err := io.ReadAll(resp.Body) if err != nil { - return nil, coreerr.E("Client.GetRaw", "forge: read response body", err) + return nil, core.E("Client.GetRaw", "forge: read response body", err) } return data, nil @@ -220,14 +268,14 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) if body != nil { data, err := json.Marshal(body) if err != nil { - return nil, coreerr.E("Client.doJSON", "forge: marshal body", err) + return nil, core.E("Client.doJSON", "forge: marshal body", err) } bodyReader = bytes.NewReader(data) } req, err := http.NewRequestWithContext(ctx, method, url, bodyReader) if err != nil { - return nil, coreerr.E("Client.doJSON", "forge: create request", err) + return nil, core.E("Client.doJSON", "forge: create request", err) } req.Header.Set("Authorization", "token "+c.token) @@ -241,7 +289,7 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) resp, err := c.httpClient.Do(req) if err != nil { - return nil, coreerr.E("Client.doJSON", "forge: request "+method+" "+path, err) + return nil, core.E("Client.doJSON", "forge: request "+method+" "+path, err) } defer resp.Body.Close() @@ -253,7 +301,7 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any) if out != nil && resp.StatusCode != http.StatusNoContent { if err := json.NewDecoder(resp.Body).Decode(out); err != nil { - return nil, coreerr.E("Client.doJSON", "forge: decode response", err) + return nil, core.E("Client.doJSON", "forge: decode response", err) } } diff --git a/client_test.go b/client_test.go index 1c67351..57fb5c6 100644 --- a/client_test.go +++ b/client_test.go @@ -2,14 +2,15 @@ package forge import ( "context" - "encoding/json" - "errors" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" + + core "dappco.re/go/core" ) -func TestClient_Good_Get(t *testing.T) { +func TestClient_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) @@ -35,7 +36,7 @@ func TestClient_Good_Get(t *testing.T) { } } -func TestClient_Good_Post(t *testing.T) { +func TestClient_Post_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) @@ -62,7 +63,7 @@ func TestClient_Good_Post(t *testing.T) { } } -func TestClient_Good_Delete(t *testing.T) { +func TestClient_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) @@ -78,7 +79,7 @@ func TestClient_Good_Delete(t *testing.T) { } } -func TestClient_Bad_ServerError(t *testing.T) { +func TestClient_ServerError_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"message": "internal error"}) @@ -91,7 +92,7 @@ func TestClient_Bad_ServerError(t *testing.T) { t.Fatal("expected error") } var apiErr *APIError - if !errors.As(err, &apiErr) { + if !core.As(err, &apiErr) { t.Fatalf("expected APIError, got %T", err) } if apiErr.StatusCode != 500 { @@ -99,7 +100,7 @@ func TestClient_Bad_ServerError(t *testing.T) { } } -func TestClient_Bad_NotFound(t *testing.T) { +func TestClient_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "not found"}) @@ -113,7 +114,7 @@ func TestClient_Bad_NotFound(t *testing.T) { } } -func TestClient_Good_ContextCancellation(t *testing.T) { +func TestClient_ContextCancellation_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-r.Context().Done() })) @@ -128,7 +129,7 @@ func TestClient_Good_ContextCancellation(t *testing.T) { } } -func TestClient_Good_Options(t *testing.T) { +func TestClient_Options_Good(t *testing.T) { c := NewClient("https://forge.lthn.ai", "tok", WithUserAgent("go-forge/1.0"), ) @@ -137,7 +138,7 @@ func TestClient_Good_Options(t *testing.T) { } } -func TestClient_Good_WithHTTPClient(t *testing.T) { +func TestClient_WithHTTPClient_Good(t *testing.T) { custom := &http.Client{} c := NewClient("https://forge.lthn.ai", "tok", WithHTTPClient(custom)) if c.httpClient != custom { @@ -145,7 +146,7 @@ func TestClient_Good_WithHTTPClient(t *testing.T) { } } -func TestAPIError_Good_Error(t *testing.T) { +func TestAPIError_Error_Good(t *testing.T) { e := &APIError{StatusCode: 404, Message: "not found", URL: "/api/v1/repos/x/y"} got := e.Error() want := "forge: /api/v1/repos/x/y 404: not found" @@ -154,28 +155,28 @@ func TestAPIError_Good_Error(t *testing.T) { } } -func TestIsConflict_Good(t *testing.T) { +func TestIsConflict_Match_Good(t *testing.T) { err := &APIError{StatusCode: http.StatusConflict, Message: "conflict", URL: "/test"} if !IsConflict(err) { t.Error("expected IsConflict to return true for 409") } } -func TestIsConflict_Bad_NotConflict(t *testing.T) { +func TestIsConflict_NotConflict_Bad(t *testing.T) { err := &APIError{StatusCode: http.StatusNotFound, Message: "not found", URL: "/test"} if IsConflict(err) { t.Error("expected IsConflict to return false for 404") } } -func TestIsForbidden_Bad_NotForbidden(t *testing.T) { +func TestIsForbidden_NotForbidden_Bad(t *testing.T) { err := &APIError{StatusCode: http.StatusNotFound, Message: "not found", URL: "/test"} if IsForbidden(err) { t.Error("expected IsForbidden to return false for 404") } } -func TestClient_Good_RateLimit(t *testing.T) { +func TestClient_RateLimit_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-RateLimit-Limit", "100") w.Header().Set("X-RateLimit-Remaining", "99") @@ -202,7 +203,7 @@ func TestClient_Good_RateLimit(t *testing.T) { } } -func TestClient_Bad_Forbidden(t *testing.T) { +func TestClient_Forbidden_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusForbidden) json.NewEncoder(w).Encode(map[string]string{"message": "forbidden"}) @@ -216,7 +217,7 @@ func TestClient_Bad_Forbidden(t *testing.T) { } } -func TestClient_Bad_Conflict(t *testing.T) { +func TestClient_Conflict_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusConflict) json.NewEncoder(w).Encode(map[string]string{"message": "already exists"}) diff --git a/cmd/forgegen/generator.go b/cmd/forgegen/generator.go index aea2da5..3e0a3fb 100644 --- a/cmd/forgegen/generator.go +++ b/cmd/forgegen/generator.go @@ -2,14 +2,13 @@ package main import ( "bytes" + "cmp" "maps" - "path/filepath" "slices" - "strings" "text/template" + core "dappco.re/go/core" coreio "dappco.re/go/core/io" - coreerr "dappco.re/go/core/log" ) // typeGrouping maps type name prefixes to output file names. @@ -110,7 +109,7 @@ func classifyType(name string) string { bestKey := "" bestGroup := "" for key, group := range typeGrouping { - if strings.HasPrefix(name, key) && len(key) > len(bestKey) { + if core.HasPrefix(name, key) && len(key) > len(bestKey) { bestKey = key bestGroup = group } @@ -122,10 +121,10 @@ func classifyType(name string) string { // Strip CRUD prefixes and Option suffix, then retry. base := name for _, prefix := range []string{"Create", "Edit", "Delete", "Update", "Add", "Submit", "Replace", "Set", "Transfer"} { - base = strings.TrimPrefix(base, prefix) + base = core.TrimPrefix(base, prefix) } - base = strings.TrimSuffix(base, "Option") - base = strings.TrimSuffix(base, "Options") + base = core.TrimSuffix(base, "Option") + base = core.TrimSuffix(base, "Options") if base != name && base != "" { if group, ok := typeGrouping[base]; ok { @@ -135,7 +134,7 @@ func classifyType(name string) string { bestKey = "" bestGroup = "" for key, group := range typeGrouping { - if strings.HasPrefix(base, key) && len(key) > len(bestKey) { + if core.HasPrefix(base, key) && len(key) > len(bestKey) { bestKey = key bestGroup = group } @@ -151,7 +150,7 @@ func classifyType(name string) string { // sanitiseLine collapses a multi-line string into a single line, // replacing newlines and consecutive whitespace with a single space. func sanitiseLine(s string) string { - return strings.Join(strings.Fields(s), " ") + return core.Join(" ", splitFields(s)...) } // enumConstName generates a Go constant name for an enum value. @@ -204,9 +203,14 @@ type templateData struct { } // Generate writes Go source files for the extracted types, grouped by logical domain. +// +// Usage: +// +// err := Generate(types, pairs, "types") +// _ = err func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error { if err := coreio.Local.EnsureDir(outDir); err != nil { - return coreerr.E("Generate", "create output directory", err) + return core.E("Generate", "create output directory", err) } // Group types by output file. @@ -219,7 +223,7 @@ func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error { // Sort types within each group for deterministic output. for file := range groups { slices.SortFunc(groups[file], func(a, b *GoType) int { - return strings.Compare(a.Name, b.Name) + return cmp.Compare(a.Name, b.Name) }) } @@ -228,9 +232,9 @@ func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error { slices.Sort(fileNames) for _, file := range fileNames { - outPath := filepath.Join(outDir, file+".go") + outPath := core.JoinPath(outDir, file+".go") if err := writeFile(outPath, groups[file]); err != nil { - return coreerr.E("Generate", "write "+outPath, err) + return core.E("Generate", "write "+outPath, err) } } @@ -241,7 +245,7 @@ func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error { func writeFile(path string, types []*GoType) error { needTime := slices.ContainsFunc(types, func(gt *GoType) bool { return slices.ContainsFunc(gt.Fields, func(f GoField) bool { - return strings.Contains(f.GoType, "time.Time") + return core.Contains(f.GoType, "time.Time") }) }) @@ -252,11 +256,11 @@ func writeFile(path string, types []*GoType) error { var buf bytes.Buffer if err := fileHeader.Execute(&buf, data); err != nil { - return coreerr.E("writeFile", "execute template", err) + return core.E("writeFile", "execute template", err) } if err := coreio.Local.Write(path, buf.String()); err != nil { - return coreerr.E("writeFile", "write file", err) + return core.E("writeFile", "write file", err) } return nil diff --git a/cmd/forgegen/generator_test.go b/cmd/forgegen/generator_test.go index 9f60e45..ae3b059 100644 --- a/cmd/forgegen/generator_test.go +++ b/cmd/forgegen/generator_test.go @@ -1,15 +1,13 @@ package main import ( - "os" - "path/filepath" - "strings" "testing" + core "dappco.re/go/core" coreio "dappco.re/go/core/io" ) -func TestGenerate_Good_CreatesFiles(t *testing.T) { +func TestGenerate_CreatesFiles_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) @@ -23,10 +21,10 @@ func TestGenerate_Good_CreatesFiles(t *testing.T) { t.Fatal(err) } - entries, _ := os.ReadDir(outDir) + entries, _ := coreio.Local.List(outDir) goFiles := 0 for _, e := range entries { - if strings.HasSuffix(e.Name(), ".go") { + if core.HasSuffix(e.Name(), ".go") { goFiles++ } } @@ -35,7 +33,7 @@ func TestGenerate_Good_CreatesFiles(t *testing.T) { } } -func TestGenerate_Good_ValidGoSyntax(t *testing.T) { +func TestGenerate_ValidGoSyntax_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) @@ -49,11 +47,11 @@ func TestGenerate_Good_ValidGoSyntax(t *testing.T) { t.Fatal(err) } - entries, _ := os.ReadDir(outDir) + entries, _ := coreio.Local.List(outDir) var content string for _, e := range entries { - if strings.HasSuffix(e.Name(), ".go") { - content, err = coreio.Local.Read(filepath.Join(outDir, e.Name())) + if core.HasSuffix(e.Name(), ".go") { + content, err = coreio.Local.Read(core.JoinPath(outDir, e.Name())) if err == nil { break } @@ -62,15 +60,15 @@ func TestGenerate_Good_ValidGoSyntax(t *testing.T) { if err != nil || content == "" { t.Fatal("could not read any generated file") } - if !strings.Contains(content, "package types") { + if !core.Contains(content, "package types") { t.Error("missing package declaration") } - if !strings.Contains(content, "// Code generated") { + if !core.Contains(content, "// Code generated") { t.Error("missing generated comment") } } -func TestGenerate_Good_RepositoryType(t *testing.T) { +func TestGenerate_RepositoryType_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) @@ -85,10 +83,10 @@ func TestGenerate_Good_RepositoryType(t *testing.T) { } var content string - entries, _ := os.ReadDir(outDir) + entries, _ := coreio.Local.List(outDir) for _, e := range entries { - data, _ := coreio.Local.Read(filepath.Join(outDir, e.Name())) - if strings.Contains(data, "type Repository struct") { + data, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name())) + if core.Contains(data, "type Repository struct") { content = data break } @@ -107,13 +105,13 @@ func TestGenerate_Good_RepositoryType(t *testing.T) { "`json:\"private,omitempty\"`", } for _, check := range checks { - if !strings.Contains(content, check) { + if !core.Contains(content, check) { t.Errorf("missing field with tag %s", check) } } } -func TestGenerate_Good_TimeImport(t *testing.T) { +func TestGenerate_TimeImport_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) @@ -127,10 +125,10 @@ func TestGenerate_Good_TimeImport(t *testing.T) { t.Fatal(err) } - entries, _ := os.ReadDir(outDir) + entries, _ := coreio.Local.List(outDir) for _, e := range entries { - content, _ := coreio.Local.Read(filepath.Join(outDir, e.Name())) - if strings.Contains(content, "time.Time") && !strings.Contains(content, "\"time\"") { + content, _ := coreio.Local.Read(core.JoinPath(outDir, e.Name())) + if core.Contains(content, "time.Time") && !core.Contains(content, "\"time\"") { t.Errorf("file %s uses time.Time but doesn't import time", e.Name()) } } diff --git a/cmd/forgegen/helpers.go b/cmd/forgegen/helpers.go new file mode 100644 index 0000000..401e82a --- /dev/null +++ b/cmd/forgegen/helpers.go @@ -0,0 +1,41 @@ +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 +} diff --git a/cmd/forgegen/main.go b/cmd/forgegen/main.go index 856331d..469f626 100644 --- a/cmd/forgegen/main.go +++ b/cmd/forgegen/main.go @@ -2,8 +2,8 @@ package main import ( "flag" - "fmt" - "os" + + core "dappco.re/go/core" ) func main() { @@ -13,18 +13,16 @@ func main() { spec, err := LoadSpec(*specPath) if err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) + panic(core.E("forgegen.main", "load spec", err)) } types := ExtractTypes(spec) pairs := DetectCRUDPairs(spec) - fmt.Printf("Loaded %d types, %d CRUD pairs\n", len(types), len(pairs)) - fmt.Printf("Output dir: %s\n", *outDir) + core.Print(nil, "Loaded %d types, %d CRUD pairs", len(types), len(pairs)) + core.Print(nil, "Output dir: %s", *outDir) if err := Generate(types, pairs, *outDir); err != nil { - fmt.Fprintf(os.Stderr, "Error: %v\n", err) - os.Exit(1) + panic(core.E("forgegen.main", "generate types", err)) } } diff --git a/cmd/forgegen/parser.go b/cmd/forgegen/parser.go index f32bb8e..f42e77b 100644 --- a/cmd/forgegen/parser.go +++ b/cmd/forgegen/parser.go @@ -1,16 +1,20 @@ package main import ( - "encoding/json" - "fmt" + "cmp" + json "github.com/goccy/go-json" "slices" - "strings" + core "dappco.re/go/core" coreio "dappco.re/go/core/io" - coreerr "dappco.re/go/core/log" ) // Spec represents a Swagger 2.0 specification document. +// +// Usage: +// +// spec, err := LoadSpec("testdata/swagger.v1.json") +// _ = spec type Spec struct { Swagger string `json:"swagger"` Info SpecInfo `json:"info"` @@ -19,12 +23,20 @@ type Spec struct { } // SpecInfo holds metadata about the API specification. +// +// Usage: +// +// _ = SpecInfo{Title: "Forgejo API", Version: "1.0"} type SpecInfo struct { Title string `json:"title"` Version string `json:"version"` } // SchemaDefinition represents a single type definition in the swagger spec. +// +// Usage: +// +// _ = SchemaDefinition{Type: "object"} type SchemaDefinition struct { Description string `json:"description"` Type string `json:"type"` @@ -35,6 +47,10 @@ type SchemaDefinition struct { } // SchemaProperty represents a single property within a schema definition. +// +// Usage: +// +// _ = SchemaProperty{Type: "string"} type SchemaProperty struct { Type string `json:"type"` Format string `json:"format"` @@ -46,6 +62,10 @@ type SchemaProperty struct { } // GoType is the intermediate representation for a Go type to be generated. +// +// Usage: +// +// _ = GoType{Name: "Repository"} type GoType struct { Name string Description string @@ -55,6 +75,10 @@ type GoType struct { } // GoField is the intermediate representation for a single struct field. +// +// Usage: +// +// _ = GoField{GoName: "ID", GoType: "int64"} type GoField struct { GoName string GoType string @@ -64,6 +88,10 @@ type GoField struct { } // CRUDPair groups a base type with its corresponding Create and Edit option types. +// +// Usage: +// +// _ = CRUDPair{Base: "Repository", Create: "CreateRepoOption", Edit: "EditRepoOption"} type CRUDPair struct { Base string Create string @@ -71,19 +99,29 @@ type CRUDPair struct { } // LoadSpec reads and parses a Swagger 2.0 JSON file from the given path. +// +// Usage: +// +// spec, err := LoadSpec("testdata/swagger.v1.json") +// _ = spec func LoadSpec(path string) (*Spec, error) { content, err := coreio.Local.Read(path) if err != nil { - return nil, coreerr.E("LoadSpec", "read spec", err) + return nil, core.E("LoadSpec", "read spec", err) } var spec Spec if err := json.Unmarshal([]byte(content), &spec); err != nil { - return nil, coreerr.E("LoadSpec", "parse spec", err) + return nil, core.E("LoadSpec", "parse spec", err) } return &spec, nil } // ExtractTypes converts all swagger definitions into Go type intermediate representations. +// +// Usage: +// +// types := ExtractTypes(spec) +// _ = types["Repository"] func ExtractTypes(spec *Spec) map[string]*GoType { result := make(map[string]*GoType) for name, def := range spec.Definitions { @@ -91,7 +129,7 @@ func ExtractTypes(spec *Spec) map[string]*GoType { if len(def.Enum) > 0 { gt.IsEnum = true for _, v := range def.Enum { - gt.EnumValues = append(gt.EnumValues, fmt.Sprintf("%v", v)) + gt.EnumValues = append(gt.EnumValues, core.Sprint(v)) } slices.Sort(gt.EnumValues) result[name] = gt @@ -116,7 +154,7 @@ func ExtractTypes(spec *Spec) map[string]*GoType { gt.Fields = append(gt.Fields, gf) } slices.SortFunc(gt.Fields, func(a, b GoField) int { - return strings.Compare(a.GoName, b.GoName) + return cmp.Compare(a.GoName, b.GoName) }) result[name] = gt } @@ -125,15 +163,20 @@ func ExtractTypes(spec *Spec) map[string]*GoType { // DetectCRUDPairs finds Create*Option / Edit*Option pairs in the swagger definitions // and maps them back to the base type name. +// +// Usage: +// +// pairs := DetectCRUDPairs(spec) +// _ = pairs func DetectCRUDPairs(spec *Spec) []CRUDPair { var pairs []CRUDPair for name := range spec.Definitions { - if !strings.HasPrefix(name, "Create") || !strings.HasSuffix(name, "Option") { + if !core.HasPrefix(name, "Create") || !core.HasSuffix(name, "Option") { continue } - inner := strings.TrimPrefix(name, "Create") - inner = strings.TrimSuffix(inner, "Option") - editName := "Edit" + inner + "Option" + inner := core.TrimPrefix(name, "Create") + inner = core.TrimSuffix(inner, "Option") + editName := core.Concat("Edit", inner, "Option") pair := CRUDPair{Base: inner, Create: name} if _, ok := spec.Definitions[editName]; ok { pair.Edit = editName @@ -141,7 +184,7 @@ func DetectCRUDPairs(spec *Spec) []CRUDPair { pairs = append(pairs, pair) } slices.SortFunc(pairs, func(a, b CRUDPair) int { - return strings.Compare(a.Base, b.Base) + return cmp.Compare(a.Base, b.Base) }) return pairs } @@ -149,7 +192,7 @@ func DetectCRUDPairs(spec *Spec) []CRUDPair { // resolveGoType maps a swagger schema property to a Go type string. func resolveGoType(prop SchemaProperty) string { if prop.Ref != "" { - parts := strings.Split(prop.Ref, "/") + parts := core.Split(prop.Ref, "/") return "*" + parts[len(parts)-1] } switch prop.Type { @@ -196,19 +239,17 @@ func resolveGoType(prop SchemaProperty) string { // with common acronyms kept uppercase. func pascalCase(s string) string { var parts []string - for p := range strings.FieldsFuncSeq(s, func(r rune) bool { - return r == '_' || r == '-' - }) { + for _, p := range splitSnakeKebab(s) { if len(p) == 0 { continue } - upper := strings.ToUpper(p) + upper := core.Upper(p) switch upper { case "ID", "URL", "HTML", "SSH", "HTTP", "HTTPS", "API", "URI", "GPG", "IP", "CSS", "JS": parts = append(parts, upper) default: - parts = append(parts, strings.ToUpper(p[:1])+p[1:]) + parts = append(parts, core.Concat(core.Upper(p[:1]), p[1:])) } } - return strings.Join(parts, "") + return core.Concat(parts...) } diff --git a/cmd/forgegen/parser_test.go b/cmd/forgegen/parser_test.go index 2607268..b8808c6 100644 --- a/cmd/forgegen/parser_test.go +++ b/cmd/forgegen/parser_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestParser_Good_LoadSpec(t *testing.T) { +func TestParser_LoadSpec_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) @@ -17,7 +17,7 @@ func TestParser_Good_LoadSpec(t *testing.T) { } } -func TestParser_Good_ExtractTypes(t *testing.T) { +func TestParser_ExtractTypes_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) @@ -38,7 +38,7 @@ func TestParser_Good_ExtractTypes(t *testing.T) { } } -func TestParser_Good_FieldTypes(t *testing.T) { +func TestParser_FieldTypes_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) @@ -74,7 +74,7 @@ func TestParser_Good_FieldTypes(t *testing.T) { } } -func TestParser_Good_DetectCreateEditPairs(t *testing.T) { +func TestParser_DetectCreateEditPairs_Good(t *testing.T) { spec, err := LoadSpec("../../testdata/swagger.v1.json") if err != nil { t.Fatal(err) diff --git a/commits.go b/commits.go index 27b501e..8db96cc 100644 --- a/commits.go +++ b/commits.go @@ -2,7 +2,6 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" @@ -12,6 +11,11 @@ import ( // and git notes. // No Resource embedding — collection and item commit paths differ, and the // remaining endpoints are heterogeneous across status and note paths. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Commits.GetCombinedStatus(ctx, "core", "go-forge", "main") type CommitService struct { client *Client } @@ -51,7 +55,7 @@ func (s *CommitService) Get(ctx context.Context, params Params) (*types.Commit, // GetCombinedStatus returns the combined status for a given ref (branch, tag, or SHA). func (s *CommitService) GetCombinedStatus(ctx context.Context, owner, repo, ref string) (*types.CombinedStatus, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", owner, repo, ref) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/statuses/{ref}", pathParams("owner", owner, "repo", repo, "ref", ref)) var out types.CombinedStatus if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -61,7 +65,7 @@ func (s *CommitService) GetCombinedStatus(ctx context.Context, owner, repo, ref // ListStatuses returns all commit statuses for a given ref. func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref string) ([]types.CommitStatus, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/commits/%s/statuses", owner, repo, ref) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/commits/{ref}/statuses", pathParams("owner", owner, "repo", repo, "ref", ref)) var out []types.CommitStatus if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -71,7 +75,7 @@ func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref strin // CreateStatus creates a new commit status for the given SHA. func (s *CommitService) CreateStatus(ctx context.Context, owner, repo, sha string, opts *types.CreateStatusOption) (*types.CommitStatus, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/statuses/%s", owner, repo, sha) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/statuses/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha)) var out types.CommitStatus if err := s.client.Post(ctx, path, opts, &out); err != nil { return nil, err @@ -81,7 +85,7 @@ func (s *CommitService) CreateStatus(ctx context.Context, owner, repo, sha strin // GetNote returns the git note for a given commit SHA. func (s *CommitService) GetNote(ctx context.Context, owner, repo, sha string) (*types.Note, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/git/notes/%s", owner, repo, sha) + 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.Get(ctx, path, &out); err != nil { return nil, err diff --git a/commits_test.go b/commits_test.go index 82043f8..d75a20a 100644 --- a/commits_test.go +++ b/commits_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestCommitService_Good_List(t *testing.T) { +func TestCommitService_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) @@ -61,7 +61,7 @@ func TestCommitService_Good_List(t *testing.T) { } } -func TestCommitService_Good_Get(t *testing.T) { +func TestCommitService_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) @@ -95,7 +95,7 @@ func TestCommitService_Good_Get(t *testing.T) { } } -func TestCommitService_Good_ListStatuses(t *testing.T) { +func TestCommitService_ListStatuses_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) @@ -126,7 +126,7 @@ func TestCommitService_Good_ListStatuses(t *testing.T) { } } -func TestCommitService_Good_CreateStatus(t *testing.T) { +func TestCommitService_CreateStatus_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) @@ -169,7 +169,7 @@ func TestCommitService_Good_CreateStatus(t *testing.T) { } } -func TestCommitService_Good_GetNote(t *testing.T) { +func TestCommitService_GetNote_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) @@ -199,7 +199,7 @@ func TestCommitService_Good_GetNote(t *testing.T) { } } -func TestCommitService_Good_GetCombinedStatus(t *testing.T) { +func TestCommitService_GetCombinedStatus_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) @@ -234,7 +234,7 @@ func TestCommitService_Good_GetCombinedStatus(t *testing.T) { } } -func TestCommitService_Bad_NotFound(t *testing.T) { +func TestCommitService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "not found"}) diff --git a/config.go b/config.go index b4ad6fa..d1e442f 100644 --- a/config.go +++ b/config.go @@ -1,14 +1,18 @@ package forge import ( - "os" + "syscall" - coreerr "dappco.re/go/core/log" + core "dappco.re/go/core" ) const ( // DefaultURL is the fallback Forgejo instance URL when neither flag nor // environment variable is set. + // + // Usage: + // cfgURL, _, _ := forge.ResolveConfig("", "") + // _ = cfgURL == forge.DefaultURL DefaultURL = "http://localhost:3000" ) @@ -18,9 +22,15 @@ const ( // Environment variables: // - FORGE_URL — base URL of the Forgejo instance // - FORGE_TOKEN — API token for authentication +// +// Usage: +// +// url, token, err := forge.ResolveConfig("", "") +// _ = url +// _ = token func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { - url = os.Getenv("FORGE_URL") - token = os.Getenv("FORGE_TOKEN") + url, _ = syscall.Getenv("FORGE_URL") + token, _ = syscall.Getenv("FORGE_TOKEN") if flagURL != "" { url = flagURL @@ -36,13 +46,18 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { // NewForgeFromConfig creates a new Forge client using resolved configuration. // It returns an error if no API token is available from flags or environment. +// +// Usage: +// +// f, err := forge.NewForgeFromConfig("", "") +// _ = f func NewForgeFromConfig(flagURL, flagToken string, opts ...Option) (*Forge, error) { url, token, err := ResolveConfig(flagURL, flagToken) if err != nil { return nil, err } if token == "" { - return nil, coreerr.E("NewForgeFromConfig", "forge: no API token configured (set FORGE_TOKEN or pass --token)", nil) + return nil, core.E("NewForgeFromConfig", "forge: no API token configured (set FORGE_TOKEN or pass --token)", nil) } return NewForge(url, token, opts...), nil } diff --git a/config_test.go b/config_test.go index 2b7c58f..009789e 100644 --- a/config_test.go +++ b/config_test.go @@ -1,11 +1,10 @@ package forge import ( - "os" "testing" ) -func TestResolveConfig_Good_EnvOverrides(t *testing.T) { +func TestResolveConfig_EnvOverrides_Good(t *testing.T) { t.Setenv("FORGE_URL", "https://forge.example.com") t.Setenv("FORGE_TOKEN", "env-token") @@ -21,7 +20,7 @@ func TestResolveConfig_Good_EnvOverrides(t *testing.T) { } } -func TestResolveConfig_Good_FlagOverridesEnv(t *testing.T) { +func TestResolveConfig_FlagOverridesEnv_Good(t *testing.T) { t.Setenv("FORGE_URL", "https://env.example.com") t.Setenv("FORGE_TOKEN", "env-token") @@ -37,9 +36,9 @@ func TestResolveConfig_Good_FlagOverridesEnv(t *testing.T) { } } -func TestResolveConfig_Good_DefaultURL(t *testing.T) { - os.Unsetenv("FORGE_URL") - os.Unsetenv("FORGE_TOKEN") +func TestResolveConfig_DefaultURL_Good(t *testing.T) { + t.Setenv("FORGE_URL", "") + t.Setenv("FORGE_TOKEN", "") url, _, err := ResolveConfig("", "") if err != nil { @@ -50,9 +49,9 @@ func TestResolveConfig_Good_DefaultURL(t *testing.T) { } } -func TestNewForgeFromConfig_Bad_NoToken(t *testing.T) { - os.Unsetenv("FORGE_URL") - os.Unsetenv("FORGE_TOKEN") +func TestNewForgeFromConfig_NoToken_Bad(t *testing.T) { + t.Setenv("FORGE_URL", "") + t.Setenv("FORGE_TOKEN", "") _, err := NewForgeFromConfig("", "") if err == nil { diff --git a/contents.go b/contents.go index 8a6f48e..0fdbfc0 100644 --- a/contents.go +++ b/contents.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "dappco.re/go/core/forge/types" ) // ContentService handles file read/write operations via the Forgejo API. // No Resource embedding — paths vary by operation. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Contents.GetFile(ctx, "core", "go-forge", "README.md") type ContentService struct { client *Client } @@ -19,7 +23,7 @@ func newContentService(c *Client) *ContentService { // GetFile returns metadata and content for a file in a repository. func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath string) (*types.ContentsResponse, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath)) var out types.ContentsResponse if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -29,7 +33,7 @@ func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath stri // CreateFile creates a new file in a repository. func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath string, opts *types.CreateFileOptions) (*types.FileResponse, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath)) var out types.FileResponse if err := s.client.Post(ctx, path, opts, &out); err != nil { return nil, err @@ -39,7 +43,7 @@ func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath s // UpdateFile updates an existing file in a repository. func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath string, opts *types.UpdateFileOptions) (*types.FileResponse, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath)) var out types.FileResponse if err := s.client.Put(ctx, path, opts, &out); err != nil { return nil, err @@ -49,12 +53,12 @@ func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath s // DeleteFile deletes a file from a repository. Uses DELETE with a JSON body. func (s *ContentService) DeleteFile(ctx context.Context, owner, repo, filepath string, opts *types.DeleteFileOptions) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/contents/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath)) return s.client.DeleteWithBody(ctx, path, opts) } // GetRawFile returns the raw file content as bytes. func (s *ContentService) GetRawFile(ctx context.Context, owner, repo, filepath string) ([]byte, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/raw/%s", owner, repo, filepath) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/raw/{filepath}", pathParams("owner", owner, "repo", repo, "filepath", filepath)) return s.client.GetRaw(ctx, path) } diff --git a/contents_test.go b/contents_test.go index 1716276..7e03ca7 100644 --- a/contents_test.go +++ b/contents_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestContentService_Good_GetFile(t *testing.T) { +func TestContentService_GetFile_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) @@ -49,7 +49,7 @@ func TestContentService_Good_GetFile(t *testing.T) { } } -func TestContentService_Good_CreateFile(t *testing.T) { +func TestContentService_CreateFile_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) @@ -98,7 +98,7 @@ func TestContentService_Good_CreateFile(t *testing.T) { } } -func TestContentService_Good_UpdateFile(t *testing.T) { +func TestContentService_UpdateFile_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) @@ -138,7 +138,7 @@ func TestContentService_Good_UpdateFile(t *testing.T) { } } -func TestContentService_Good_DeleteFile(t *testing.T) { +func TestContentService_DeleteFile_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) @@ -168,7 +168,7 @@ func TestContentService_Good_DeleteFile(t *testing.T) { } } -func TestContentService_Good_GetRawFile(t *testing.T) { +func TestContentService_GetRawFile_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) @@ -192,7 +192,7 @@ func TestContentService_Good_GetRawFile(t *testing.T) { } } -func TestContentService_Bad_NotFound(t *testing.T) { +func TestContentService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "file not found"}) @@ -209,7 +209,7 @@ func TestContentService_Bad_NotFound(t *testing.T) { } } -func TestContentService_Bad_GetRawNotFound(t *testing.T) { +func TestContentService_GetRawNotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "file not found"}) diff --git a/forge.go b/forge.go index ecb5c17..becab42 100644 --- a/forge.go +++ b/forge.go @@ -1,6 +1,11 @@ package forge // Forge is the top-level client for the Forgejo API. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _ = f.Repos type Forge struct { client *Client @@ -26,6 +31,11 @@ type Forge struct { } // NewForge creates a new Forge client. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _ = f func NewForge(url, token string, opts ...Option) *Forge { c := NewClient(url, token, opts...) f := &Forge{client: c} diff --git a/forge_test.go b/forge_test.go index 747b37a..2ed0b2e 100644 --- a/forge_test.go +++ b/forge_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestForge_Good_NewForge(t *testing.T) { +func TestForge_NewForge_Good(t *testing.T) { f := NewForge("https://forge.lthn.ai", "tok") if f.Repos == nil { t.Fatal("Repos service is nil") @@ -20,7 +20,7 @@ func TestForge_Good_NewForge(t *testing.T) { } } -func TestForge_Good_Client(t *testing.T) { +func TestForge_Client_Good(t *testing.T) { f := NewForge("https://forge.lthn.ai", "tok") c := f.Client() if c == nil { @@ -31,7 +31,7 @@ func TestForge_Good_Client(t *testing.T) { } } -func TestRepoService_Good_ListOrgRepos(t *testing.T) { +func TestRepoService_ListOrgRepos_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) @@ -56,7 +56,7 @@ func TestRepoService_Good_ListOrgRepos(t *testing.T) { } } -func TestRepoService_Good_Get(t *testing.T) { +func TestRepoService_Get_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/repos/core/go-forge" { t.Errorf("wrong path: %s", r.URL.Path) @@ -77,7 +77,7 @@ func TestRepoService_Good_Get(t *testing.T) { } } -func TestRepoService_Good_Update(t *testing.T) { +func TestRepoService_Update_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) @@ -105,7 +105,7 @@ func TestRepoService_Good_Update(t *testing.T) { } } -func TestRepoService_Good_Delete(t *testing.T) { +func TestRepoService_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) @@ -125,7 +125,7 @@ func TestRepoService_Good_Delete(t *testing.T) { } } -func TestRepoService_Bad_Get(t *testing.T) { +func TestRepoService_Get_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "not found"}) @@ -138,7 +138,7 @@ func TestRepoService_Bad_Get(t *testing.T) { } } -func TestRepoService_Good_Fork(t *testing.T) { +func TestRepoService_Fork_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) diff --git a/go.mod b/go.mod index f973b82..0111cc8 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,9 @@ module dappco.re/go/core/forge go 1.26.0 require ( + dappco.re/go/core v0.4.7 dappco.re/go/core/io v0.2.0 - dappco.re/go/core/log v0.1.0 + github.com/goccy/go-json v0.10.6 ) require forge.lthn.ai/core/go-log v0.0.4 // indirect diff --git a/go.sum b/go.sum index 76d01ec..8169f53 100644 --- a/go.sum +++ b/go.sum @@ -1,11 +1,13 @@ +dappco.re/go/core v0.4.7 h1:KmIA/2lo6rl1NMtLrKqCWfMlUqpDZYH3q0/d10dTtGA= +dappco.re/go/core v0.4.7/go.mod h1:f2/tBZ3+3IqDrg2F5F598llv0nmb/4gJVCFzM5geE4A= dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4= dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E= -dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc= -dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs= forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= forge.lthn.ai/core/go-log v0.0.4/go.mod h1:r14MXKOD3LF/sI8XUJQhRk/SZHBE7jAFVuCfgkXoZPw= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= +github.com/goccy/go-json v0.10.6/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= diff --git a/helpers.go b/helpers.go new file mode 100644 index 0000000..936af77 --- /dev/null +++ b/helpers.go @@ -0,0 +1,35 @@ +package forge + +import ( + "strconv" + + 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 lastIndexByte(s string, b byte) int { + for i := len(s) - 1; i >= 0; i-- { + if s[i] == b { + return i + } + } + return -1 +} diff --git a/issues.go b/issues.go index a6ab01d..15306a4 100644 --- a/issues.go +++ b/issues.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" ) // IssueService handles issue operations within a repository. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Issues.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) type IssueService struct { Resource[types.Issue, types.CreateIssueOption, types.EditIssueOption] } @@ -23,77 +27,77 @@ func newIssueService(c *Client) *IssueService { // Pin pins an issue. func (s *IssueService) Pin(ctx context.Context, owner, repo string, index int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return s.client.Post(ctx, path, nil, nil) } // Unpin unpins an issue. func (s *IssueService) Unpin(ctx context.Context, owner, repo string, index int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/pin", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return s.client.Delete(ctx, path) } // SetDeadline sets or updates the deadline on an issue. func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, index int64, deadline string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/deadline", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/deadline", pathParams("owner", owner, "repo", repo, "index", int64String(index))) body := map[string]string{"due_date": deadline} return s.client.Post(ctx, path, body, nil) } // AddReaction adds a reaction to an issue. func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) body := map[string]string{"content": reaction} return s.client.Post(ctx, path, body, nil) } // DeleteReaction removes a reaction from an issue. func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, index int64, reaction string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) body := map[string]string{"content": reaction} return s.client.DeleteWithBody(ctx, path, body) } // StartStopwatch starts the stopwatch on an issue. func (s *IssueService) StartStopwatch(ctx context.Context, owner, repo string, index int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/start", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return s.client.Post(ctx, path, nil, nil) } // StopStopwatch stops the stopwatch on an issue. func (s *IssueService) StopStopwatch(ctx context.Context, owner, repo string, index int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/stopwatch/stop", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return s.client.Post(ctx, path, nil, nil) } // AddLabels adds labels to an issue. func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index int64, labelIDs []int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/labels", pathParams("owner", owner, "repo", repo, "index", int64String(index))) body := types.IssueLabelsOption{Labels: toAnySlice(labelIDs)} return s.client.Post(ctx, path, body, nil) } // RemoveLabel removes a single label from an issue. func (s *IssueService) RemoveLabel(ctx context.Context, owner, repo string, index int64, labelID int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, labelID) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/labels/{labelID}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "labelID", int64String(labelID))) return s.client.Delete(ctx, path) } // ListComments returns all comments on an issue. func (s *IssueService) ListComments(ctx context.Context, owner, repo string, index int64) ([]types.Comment, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return ListAll[types.Comment](ctx, s.client, path, nil) } // IterComments returns an iterator over all comments on an issue. func (s *IssueService) IterComments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Comment, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return ListIter[types.Comment](ctx, s.client, path, nil) } // CreateComment creates a comment on an issue. func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, index int64, body string) (*types.Comment, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/comments", pathParams("owner", owner, "repo", repo, "index", int64String(index))) opts := types.CreateIssueCommentOption{Body: body} var out types.Comment if err := s.client.Post(ctx, path, opts, &out); err != nil { diff --git a/issues_test.go b/issues_test.go index b9d7ed1..a00fff0 100644 --- a/issues_test.go +++ b/issues_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestIssueService_Good_List(t *testing.T) { +func TestIssueService_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) @@ -41,7 +41,7 @@ func TestIssueService_Good_List(t *testing.T) { } } -func TestIssueService_Good_Get(t *testing.T) { +func TestIssueService_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) @@ -63,7 +63,7 @@ func TestIssueService_Good_Get(t *testing.T) { } } -func TestIssueService_Good_Create(t *testing.T) { +func TestIssueService_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) @@ -93,7 +93,7 @@ func TestIssueService_Good_Create(t *testing.T) { } } -func TestIssueService_Good_Update(t *testing.T) { +func TestIssueService_Update_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) @@ -121,7 +121,7 @@ func TestIssueService_Good_Update(t *testing.T) { } } -func TestIssueService_Good_Delete(t *testing.T) { +func TestIssueService_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) @@ -141,7 +141,7 @@ func TestIssueService_Good_Delete(t *testing.T) { } } -func TestIssueService_Good_CreateComment(t *testing.T) { +func TestIssueService_CreateComment_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) @@ -168,7 +168,7 @@ func TestIssueService_Good_CreateComment(t *testing.T) { } } -func TestIssueService_Good_Pin(t *testing.T) { +func TestIssueService_Pin_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) @@ -187,7 +187,7 @@ func TestIssueService_Good_Pin(t *testing.T) { } } -func TestIssueService_Bad_List(t *testing.T) { +func TestIssueService_List_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"message": "boom"}) @@ -200,7 +200,7 @@ func TestIssueService_Bad_List(t *testing.T) { } } -func TestIssueService_Ugly_ListIgnoresIndexParam(t *testing.T) { +func TestIssueService_ListIgnoresIndexParam_Ugly(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/repos/core/go-forge/issues" { t.Errorf("wrong path: %s", r.URL.Path) diff --git a/labels.go b/labels.go index acb1146..341e7d3 100644 --- a/labels.go +++ b/labels.go @@ -2,7 +2,6 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" @@ -10,6 +9,11 @@ import ( // LabelService handles repository labels, organisation labels, and issue labels. // No Resource embedding — paths are heterogeneous. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Labels.ListRepoLabels(ctx, "core", "go-forge") type LabelService struct { client *Client } @@ -20,19 +24,19 @@ func newLabelService(c *Client) *LabelService { // ListRepoLabels returns all labels for a repository. func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) ([]types.Label, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels", pathParams("owner", owner, "repo", repo)) return ListAll[types.Label](ctx, s.client, path, nil) } // IterRepoLabels returns an iterator over all labels for a repository. func (s *LabelService) IterRepoLabels(ctx context.Context, owner, repo string) iter.Seq2[types.Label, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels", pathParams("owner", owner, "repo", repo)) return ListIter[types.Label](ctx, s.client, path, nil) } // GetRepoLabel returns a single label by ID. func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id int64) (*types.Label, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id))) var out types.Label if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -42,7 +46,7 @@ func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id // CreateRepoLabel creates a new label in a repository. func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string, opts *types.CreateLabelOption) (*types.Label, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels", pathParams("owner", owner, "repo", repo)) var out types.Label if err := s.client.Post(ctx, path, opts, &out); err != nil { return nil, err @@ -52,7 +56,7 @@ func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string, // EditRepoLabel updates an existing label in a repository. func (s *LabelService) EditRepoLabel(ctx context.Context, owner, repo string, id int64, opts *types.EditLabelOption) (*types.Label, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id))) var out types.Label if err := s.client.Patch(ctx, path, opts, &out); err != nil { return nil, err @@ -62,25 +66,25 @@ func (s *LabelService) EditRepoLabel(ctx context.Context, owner, repo string, id // DeleteRepoLabel deletes a label from a repository. func (s *LabelService) DeleteRepoLabel(ctx context.Context, owner, repo string, id int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id))) return s.client.Delete(ctx, path) } // ListOrgLabels returns all labels for an organisation. func (s *LabelService) ListOrgLabels(ctx context.Context, org string) ([]types.Label, error) { - path := fmt.Sprintf("/api/v1/orgs/%s/labels", org) + path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org)) return ListAll[types.Label](ctx, s.client, path, nil) } // IterOrgLabels returns an iterator over all labels for an organisation. func (s *LabelService) IterOrgLabels(ctx context.Context, org string) iter.Seq2[types.Label, error] { - path := fmt.Sprintf("/api/v1/orgs/%s/labels", org) + path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org)) return ListIter[types.Label](ctx, s.client, path, nil) } // CreateOrgLabel creates a new label in an organisation. func (s *LabelService) CreateOrgLabel(ctx context.Context, org string, opts *types.CreateLabelOption) (*types.Label, error) { - path := fmt.Sprintf("/api/v1/orgs/%s/labels", org) + path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org)) var out types.Label if err := s.client.Post(ctx, path, opts, &out); err != nil { return nil, err diff --git a/labels_test.go b/labels_test.go index c4af73d..92f8b87 100644 --- a/labels_test.go +++ b/labels_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestLabelService_Good_ListRepoLabels(t *testing.T) { +func TestLabelService_ListRepoLabels_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) @@ -42,7 +42,7 @@ func TestLabelService_Good_ListRepoLabels(t *testing.T) { } } -func TestLabelService_Good_CreateRepoLabel(t *testing.T) { +func TestLabelService_CreateRepoLabel_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) @@ -84,7 +84,7 @@ func TestLabelService_Good_CreateRepoLabel(t *testing.T) { } } -func TestLabelService_Good_GetRepoLabel(t *testing.T) { +func TestLabelService_GetRepoLabel_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) @@ -106,7 +106,7 @@ func TestLabelService_Good_GetRepoLabel(t *testing.T) { } } -func TestLabelService_Good_EditRepoLabel(t *testing.T) { +func TestLabelService_EditRepoLabel_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) @@ -135,7 +135,7 @@ func TestLabelService_Good_EditRepoLabel(t *testing.T) { } } -func TestLabelService_Good_DeleteRepoLabel(t *testing.T) { +func TestLabelService_DeleteRepoLabel_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) @@ -154,7 +154,7 @@ func TestLabelService_Good_DeleteRepoLabel(t *testing.T) { } } -func TestLabelService_Good_ListOrgLabels(t *testing.T) { +func TestLabelService_ListOrgLabels_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) @@ -182,7 +182,7 @@ func TestLabelService_Good_ListOrgLabels(t *testing.T) { } } -func TestLabelService_Good_CreateOrgLabel(t *testing.T) { +func TestLabelService_CreateOrgLabel_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) @@ -214,7 +214,7 @@ func TestLabelService_Good_CreateOrgLabel(t *testing.T) { } } -func TestLabelService_Bad_NotFound(t *testing.T) { +func TestLabelService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "label not found"}) diff --git a/milestones.go b/milestones.go index fa2dfd8..84c3365 100644 --- a/milestones.go +++ b/milestones.go @@ -2,12 +2,16 @@ package forge import ( "context" - "fmt" "dappco.re/go/core/forge/types" ) // MilestoneService handles repository milestones. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Milestones.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) type MilestoneService struct { client *Client } @@ -18,13 +22,13 @@ func newMilestoneService(c *Client) *MilestoneService { // ListAll returns all milestones for a repository. func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones", params["owner"], params["repo"]) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params) return ListAll[types.Milestone](ctx, s.client, path, nil) } // Get returns a single milestone by ID. func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d", owner, repo, id) + 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.Get(ctx, path, &out); err != nil { return nil, err @@ -34,7 +38,7 @@ func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64 // Create creates a new milestone. func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", pathParams("owner", owner, "repo", repo)) var out types.Milestone if err := s.client.Post(ctx, path, opts, &out); err != nil { return nil, err diff --git a/misc.go b/misc.go index b05526a..77243c2 100644 --- a/misc.go +++ b/misc.go @@ -2,7 +2,6 @@ package forge import ( "context" - "fmt" "dappco.re/go/core/forge/types" ) @@ -11,6 +10,11 @@ import ( // markdown rendering, licence templates, gitignore templates, and // server metadata. // No Resource embedding — heterogeneous read-only endpoints. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Misc.GetVersion(ctx) type MiscService struct { client *Client } @@ -41,7 +45,7 @@ func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplat // GetLicense returns a single licence template by name. func (s *MiscService) GetLicense(ctx context.Context, name string) (*types.LicenseTemplateInfo, error) { - path := fmt.Sprintf("/api/v1/licenses/%s", name) + path := ResolvePath("/api/v1/licenses/{name}", pathParams("name", name)) var out types.LicenseTemplateInfo if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -60,7 +64,7 @@ func (s *MiscService) ListGitignoreTemplates(ctx context.Context) ([]string, err // GetGitignoreTemplate returns a single gitignore template by name. func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*types.GitignoreTemplateInfo, error) { - path := fmt.Sprintf("/api/v1/gitignore/templates/%s", name) + path := ResolvePath("/api/v1/gitignore/templates/{name}", pathParams("name", name)) var out types.GitignoreTemplateInfo if err := s.client.Get(ctx, path, &out); err != nil { return nil, err diff --git a/misc_test.go b/misc_test.go index 25cf7f4..c0e845a 100644 --- a/misc_test.go +++ b/misc_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestMiscService_Good_RenderMarkdown(t *testing.T) { +func TestMiscService_RenderMarkdown_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) @@ -44,7 +44,7 @@ func TestMiscService_Good_RenderMarkdown(t *testing.T) { } } -func TestMiscService_Good_GetVersion(t *testing.T) { +func TestMiscService_GetVersion_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) @@ -68,7 +68,7 @@ func TestMiscService_Good_GetVersion(t *testing.T) { } } -func TestMiscService_Good_ListLicenses(t *testing.T) { +func TestMiscService_ListLicenses_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) @@ -99,7 +99,7 @@ func TestMiscService_Good_ListLicenses(t *testing.T) { } } -func TestMiscService_Good_GetLicense(t *testing.T) { +func TestMiscService_GetLicense_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) @@ -128,7 +128,7 @@ func TestMiscService_Good_GetLicense(t *testing.T) { } } -func TestMiscService_Good_ListGitignoreTemplates(t *testing.T) { +func TestMiscService_ListGitignoreTemplates_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) @@ -153,7 +153,7 @@ func TestMiscService_Good_ListGitignoreTemplates(t *testing.T) { } } -func TestMiscService_Good_GetGitignoreTemplate(t *testing.T) { +func TestMiscService_GetGitignoreTemplate_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) @@ -178,7 +178,7 @@ func TestMiscService_Good_GetGitignoreTemplate(t *testing.T) { } } -func TestMiscService_Good_GetNodeInfo(t *testing.T) { +func TestMiscService_GetNodeInfo_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) @@ -209,7 +209,7 @@ func TestMiscService_Good_GetNodeInfo(t *testing.T) { } } -func TestMiscService_Bad_NotFound(t *testing.T) { +func TestMiscService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "not found"}) diff --git a/notifications.go b/notifications.go index e3b8af2..d053bbc 100644 --- a/notifications.go +++ b/notifications.go @@ -2,7 +2,6 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" @@ -10,6 +9,11 @@ import ( // NotificationService handles notification operations via the Forgejo API. // No Resource embedding — varied endpoint shapes. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Notifications.List(ctx) type NotificationService struct { client *Client } @@ -30,13 +34,13 @@ func (s *NotificationService) Iter(ctx context.Context) iter.Seq2[types.Notifica // ListRepo returns all notifications for a specific repository. func (s *NotificationService) ListRepo(ctx context.Context, owner, repo string) ([]types.NotificationThread, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/notifications", pathParams("owner", owner, "repo", repo)) return ListAll[types.NotificationThread](ctx, s.client, path, nil) } // IterRepo returns an iterator over all notifications for a specific repository. func (s *NotificationService) IterRepo(ctx context.Context, owner, repo string) iter.Seq2[types.NotificationThread, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/notifications", pathParams("owner", owner, "repo", repo)) return ListIter[types.NotificationThread](ctx, s.client, path, nil) } @@ -47,7 +51,7 @@ func (s *NotificationService) MarkRead(ctx context.Context) error { // GetThread returns a single notification thread by ID. func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.NotificationThread, error) { - path := fmt.Sprintf("/api/v1/notifications/threads/%d", id) + path := ResolvePath("/api/v1/notifications/threads/{id}", pathParams("id", int64String(id))) var out types.NotificationThread if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -57,6 +61,6 @@ func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.N // MarkThreadRead marks a single notification thread as read. func (s *NotificationService) MarkThreadRead(ctx context.Context, id int64) error { - path := fmt.Sprintf("/api/v1/notifications/threads/%d", id) + path := ResolvePath("/api/v1/notifications/threads/{id}", pathParams("id", int64String(id))) return s.client.Patch(ctx, path, nil, nil) } diff --git a/notifications_test.go b/notifications_test.go index a497451..69f1fd2 100644 --- a/notifications_test.go +++ b/notifications_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestNotificationService_Good_List(t *testing.T) { +func TestNotificationService_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) @@ -45,7 +45,7 @@ func TestNotificationService_Good_List(t *testing.T) { } } -func TestNotificationService_Good_ListRepo(t *testing.T) { +func TestNotificationService_ListRepo_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) @@ -73,7 +73,7 @@ func TestNotificationService_Good_ListRepo(t *testing.T) { } } -func TestNotificationService_Good_GetThread(t *testing.T) { +func TestNotificationService_GetThread_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) @@ -107,7 +107,7 @@ func TestNotificationService_Good_GetThread(t *testing.T) { } } -func TestNotificationService_Good_MarkRead(t *testing.T) { +func TestNotificationService_MarkRead_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) @@ -126,7 +126,7 @@ func TestNotificationService_Good_MarkRead(t *testing.T) { } } -func TestNotificationService_Good_MarkThreadRead(t *testing.T) { +func TestNotificationService_MarkThreadRead_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) @@ -145,7 +145,7 @@ func TestNotificationService_Good_MarkThreadRead(t *testing.T) { } } -func TestNotificationService_Bad_NotFound(t *testing.T) { +func TestNotificationService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "thread not found"}) diff --git a/orgs.go b/orgs.go index 36d4dc2..8001e86 100644 --- a/orgs.go +++ b/orgs.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" ) // OrgService handles organisation operations. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Orgs.ListMembers(ctx, "core") type OrgService struct { Resource[types.Organization, types.CreateOrgOption, types.EditOrgOption] } @@ -23,37 +27,37 @@ func newOrgService(c *Client) *OrgService { // ListMembers returns all members of an organisation. func (s *OrgService) ListMembers(ctx context.Context, org string) ([]types.User, error) { - path := fmt.Sprintf("/api/v1/orgs/%s/members", org) + path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org)) return ListAll[types.User](ctx, s.client, path, nil) } // IterMembers returns an iterator over all members of an organisation. func (s *OrgService) IterMembers(ctx context.Context, org string) iter.Seq2[types.User, error] { - path := fmt.Sprintf("/api/v1/orgs/%s/members", org) + path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org)) return ListIter[types.User](ctx, s.client, path, nil) } // AddMember adds a user to an organisation. func (s *OrgService) AddMember(ctx context.Context, org, username string) error { - path := fmt.Sprintf("/api/v1/orgs/%s/members/%s", org, username) + path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username)) return s.client.Put(ctx, path, nil, nil) } // RemoveMember removes a user from an organisation. func (s *OrgService) RemoveMember(ctx context.Context, org, username string) error { - path := fmt.Sprintf("/api/v1/orgs/%s/members/%s", org, username) + path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username)) return s.client.Delete(ctx, path) } // ListUserOrgs returns all organisations for a user. func (s *OrgService) ListUserOrgs(ctx context.Context, username string) ([]types.Organization, error) { - path := fmt.Sprintf("/api/v1/users/%s/orgs", username) + path := ResolvePath("/api/v1/users/{username}/orgs", pathParams("username", username)) return ListAll[types.Organization](ctx, s.client, path, nil) } // IterUserOrgs returns an iterator over all organisations for a user. func (s *OrgService) IterUserOrgs(ctx context.Context, username string) iter.Seq2[types.Organization, error] { - path := fmt.Sprintf("/api/v1/users/%s/orgs", username) + path := ResolvePath("/api/v1/users/{username}/orgs", pathParams("username", username)) return ListIter[types.Organization](ctx, s.client, path, nil) } diff --git a/orgs_test.go b/orgs_test.go index bec5198..04dbdb3 100644 --- a/orgs_test.go +++ b/orgs_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestOrgService_Good_List(t *testing.T) { +func TestOrgService_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) @@ -36,7 +36,7 @@ func TestOrgService_Good_List(t *testing.T) { } } -func TestOrgService_Good_Get(t *testing.T) { +func TestOrgService_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) @@ -58,7 +58,7 @@ func TestOrgService_Good_Get(t *testing.T) { } } -func TestOrgService_Good_ListMembers(t *testing.T) { +func TestOrgService_ListMembers_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) diff --git a/packages.go b/packages.go index ee724ea..2553349 100644 --- a/packages.go +++ b/packages.go @@ -2,7 +2,6 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" @@ -10,6 +9,11 @@ import ( // PackageService handles package registry operations via the Forgejo API. // No Resource embedding — paths vary by operation. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Packages.List(ctx, "core") type PackageService struct { client *Client } @@ -20,19 +24,19 @@ func newPackageService(c *Client) *PackageService { // List returns all packages for a given owner. func (s *PackageService) List(ctx context.Context, owner string) ([]types.Package, error) { - path := fmt.Sprintf("/api/v1/packages/%s", owner) + path := ResolvePath("/api/v1/packages/{owner}", pathParams("owner", owner)) return ListAll[types.Package](ctx, s.client, path, nil) } // Iter returns an iterator over all packages for a given owner. func (s *PackageService) Iter(ctx context.Context, owner string) iter.Seq2[types.Package, error] { - path := fmt.Sprintf("/api/v1/packages/%s", owner) + path := ResolvePath("/api/v1/packages/{owner}", pathParams("owner", owner)) return ListIter[types.Package](ctx, s.client, path, nil) } // Get returns a single package by owner, type, name, and version. func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version string) (*types.Package, error) { - path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version) + path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) var out types.Package if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -42,18 +46,18 @@ func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version // Delete removes a package by owner, type, name, and version. func (s *PackageService) Delete(ctx context.Context, owner, pkgType, name, version string) error { - path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version) + path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) return s.client.Delete(ctx, path) } // ListFiles returns all files for a specific package version. func (s *PackageService) ListFiles(ctx context.Context, owner, pkgType, name, version string) ([]types.PackageFile, error) { - path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version) + path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}/files", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) return ListAll[types.PackageFile](ctx, s.client, path, nil) } // IterFiles returns an iterator over all files for a specific package version. func (s *PackageService) IterFiles(ctx context.Context, owner, pkgType, name, version string) iter.Seq2[types.PackageFile, error] { - path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version) + path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}/files", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) return ListIter[types.PackageFile](ctx, s.client, path, nil) } diff --git a/packages_test.go b/packages_test.go index a8cfc8d..a457e82 100644 --- a/packages_test.go +++ b/packages_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestPackageService_Good_List(t *testing.T) { +func TestPackageService_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) @@ -42,7 +42,7 @@ func TestPackageService_Good_List(t *testing.T) { } } -func TestPackageService_Good_Get(t *testing.T) { +func TestPackageService_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) @@ -75,7 +75,7 @@ func TestPackageService_Good_Get(t *testing.T) { } } -func TestPackageService_Good_Delete(t *testing.T) { +func TestPackageService_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) @@ -94,7 +94,7 @@ func TestPackageService_Good_Delete(t *testing.T) { } } -func TestPackageService_Good_ListFiles(t *testing.T) { +func TestPackageService_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) @@ -125,7 +125,7 @@ func TestPackageService_Good_ListFiles(t *testing.T) { } } -func TestPackageService_Bad_NotFound(t *testing.T) { +func TestPackageService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "package not found"}) diff --git a/pagination.go b/pagination.go index 26e8565..3c2c468 100644 --- a/pagination.go +++ b/pagination.go @@ -7,19 +7,34 @@ import ( "net/url" "strconv" - coreerr "dappco.re/go/core/log" + core "dappco.re/go/core" ) // ListOptions controls pagination. +// +// Usage: +// +// opts := forge.ListOptions{Page: 1, Limit: 50} +// _ = opts type ListOptions struct { Page int // 1-based page number Limit int // items per page (default 50) } // DefaultList returns sensible default pagination. +// +// Usage: +// +// page, err := forge.ListPage[types.Repository](ctx, client, path, nil, forge.DefaultList) +// _ = page var DefaultList = ListOptions{Page: 1, Limit: 50} // PagedResult holds a single page of results with metadata. +// +// Usage: +// +// page, err := forge.ListPage[types.Repository](ctx, client, path, nil, forge.DefaultList) +// _ = page type PagedResult[T any] struct { Items []T TotalCount int @@ -29,6 +44,11 @@ type PagedResult[T any] struct { // ListPage fetches a single page of results. // Extra query params can be passed via the query map. +// +// Usage: +// +// page, err := forge.ListPage[types.Repository](ctx, client, "/api/v1/user/repos", nil, forge.DefaultList) +// _ = page func ListPage[T any](ctx context.Context, c *Client, path string, query map[string]string, opts ListOptions) (*PagedResult[T], error) { if opts.Page < 1 { opts.Page = 1 @@ -39,7 +59,7 @@ func ListPage[T any](ctx context.Context, c *Client, path string, query map[stri u, err := url.Parse(path) if err != nil { - return nil, coreerr.E("ListPage", "forge: parse path", err) + return nil, core.E("ListPage", "forge: parse path", err) } q := u.Query() @@ -70,6 +90,11 @@ func ListPage[T any](ctx context.Context, c *Client, path string, query map[stri } // ListAll fetches all pages of results. +// +// Usage: +// +// items, err := forge.ListAll[types.Repository](ctx, client, "/api/v1/user/repos", nil) +// _ = items func ListAll[T any](ctx context.Context, c *Client, path string, query map[string]string) ([]T, error) { var all []T page := 1 @@ -90,6 +115,12 @@ func ListAll[T any](ctx context.Context, c *Client, path string, query map[strin } // ListIter returns an iterator over all resources across all pages. +// +// Usage: +// +// for item, err := range forge.ListIter[types.Repository](ctx, client, "/api/v1/user/repos", nil) { +// _, _ = item, err +// } func ListIter[T any](ctx context.Context, c *Client, path string, query map[string]string) iter.Seq2[T, error] { return func(yield func(T, error) bool) { page := 1 diff --git a/pagination_test.go b/pagination_test.go index 61e047e..89e650c 100644 --- a/pagination_test.go +++ b/pagination_test.go @@ -2,13 +2,13 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" ) -func TestPagination_Good_SinglePage(t *testing.T) { +func TestPagination_SinglePage_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Total-Count", "2") json.NewEncoder(w).Encode([]map[string]int{{"id": 1}, {"id": 2}}) @@ -25,7 +25,7 @@ func TestPagination_Good_SinglePage(t *testing.T) { } } -func TestPagination_Good_MultiPage(t *testing.T) { +func TestPagination_MultiPage_Good(t *testing.T) { page := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { page++ @@ -48,7 +48,7 @@ func TestPagination_Good_MultiPage(t *testing.T) { } } -func TestPagination_Good_EmptyResult(t *testing.T) { +func TestPagination_EmptyResult_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Total-Count", "0") json.NewEncoder(w).Encode([]map[string]int{}) @@ -65,7 +65,7 @@ func TestPagination_Good_EmptyResult(t *testing.T) { } } -func TestPagination_Good_Iter(t *testing.T) { +func TestPagination_Iter_Good(t *testing.T) { page := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { page++ @@ -95,7 +95,7 @@ func TestPagination_Good_Iter(t *testing.T) { } } -func TestListPage_Good_QueryParams(t *testing.T) { +func TestListPage_QueryParams_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { p := r.URL.Query().Get("page") l := r.URL.Query().Get("limit") @@ -116,7 +116,7 @@ func TestListPage_Good_QueryParams(t *testing.T) { } } -func TestPagination_Bad_ServerError(t *testing.T) { +func TestPagination_ServerError_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) json.NewEncoder(w).Encode(map[string]string{"message": "fail"}) diff --git a/params.go b/params.go index ce20aaf..9646e97 100644 --- a/params.go +++ b/params.go @@ -2,17 +2,28 @@ package forge import ( "net/url" - "strings" + + core "dappco.re/go/core" ) // Params maps path variable names to values. // Example: Params{"owner": "core", "repo": "go-forge"} +// +// Usage: +// +// params := forge.Params{"owner": "core", "repo": "go-forge"} +// _ = params type Params map[string]string // ResolvePath substitutes {placeholders} in path with values from params. +// +// Usage: +// +// path := forge.ResolvePath("/api/v1/repos/{owner}/{repo}", forge.Params{"owner": "core", "repo": "go-forge"}) +// _ = path func ResolvePath(path string, params Params) string { for k, v := range params { - path = strings.ReplaceAll(path, "{"+k+"}", url.PathEscape(v)) + path = core.Replace(path, "{"+k+"}", url.PathEscape(v)) } return path } diff --git a/params_test.go b/params_test.go index b82d2a3..7d20f33 100644 --- a/params_test.go +++ b/params_test.go @@ -2,7 +2,7 @@ package forge import "testing" -func TestResolvePath_Good_Simple(t *testing.T) { +func TestResolvePath_Simple_Good(t *testing.T) { got := ResolvePath("/api/v1/repos/{owner}/{repo}", Params{"owner": "core", "repo": "go-forge"}) want := "/api/v1/repos/core/go-forge" if got != want { @@ -10,14 +10,14 @@ func TestResolvePath_Good_Simple(t *testing.T) { } } -func TestResolvePath_Good_NoParams(t *testing.T) { +func TestResolvePath_NoParams_Good(t *testing.T) { got := ResolvePath("/api/v1/user", nil) if got != "/api/v1/user" { t.Errorf("got %q", got) } } -func TestResolvePath_Good_WithID(t *testing.T) { +func TestResolvePath_WithID_Good(t *testing.T) { got := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}", Params{ "owner": "core", "repo": "go-forge", "index": "42", }) @@ -27,7 +27,7 @@ func TestResolvePath_Good_WithID(t *testing.T) { } } -func TestResolvePath_Good_URLEncoding(t *testing.T) { +func TestResolvePath_URLEncoding_Good(t *testing.T) { got := ResolvePath("/api/v1/repos/{owner}/{repo}", Params{"owner": "my org", "repo": "my repo"}) want := "/api/v1/repos/my%20org/my%20repo" if got != want { diff --git a/pulls.go b/pulls.go index 408f438..e9d52ee 100644 --- a/pulls.go +++ b/pulls.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" ) // PullService handles pull request operations within a repository. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Pulls.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) type PullService struct { Resource[types.PullRequest, types.CreatePullRequestOption, types.EditPullRequestOption] } @@ -23,32 +27,32 @@ func newPullService(c *Client) *PullService { // Merge merges a pull request. Method is one of "merge", "rebase", "rebase-merge", "squash", "fast-forward-only", "manually-merged". func (s *PullService) Merge(ctx context.Context, owner, repo string, index int64, method string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/merge", pathParams("owner", owner, "repo", repo, "index", int64String(index))) body := map[string]string{"Do": method} return s.client.Post(ctx, path, body, nil) } // Update updates a pull request branch with the base branch. func (s *PullService) Update(ctx context.Context, owner, repo string, index int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/update", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/update", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return s.client.Post(ctx, path, nil, nil) } // ListReviews returns all reviews on a pull request. func (s *PullService) ListReviews(ctx context.Context, owner, repo string, index int64) ([]types.PullReview, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return ListAll[types.PullReview](ctx, s.client, path, nil) } // IterReviews returns an iterator over all reviews on a pull request. func (s *PullService) IterReviews(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.PullReview, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index))) return ListIter[types.PullReview](ctx, s.client, path, nil) } // SubmitReview creates a new review on a pull request. func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, index int64, review map[string]any) (*types.PullReview, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews", pathParams("owner", owner, "repo", repo, "index", int64String(index))) var out types.PullReview if err := s.client.Post(ctx, path, review, &out); err != nil { return nil, err @@ -58,13 +62,13 @@ func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, inde // DismissReview dismisses a pull request review. func (s *PullService) DismissReview(ctx context.Context, owner, repo string, index, reviewID int64, msg string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, reviewID) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{reviewID}/dismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "reviewID", int64String(reviewID))) body := map[string]string{"message": msg} return s.client.Post(ctx, path, body, nil) } // UndismissReview undismisses a pull request review. func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, reviewID) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{reviewID}/undismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "reviewID", int64String(reviewID))) return s.client.Post(ctx, path, nil, nil) } diff --git a/pulls_test.go b/pulls_test.go index b88ba22..6249eb0 100644 --- a/pulls_test.go +++ b/pulls_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestPullService_Good_List(t *testing.T) { +func TestPullService_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) @@ -41,7 +41,7 @@ func TestPullService_Good_List(t *testing.T) { } } -func TestPullService_Good_Get(t *testing.T) { +func TestPullService_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) @@ -63,7 +63,7 @@ func TestPullService_Good_Get(t *testing.T) { } } -func TestPullService_Good_Create(t *testing.T) { +func TestPullService_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) @@ -94,7 +94,7 @@ func TestPullService_Good_Create(t *testing.T) { } } -func TestPullService_Good_Merge(t *testing.T) { +func TestPullService_Merge_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) @@ -118,7 +118,7 @@ func TestPullService_Good_Merge(t *testing.T) { } } -func TestPullService_Bad_Merge(t *testing.T) { +func TestPullService_Merge_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusConflict) json.NewEncoder(w).Encode(map[string]string{"message": "already merged"}) diff --git a/releases.go b/releases.go index ae32d49..ed3a841 100644 --- a/releases.go +++ b/releases.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" ) // ReleaseService handles release operations within a repository. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Releases.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) type ReleaseService struct { Resource[types.Release, types.CreateReleaseOption, types.EditReleaseOption] } @@ -23,7 +27,7 @@ func newReleaseService(c *Client) *ReleaseService { // GetByTag returns a release by its tag name. func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) (*types.Release, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner, repo, tag) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/tags/{tag}", pathParams("owner", owner, "repo", repo, "tag", tag)) var out types.Release if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -33,25 +37,25 @@ func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) // DeleteByTag deletes a release by its tag name. func (s *ReleaseService) DeleteByTag(ctx context.Context, owner, repo, tag string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner, repo, tag) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/tags/{tag}", pathParams("owner", owner, "repo", repo, "tag", tag)) return s.client.Delete(ctx, path) } // ListAssets returns all assets for a release. func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, releaseID int64) ([]types.Attachment, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner, repo, releaseID) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID))) return ListAll[types.Attachment](ctx, s.client, path, nil) } // IterAssets returns an iterator over all assets for a release. func (s *ReleaseService) IterAssets(ctx context.Context, owner, repo string, releaseID int64) iter.Seq2[types.Attachment, error] { - path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner, repo, releaseID) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID))) return ListIter[types.Attachment](ctx, s.client, path, nil) } // GetAsset returns a single asset for a release. func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) (*types.Attachment, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets/{assetID}", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID), "assetID", int64String(assetID))) var out types.Attachment if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -61,6 +65,6 @@ func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, relea // DeleteAsset deletes a single asset from a release. func (s *ReleaseService) DeleteAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets/{assetID}", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID), "assetID", int64String(assetID))) return s.client.Delete(ctx, path) } diff --git a/releases_test.go b/releases_test.go index 1520fd2..c0b22c3 100644 --- a/releases_test.go +++ b/releases_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestReleaseService_Good_List(t *testing.T) { +func TestReleaseService_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) @@ -36,7 +36,7 @@ func TestReleaseService_Good_List(t *testing.T) { } } -func TestReleaseService_Good_Get(t *testing.T) { +func TestReleaseService_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) @@ -61,7 +61,7 @@ func TestReleaseService_Good_Get(t *testing.T) { } } -func TestReleaseService_Good_GetByTag(t *testing.T) { +func TestReleaseService_GetByTag_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) diff --git a/repos.go b/repos.go index 13a9f0b..1f31917 100644 --- a/repos.go +++ b/repos.go @@ -8,6 +8,11 @@ import ( ) // RepoService handles repository operations. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Repos.ListOrgRepos(ctx, "core") type RepoService struct { Resource[types.Repository, types.CreateRepoOption, types.EditRepoOption] } diff --git a/resource.go b/resource.go index 3ee5d45..f6fb521 100644 --- a/resource.go +++ b/resource.go @@ -3,11 +3,17 @@ package forge import ( "context" "iter" - "strings" + + core "dappco.re/go/core" ) // Resource provides generic CRUD operations for a Forgejo API resource. // T is the resource type, C is the create options type, U is the update options type. +// +// Usage: +// +// r := forge.NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](client, "/api/v1/repos/{owner}/{repo}/issues/{index}") +// _ = r type Resource[T any, C any, U any] struct { client *Client path string // item path: /api/v1/repos/{owner}/{repo}/issues/{index} @@ -17,13 +23,18 @@ type Resource[T any, C any, U any] struct { // NewResource creates a new Resource for the given path pattern. // The path should be the item path (e.g., /repos/{owner}/{repo}/issues/{index}). // The collection path is derived by stripping the last /{placeholder} segment. +// +// Usage: +// +// r := forge.NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](client, "/api/v1/repos/{owner}/{repo}/issues/{index}") +// _ = r func NewResource[T any, C any, U any](c *Client, path string) *Resource[T, C, U] { collection := path // Strip last segment if it's a pure placeholder like /{index} // Don't strip if mixed like /repos or /{org}/repos - if i := strings.LastIndex(path, "/"); i >= 0 { + if i := lastIndexByte(path, '/'); i >= 0 { lastSeg := path[i+1:] - if strings.HasPrefix(lastSeg, "{") && strings.HasSuffix(lastSeg, "}") { + if core.HasPrefix(lastSeg, "{") && core.HasSuffix(lastSeg, "}") { collection = path[:i] } } diff --git a/resource_test.go b/resource_test.go index 6d0d560..8f81d25 100644 --- a/resource_test.go +++ b/resource_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -22,7 +22,7 @@ type testUpdate struct { Name *string `json:"name,omitempty"` } -func TestResource_Good_List(t *testing.T) { +func TestResource_List_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/orgs/core/repos" { t.Errorf("wrong path: %s", r.URL.Path) @@ -44,7 +44,7 @@ func TestResource_Good_List(t *testing.T) { } } -func TestResource_Good_Get(t *testing.T) { +func TestResource_Get_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/repos/core/go-forge" { t.Errorf("wrong path: %s", r.URL.Path) @@ -65,7 +65,7 @@ func TestResource_Good_Get(t *testing.T) { } } -func TestResource_Good_Create(t *testing.T) { +func TestResource_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) @@ -94,7 +94,7 @@ func TestResource_Good_Create(t *testing.T) { } } -func TestResource_Good_Update(t *testing.T) { +func TestResource_Update_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) @@ -116,7 +116,7 @@ func TestResource_Good_Update(t *testing.T) { } } -func TestResource_Good_Delete(t *testing.T) { +func TestResource_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) @@ -134,7 +134,7 @@ func TestResource_Good_Delete(t *testing.T) { } } -func TestResource_Good_ListAll(t *testing.T) { +func TestResource_ListAll_Good(t *testing.T) { page := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { page++ @@ -159,7 +159,7 @@ func TestResource_Good_ListAll(t *testing.T) { } } -func TestResource_Good_Iter(t *testing.T) { +func TestResource_Iter_Good(t *testing.T) { page := 0 srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { page++ @@ -190,7 +190,7 @@ func TestResource_Good_Iter(t *testing.T) { } } -func TestResource_Bad_IterError(t *testing.T) { +func TestResource_IterError_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusInternalServerError) json.NewEncoder(w).Encode(map[string]string{"message": "server error"}) @@ -212,7 +212,7 @@ func TestResource_Bad_IterError(t *testing.T) { } } -func TestResource_Good_IterBreakEarly(t *testing.T) { +func TestResource_IterBreakEarly_Good(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("X-Total-Count", "100") json.NewEncoder(w).Encode([]testItem{{1, "a"}, {2, "b"}, {3, "c"}}) diff --git a/teams.go b/teams.go index 32470ec..862584b 100644 --- a/teams.go +++ b/teams.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" ) // TeamService handles team operations. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Teams.ListMembers(ctx, 42) type TeamService struct { Resource[types.Team, types.CreateTeamOption, types.EditTeamOption] } @@ -23,60 +27,60 @@ func newTeamService(c *Client) *TeamService { // ListMembers returns all members of a team. func (s *TeamService) ListMembers(ctx context.Context, teamID int64) ([]types.User, error) { - path := fmt.Sprintf("/api/v1/teams/%d/members", teamID) + path := ResolvePath("/api/v1/teams/{teamID}/members", pathParams("teamID", int64String(teamID))) return ListAll[types.User](ctx, s.client, path, nil) } // IterMembers returns an iterator over all members of a team. func (s *TeamService) IterMembers(ctx context.Context, teamID int64) iter.Seq2[types.User, error] { - path := fmt.Sprintf("/api/v1/teams/%d/members", teamID) + path := ResolvePath("/api/v1/teams/{teamID}/members", pathParams("teamID", int64String(teamID))) return ListIter[types.User](ctx, s.client, path, nil) } // AddMember adds a user to a team. func (s *TeamService) AddMember(ctx context.Context, teamID int64, username string) error { - path := fmt.Sprintf("/api/v1/teams/%d/members/%s", teamID, username) + path := ResolvePath("/api/v1/teams/{teamID}/members/{username}", pathParams("teamID", int64String(teamID), "username", username)) return s.client.Put(ctx, path, nil, nil) } // RemoveMember removes a user from a team. func (s *TeamService) RemoveMember(ctx context.Context, teamID int64, username string) error { - path := fmt.Sprintf("/api/v1/teams/%d/members/%s", teamID, username) + path := ResolvePath("/api/v1/teams/{teamID}/members/{username}", pathParams("teamID", int64String(teamID), "username", username)) return s.client.Delete(ctx, path) } // ListRepos returns all repositories managed by a team. func (s *TeamService) ListRepos(ctx context.Context, teamID int64) ([]types.Repository, error) { - path := fmt.Sprintf("/api/v1/teams/%d/repos", teamID) + path := ResolvePath("/api/v1/teams/{teamID}/repos", pathParams("teamID", int64String(teamID))) return ListAll[types.Repository](ctx, s.client, path, nil) } // IterRepos returns an iterator over all repositories managed by a team. func (s *TeamService) IterRepos(ctx context.Context, teamID int64) iter.Seq2[types.Repository, error] { - path := fmt.Sprintf("/api/v1/teams/%d/repos", teamID) + path := ResolvePath("/api/v1/teams/{teamID}/repos", pathParams("teamID", int64String(teamID))) return ListIter[types.Repository](ctx, s.client, path, nil) } // AddRepo adds a repository to a team. func (s *TeamService) AddRepo(ctx context.Context, teamID int64, org, repo string) error { - path := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, repo) + path := ResolvePath("/api/v1/teams/{teamID}/repos/{org}/{repo}", pathParams("teamID", int64String(teamID), "org", org, "repo", repo)) return s.client.Put(ctx, path, nil, nil) } // RemoveRepo removes a repository from a team. func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error { - path := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, repo) + path := ResolvePath("/api/v1/teams/{teamID}/repos/{org}/{repo}", pathParams("teamID", int64String(teamID), "org", org, "repo", repo)) return s.client.Delete(ctx, path) } // ListOrgTeams returns all teams in an organisation. func (s *TeamService) ListOrgTeams(ctx context.Context, org string) ([]types.Team, error) { - path := fmt.Sprintf("/api/v1/orgs/%s/teams", org) + path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org)) return ListAll[types.Team](ctx, s.client, path, nil) } // IterOrgTeams returns an iterator over all teams in an organisation. func (s *TeamService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[types.Team, error] { - path := fmt.Sprintf("/api/v1/orgs/%s/teams", org) + path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org)) return ListIter[types.Team](ctx, s.client, path, nil) } diff --git a/teams_test.go b/teams_test.go index dc2d9f0..4844b3f 100644 --- a/teams_test.go +++ b/teams_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestTeamService_Good_Get(t *testing.T) { +func TestTeamService_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) @@ -32,7 +32,7 @@ func TestTeamService_Good_Get(t *testing.T) { } } -func TestTeamService_Good_ListMembers(t *testing.T) { +func TestTeamService_ListMembers_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) @@ -61,7 +61,7 @@ func TestTeamService_Good_ListMembers(t *testing.T) { } } -func TestTeamService_Good_AddMember(t *testing.T) { +func TestTeamService_AddMember_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) diff --git a/users.go b/users.go index 2aba489..0918d04 100644 --- a/users.go +++ b/users.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" ) // UserService handles user operations. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Users.GetCurrent(ctx) type UserService struct { Resource[types.User, struct{}, struct{}] } @@ -32,60 +36,60 @@ func (s *UserService) GetCurrent(ctx context.Context) (*types.User, error) { // ListFollowers returns all followers of a user. func (s *UserService) ListFollowers(ctx context.Context, username string) ([]types.User, error) { - path := fmt.Sprintf("/api/v1/users/%s/followers", username) + path := ResolvePath("/api/v1/users/{username}/followers", pathParams("username", username)) return ListAll[types.User](ctx, s.client, path, nil) } // IterFollowers returns an iterator over all followers of a user. func (s *UserService) IterFollowers(ctx context.Context, username string) iter.Seq2[types.User, error] { - path := fmt.Sprintf("/api/v1/users/%s/followers", username) + path := ResolvePath("/api/v1/users/{username}/followers", pathParams("username", username)) return ListIter[types.User](ctx, s.client, path, nil) } // ListFollowing returns all users that a user is following. func (s *UserService) ListFollowing(ctx context.Context, username string) ([]types.User, error) { - path := fmt.Sprintf("/api/v1/users/%s/following", username) + path := ResolvePath("/api/v1/users/{username}/following", pathParams("username", username)) return ListAll[types.User](ctx, s.client, path, nil) } // IterFollowing returns an iterator over all users that a user is following. func (s *UserService) IterFollowing(ctx context.Context, username string) iter.Seq2[types.User, error] { - path := fmt.Sprintf("/api/v1/users/%s/following", username) + path := ResolvePath("/api/v1/users/{username}/following", pathParams("username", username)) return ListIter[types.User](ctx, s.client, path, nil) } // Follow follows a user as the authenticated user. func (s *UserService) Follow(ctx context.Context, username string) error { - path := fmt.Sprintf("/api/v1/user/following/%s", username) + path := ResolvePath("/api/v1/user/following/{username}", pathParams("username", username)) return s.client.Put(ctx, path, nil, nil) } // Unfollow unfollows a user as the authenticated user. func (s *UserService) Unfollow(ctx context.Context, username string) error { - path := fmt.Sprintf("/api/v1/user/following/%s", username) + path := ResolvePath("/api/v1/user/following/{username}", pathParams("username", username)) return s.client.Delete(ctx, path) } // ListStarred returns all repositories starred by a user. func (s *UserService) ListStarred(ctx context.Context, username string) ([]types.Repository, error) { - path := fmt.Sprintf("/api/v1/users/%s/starred", username) + path := ResolvePath("/api/v1/users/{username}/starred", pathParams("username", username)) return ListAll[types.Repository](ctx, s.client, path, nil) } // IterStarred returns an iterator over all repositories starred by a user. func (s *UserService) IterStarred(ctx context.Context, username string) iter.Seq2[types.Repository, error] { - path := fmt.Sprintf("/api/v1/users/%s/starred", username) + path := ResolvePath("/api/v1/users/{username}/starred", pathParams("username", username)) return ListIter[types.Repository](ctx, s.client, path, nil) } // Star stars a repository as the authenticated user. func (s *UserService) Star(ctx context.Context, owner, repo string) error { - path := fmt.Sprintf("/api/v1/user/starred/%s/%s", owner, repo) + path := ResolvePath("/api/v1/user/starred/{owner}/{repo}", pathParams("owner", owner, "repo", repo)) return s.client.Put(ctx, path, nil, nil) } // Unstar unstars a repository as the authenticated user. func (s *UserService) Unstar(ctx context.Context, owner, repo string) error { - path := fmt.Sprintf("/api/v1/user/starred/%s/%s", owner, repo) + path := ResolvePath("/api/v1/user/starred/{owner}/{repo}", pathParams("owner", owner, "repo", repo)) return s.client.Delete(ctx, path) } diff --git a/users_test.go b/users_test.go index 015f7f8..bfda464 100644 --- a/users_test.go +++ b/users_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestUserService_Good_Get(t *testing.T) { +func TestUserService_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) @@ -32,7 +32,7 @@ func TestUserService_Good_Get(t *testing.T) { } } -func TestUserService_Good_GetCurrent(t *testing.T) { +func TestUserService_GetCurrent_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) @@ -54,7 +54,7 @@ func TestUserService_Good_GetCurrent(t *testing.T) { } } -func TestUserService_Good_ListFollowers(t *testing.T) { +func TestUserService_ListFollowers_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) diff --git a/webhooks.go b/webhooks.go index b814b28..1cc8894 100644 --- a/webhooks.go +++ b/webhooks.go @@ -2,7 +2,6 @@ package forge import ( "context" - "fmt" "iter" "dappco.re/go/core/forge/types" @@ -10,6 +9,11 @@ import ( // WebhookService handles webhook (hook) operations within a repository. // Embeds Resource for standard CRUD on /api/v1/repos/{owner}/{repo}/hooks/{id}. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Webhooks.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) type WebhookService struct { Resource[types.Hook, types.CreateHookOption, types.EditHookOption] } @@ -24,18 +28,18 @@ func newWebhookService(c *Client) *WebhookService { // TestHook triggers a test delivery for a webhook. func (s *WebhookService) TestHook(ctx context.Context, owner, repo string, id int64) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/hooks/%d/tests", owner, repo, id) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks/{id}/tests", pathParams("owner", owner, "repo", repo, "id", int64String(id))) return s.client.Post(ctx, path, nil, nil) } // ListOrgHooks returns all webhooks for an organisation. func (s *WebhookService) ListOrgHooks(ctx context.Context, org string) ([]types.Hook, error) { - path := fmt.Sprintf("/api/v1/orgs/%s/hooks", org) + path := ResolvePath("/api/v1/orgs/{org}/hooks", pathParams("org", org)) return ListAll[types.Hook](ctx, s.client, path, nil) } // IterOrgHooks returns an iterator over all webhooks for an organisation. func (s *WebhookService) IterOrgHooks(ctx context.Context, org string) iter.Seq2[types.Hook, error] { - path := fmt.Sprintf("/api/v1/orgs/%s/hooks", org) + path := ResolvePath("/api/v1/orgs/{org}/hooks", pathParams("org", org)) return ListIter[types.Hook](ctx, s.client, path, nil) } diff --git a/webhooks_test.go b/webhooks_test.go index 4098061..b6a973e 100644 --- a/webhooks_test.go +++ b/webhooks_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestWebhookService_Good_List(t *testing.T) { +func TestWebhookService_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) @@ -36,7 +36,7 @@ func TestWebhookService_Good_List(t *testing.T) { } } -func TestWebhookService_Good_Get(t *testing.T) { +func TestWebhookService_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) @@ -70,7 +70,7 @@ func TestWebhookService_Good_Get(t *testing.T) { } } -func TestWebhookService_Good_Create(t *testing.T) { +func TestWebhookService_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) @@ -108,7 +108,7 @@ func TestWebhookService_Good_Create(t *testing.T) { } } -func TestWebhookService_Good_TestHook(t *testing.T) { +func TestWebhookService_TestHook_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) @@ -127,7 +127,7 @@ func TestWebhookService_Good_TestHook(t *testing.T) { } } -func TestWebhookService_Good_ListOrgHooks(t *testing.T) { +func TestWebhookService_ListOrgHooks_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) @@ -155,7 +155,7 @@ func TestWebhookService_Good_ListOrgHooks(t *testing.T) { } } -func TestWebhookService_Bad_NotFound(t *testing.T) { +func TestWebhookService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "hook not found"}) diff --git a/wiki.go b/wiki.go index 898c5ca..5a5e039 100644 --- a/wiki.go +++ b/wiki.go @@ -2,13 +2,17 @@ package forge import ( "context" - "fmt" "dappco.re/go/core/forge/types" ) // WikiService handles wiki page operations for a repository. // No Resource embedding — custom endpoints for wiki CRUD. +// +// Usage: +// +// f := forge.NewForge("https://forge.lthn.ai", "token") +// _, err := f.Wiki.ListPages(ctx, "core", "go-forge") type WikiService struct { client *Client } @@ -19,7 +23,7 @@ func newWikiService(c *Client) *WikiService { // ListPages returns all wiki page metadata for a repository. func (s *WikiService) ListPages(ctx context.Context, owner, repo string) ([]types.WikiPageMetaData, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/pages", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/pages", pathParams("owner", owner, "repo", repo)) var out []types.WikiPageMetaData if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -29,7 +33,7 @@ func (s *WikiService) ListPages(ctx context.Context, owner, repo string) ([]type // GetPage returns a single wiki page by name. func (s *WikiService) GetPage(ctx context.Context, owner, repo, pageName string) (*types.WikiPage, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", owner, repo, pageName) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/page/{pageName}", pathParams("owner", owner, "repo", repo, "pageName", pageName)) var out types.WikiPage if err := s.client.Get(ctx, path, &out); err != nil { return nil, err @@ -39,7 +43,7 @@ func (s *WikiService) GetPage(ctx context.Context, owner, repo, pageName string) // CreatePage creates a new wiki page. func (s *WikiService) CreatePage(ctx context.Context, owner, repo string, opts *types.CreateWikiPageOptions) (*types.WikiPage, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/new", owner, repo) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/new", pathParams("owner", owner, "repo", repo)) var out types.WikiPage if err := s.client.Post(ctx, path, opts, &out); err != nil { return nil, err @@ -49,7 +53,7 @@ func (s *WikiService) CreatePage(ctx context.Context, owner, repo string, opts * // EditPage updates an existing wiki page. func (s *WikiService) EditPage(ctx context.Context, owner, repo, pageName string, opts *types.CreateWikiPageOptions) (*types.WikiPage, error) { - path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", owner, repo, pageName) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/page/{pageName}", pathParams("owner", owner, "repo", repo, "pageName", pageName)) var out types.WikiPage if err := s.client.Patch(ctx, path, opts, &out); err != nil { return nil, err @@ -59,6 +63,6 @@ func (s *WikiService) EditPage(ctx context.Context, owner, repo, pageName string // DeletePage removes a wiki page. func (s *WikiService) DeletePage(ctx context.Context, owner, repo, pageName string) error { - path := fmt.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", owner, repo, pageName) + path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/page/{pageName}", pathParams("owner", owner, "repo", repo, "pageName", pageName)) return s.client.Delete(ctx, path) } diff --git a/wiki_test.go b/wiki_test.go index 7889f78..2a35728 100644 --- a/wiki_test.go +++ b/wiki_test.go @@ -2,7 +2,7 @@ package forge import ( "context" - "encoding/json" + json "github.com/goccy/go-json" "net/http" "net/http/httptest" "testing" @@ -10,7 +10,7 @@ import ( "dappco.re/go/core/forge/types" ) -func TestWikiService_Good_ListPages(t *testing.T) { +func TestWikiService_ListPages_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) @@ -41,7 +41,7 @@ func TestWikiService_Good_ListPages(t *testing.T) { } } -func TestWikiService_Good_GetPage(t *testing.T) { +func TestWikiService_GetPage_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) @@ -74,7 +74,7 @@ func TestWikiService_Good_GetPage(t *testing.T) { } } -func TestWikiService_Good_CreatePage(t *testing.T) { +func TestWikiService_CreatePage_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) @@ -118,7 +118,7 @@ func TestWikiService_Good_CreatePage(t *testing.T) { } } -func TestWikiService_Good_EditPage(t *testing.T) { +func TestWikiService_EditPage_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) @@ -151,7 +151,7 @@ func TestWikiService_Good_EditPage(t *testing.T) { } } -func TestWikiService_Good_DeletePage(t *testing.T) { +func TestWikiService_DeletePage_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) @@ -170,7 +170,7 @@ func TestWikiService_Good_DeletePage(t *testing.T) { } } -func TestWikiService_Bad_NotFound(t *testing.T) { +func TestWikiService_NotFound_Bad(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) json.NewEncoder(w).Encode(map[string]string{"message": "page not found"}) -- 2.45.3 From a5dc900fd5bf57d46ab2d0b3d8a64b67890b349e Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 27 Mar 2026 04:50:29 +0000 Subject: [PATCH 3/5] docs(ax): complete v0.8.0 usage examples Co-Authored-By: Virgil --- actions.go | 54 +++++++++++++++++++++++++++++++++- admin.go | 54 +++++++++++++++++++++++++++++++++- branches.go | 26 ++++++++++++++++- client.go | 61 ++++++++++++++++++++++++++++++++------- cmd/forgegen/generator.go | 2 +- cmd/forgegen/parser.go | 20 ++++++------- commits.go | 34 +++++++++++++++++++++- config.go | 4 +-- contents.go | 22 +++++++++++++- doc.go | 2 +- forge.go | 8 +++-- issues.go | 50 +++++++++++++++++++++++++++++++- labels.go | 38 +++++++++++++++++++++++- milestones.go | 14 ++++++++- misc.go | 30 ++++++++++++++++++- notifications.go | 30 ++++++++++++++++++- orgs.go | 34 +++++++++++++++++++++- packages.go | 26 ++++++++++++++++- pagination.go | 12 ++++---- params.go | 4 +-- pulls.go | 30 ++++++++++++++++++- releases.go | 26 ++++++++++++++++- repos.go | 38 +++++++++++++++++++++++- resource.go | 32 ++++++++++++++++++-- teams.go | 42 ++++++++++++++++++++++++++- users.go | 46 ++++++++++++++++++++++++++++- webhooks.go | 14 ++++++++- wiki.go | 22 +++++++++++++- 28 files changed, 720 insertions(+), 55 deletions(-) diff --git a/actions.go b/actions.go index 0e9a39f..2e49781 100644 --- a/actions.go +++ b/actions.go @@ -11,7 +11,7 @@ import ( // organisations — secrets, variables, and workflow dispatches. // No Resource embedding — heterogeneous endpoints across repo and org levels. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Actions.ListRepoSecrets(ctx, "core", "go-forge") @@ -24,12 +24,20 @@ func newActionsService(c *Client) *ActionsService { } // ListRepoSecrets returns all secrets for a repository. +// +// Usage example: +// +// _, err := s.ListRepoSecrets(ctx, owner, repo) 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)) return ListAll[types.Secret](ctx, s.client, path, nil) } // IterRepoSecrets returns an iterator over all secrets for a repository. +// +// Usage example: +// +// _ = s.IterRepoSecrets(ctx, owner, repo) 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)) return ListIter[types.Secret](ctx, s.client, path, nil) @@ -37,6 +45,10 @@ func (s *ActionsService) IterRepoSecrets(ctx context.Context, owner, repo string // CreateRepoSecret creates or updates a secret in a repository. // Forgejo expects a PUT with {"data": "secret-value"} body. +// +// Usage example: +// +// err := s.CreateRepoSecret(ctx, owner, repo, name, data) func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{name}", pathParams("owner", owner, "repo", repo, "name", name)) body := map[string]string{"data": data} @@ -44,18 +56,30 @@ func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name } // DeleteRepoSecret removes a secret from a repository. +// +// Usage example: +// +// err := s.DeleteRepoSecret(ctx, owner, repo, name) func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/secrets/{name}", pathParams("owner", owner, "repo", repo, "name", name)) return s.client.Delete(ctx, path) } // ListRepoVariables returns all action variables for a repository. +// +// Usage example: +// +// _, err := s.ListRepoVariables(ctx, owner, repo) 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)) return ListAll[types.ActionVariable](ctx, s.client, path, nil) } // IterRepoVariables returns an iterator over all action variables for a repository. +// +// Usage example: +// +// _ = s.IterRepoVariables(ctx, owner, repo) 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)) return ListIter[types.ActionVariable](ctx, s.client, path, nil) @@ -63,6 +87,10 @@ func (s *ActionsService) IterRepoVariables(ctx context.Context, owner, repo stri // CreateRepoVariable creates a new action variable in a repository. // Forgejo expects a POST with {"value": "var-value"} body. +// +// Usage example: +// +// err := s.CreateRepoVariable(ctx, owner, repo, name, value) func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{name}", pathParams("owner", owner, "repo", repo, "name", name)) body := types.CreateVariableOption{Value: value} @@ -70,36 +98,60 @@ func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, na } // DeleteRepoVariable removes an action variable from a repository. +// +// Usage example: +// +// err := s.DeleteRepoVariable(ctx, owner, repo, name) func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/actions/variables/{name}", pathParams("owner", owner, "repo", repo, "name", name)) return s.client.Delete(ctx, path) } // ListOrgSecrets returns all secrets for an organisation. +// +// Usage example: +// +// _, err := s.ListOrgSecrets(ctx, org) func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string) ([]types.Secret, error) { path := ResolvePath("/api/v1/orgs/{org}/actions/secrets", pathParams("org", org)) return ListAll[types.Secret](ctx, s.client, path, nil) } // IterOrgSecrets returns an iterator over all secrets for an organisation. +// +// Usage example: +// +// _ = s.IterOrgSecrets(ctx, org) 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)) return ListIter[types.Secret](ctx, s.client, path, nil) } // ListOrgVariables returns all action variables for an organisation. +// +// Usage example: +// +// _, err := s.ListOrgVariables(ctx, org) func (s *ActionsService) ListOrgVariables(ctx context.Context, org string) ([]types.ActionVariable, error) { path := ResolvePath("/api/v1/orgs/{org}/actions/variables", pathParams("org", org)) return ListAll[types.ActionVariable](ctx, s.client, path, nil) } // IterOrgVariables returns an iterator over all action variables for an organisation. +// +// Usage example: +// +// _ = s.IterOrgVariables(ctx, org) 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)) return ListIter[types.ActionVariable](ctx, s.client, path, nil) } // DispatchWorkflow triggers a workflow run. +// +// Usage example: +// +// err := s.DispatchWorkflow(ctx, owner, repo, workflow, opts) 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/{workflow}/dispatches", pathParams("owner", owner, "repo", repo, "workflow", workflow)) return s.client.Post(ctx, path, opts, nil) diff --git a/admin.go b/admin.go index 825abef..76a5d38 100644 --- a/admin.go +++ b/admin.go @@ -11,7 +11,7 @@ import ( // Unlike other services, AdminService does not embed Resource[T,C,U] // because admin endpoints are heterogeneous. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Admin.ListUsers(ctx) @@ -24,16 +24,28 @@ func newAdminService(c *Client) *AdminService { } // ListUsers returns all users (admin only). +// +// Usage example: +// +// _, err := s.ListUsers(ctx) func (s *AdminService) ListUsers(ctx context.Context) ([]types.User, error) { return ListAll[types.User](ctx, s.client, "/api/v1/admin/users", nil) } // IterUsers returns an iterator over all users (admin only). +// +// Usage example: +// +// _ = s.IterUsers(ctx) func (s *AdminService) IterUsers(ctx context.Context) iter.Seq2[types.User, error] { return ListIter[types.User](ctx, s.client, "/api/v1/admin/users", nil) } // CreateUser creates a new user (admin only). +// +// Usage example: +// +// _, err := s.CreateUser(ctx, opts) func (s *AdminService) CreateUser(ctx context.Context, opts *types.CreateUserOption) (*types.User, error) { var out types.User if err := s.client.Post(ctx, "/api/v1/admin/users", opts, &out); err != nil { @@ -43,56 +55,96 @@ func (s *AdminService) CreateUser(ctx context.Context, opts *types.CreateUserOpt } // EditUser edits an existing user (admin only). +// +// Usage example: +// +// err := s.EditUser(ctx, username, opts) func (s *AdminService) EditUser(ctx context.Context, username string, opts map[string]any) error { path := ResolvePath("/api/v1/admin/users/{username}", Params{"username": username}) return s.client.Patch(ctx, path, opts, nil) } // DeleteUser deletes a user (admin only). +// +// Usage example: +// +// err := s.DeleteUser(ctx, username) func (s *AdminService) DeleteUser(ctx context.Context, username string) error { path := ResolvePath("/api/v1/admin/users/{username}", Params{"username": username}) return s.client.Delete(ctx, path) } // RenameUser renames a user (admin only). +// +// Usage example: +// +// err := s.RenameUser(ctx, username, newName) func (s *AdminService) RenameUser(ctx context.Context, username, newName string) error { path := ResolvePath("/api/v1/admin/users/{username}/rename", Params{"username": username}) return s.client.Post(ctx, path, &types.RenameUserOption{NewName: newName}, nil) } // ListOrgs returns all organisations (admin only). +// +// Usage example: +// +// _, err := s.ListOrgs(ctx) func (s *AdminService) ListOrgs(ctx context.Context) ([]types.Organization, error) { return ListAll[types.Organization](ctx, s.client, "/api/v1/admin/orgs", nil) } // IterOrgs returns an iterator over all organisations (admin only). +// +// Usage example: +// +// _ = s.IterOrgs(ctx) func (s *AdminService) IterOrgs(ctx context.Context) iter.Seq2[types.Organization, error] { return ListIter[types.Organization](ctx, s.client, "/api/v1/admin/orgs", nil) } // RunCron runs a cron task by name (admin only). +// +// Usage example: +// +// err := s.RunCron(ctx, task) func (s *AdminService) RunCron(ctx context.Context, task string) error { path := ResolvePath("/api/v1/admin/cron/{task}", Params{"task": task}) return s.client.Post(ctx, path, nil, nil) } // ListCron returns all cron tasks (admin only). +// +// Usage example: +// +// _, err := s.ListCron(ctx) func (s *AdminService) ListCron(ctx context.Context) ([]types.Cron, error) { return ListAll[types.Cron](ctx, s.client, "/api/v1/admin/cron", nil) } // IterCron returns an iterator over all cron tasks (admin only). +// +// Usage example: +// +// _ = s.IterCron(ctx) func (s *AdminService) IterCron(ctx context.Context) iter.Seq2[types.Cron, error] { return ListIter[types.Cron](ctx, s.client, "/api/v1/admin/cron", nil) } // AdoptRepo adopts an unadopted repository (admin only). +// +// Usage example: +// +// err := s.AdoptRepo(ctx, owner, repo) func (s *AdminService) AdoptRepo(ctx context.Context, owner, repo string) error { path := ResolvePath("/api/v1/admin/unadopted/{owner}/{repo}", Params{"owner": owner, "repo": repo}) return s.client.Post(ctx, path, nil, nil) } // GenerateRunnerToken generates an actions runner registration token. +// +// Usage example: +// +// _, err := s.GenerateRunnerToken(ctx) func (s *AdminService) GenerateRunnerToken(ctx context.Context) (string, error) { var out struct { Token string `json:"token"` diff --git a/branches.go b/branches.go index b7d4fd1..af2279c 100644 --- a/branches.go +++ b/branches.go @@ -9,7 +9,7 @@ import ( // BranchService handles branch operations within a repository. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Branches.ListBranchProtections(ctx, "core", "go-forge") @@ -26,18 +26,30 @@ func newBranchService(c *Client) *BranchService { } // ListBranchProtections returns all branch protections for a repository. +// +// Usage example: +// +// _, err := s.ListBranchProtections(ctx, owner, repo) 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)) return ListAll[types.BranchProtection](ctx, s.client, path, nil) } // IterBranchProtections returns an iterator over all branch protections for a repository. +// +// Usage example: +// +// _ = s.IterBranchProtections(ctx, owner, repo) 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)) return ListIter[types.BranchProtection](ctx, s.client, path, nil) } // GetBranchProtection returns a single branch protection by name. +// +// Usage example: +// +// _, err := s.GetBranchProtection(ctx, owner, repo, name) func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, name string) (*types.BranchProtection, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/branch_protections/{name}", pathParams("owner", owner, "repo", repo, "name", name)) var out types.BranchProtection @@ -48,6 +60,10 @@ func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, na } // CreateBranchProtection creates a new branch protection rule. +// +// Usage example: +// +// _, err := s.CreateBranchProtection(ctx, owner, repo, opts) 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)) var out types.BranchProtection @@ -58,6 +74,10 @@ func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo } // EditBranchProtection updates an existing branch protection rule. +// +// Usage example: +// +// _, err := s.EditBranchProtection(ctx, owner, repo, name, opts) 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)) var out types.BranchProtection @@ -68,6 +88,10 @@ func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, n } // DeleteBranchProtection deletes a branch protection rule. +// +// Usage example: +// +// err := s.DeleteBranchProtection(ctx, owner, repo, name) 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)) return s.client.Delete(ctx, path) diff --git a/client.go b/client.go index 8b4ca90..6d3b506 100644 --- a/client.go +++ b/client.go @@ -13,7 +13,7 @@ import ( // APIError represents an error response from the Forgejo API. // -// Usage: +// Usage example: // // if apiErr, ok := err.(*forge.APIError); ok { // _ = apiErr.StatusCode @@ -24,13 +24,18 @@ type APIError struct { URL string } +// Error returns the API error as a diagnostic string. +// +// Usage example: +// +// _ = e.Error() func (e *APIError) Error() string { return core.Concat("forge: ", e.URL, " ", strconv.Itoa(e.StatusCode), ": ", e.Message) } // IsNotFound returns true if the error is a 404 response. // -// Usage: +// Usage example: // // if forge.IsNotFound(err) { // return nil @@ -42,7 +47,7 @@ func IsNotFound(err error) bool { // IsForbidden returns true if the error is a 403 response. // -// Usage: +// Usage example: // // if forge.IsForbidden(err) { // return nil @@ -54,7 +59,7 @@ func IsForbidden(err error) bool { // IsConflict returns true if the error is a 409 response. // -// Usage: +// Usage example: // // if forge.IsConflict(err) { // return nil @@ -66,14 +71,14 @@ func IsConflict(err error) bool { // Option configures the Client. // -// Usage: +// Usage example: // // opts := []forge.Option{forge.WithUserAgent("go-forge/1.0")} type Option func(*Client) // WithHTTPClient sets a custom http.Client. // -// Usage: +// Usage example: // // c := forge.NewClient(url, token, forge.WithHTTPClient(http.DefaultClient)) func WithHTTPClient(hc *http.Client) Option { @@ -82,7 +87,7 @@ func WithHTTPClient(hc *http.Client) Option { // WithUserAgent sets the User-Agent header. // -// Usage: +// Usage example: // // c := forge.NewClient(url, token, forge.WithUserAgent("go-forge/1.0")) func WithUserAgent(ua string) Option { @@ -91,7 +96,7 @@ func WithUserAgent(ua string) Option { // RateLimit represents the rate limit information from the Forgejo API. // -// Usage: +// Usage example: // // rl := client.RateLimit() // _ = rl.Remaining @@ -103,7 +108,7 @@ type RateLimit struct { // Client is a low-level HTTP client for the Forgejo API. // -// Usage: +// Usage example: // // c := forge.NewClient("https://forge.lthn.ai", "token") // _ = c @@ -116,13 +121,17 @@ type Client struct { } // RateLimit returns the last known rate limit information. +// +// Usage example: +// +// _ = c.RateLimit() func (c *Client) RateLimit() RateLimit { return c.rateLimit } // NewClient creates a new Forgejo API client. // -// Usage: +// Usage example: // // c := forge.NewClient("https://forge.lthn.ai", "token") // _ = c @@ -144,36 +153,60 @@ func NewClient(url, token string, opts ...Option) *Client { } // Get performs a GET request. +// +// Usage example: +// +// err := c.Get(ctx, path, out) func (c *Client) Get(ctx context.Context, path string, out any) error { _, err := c.doJSON(ctx, http.MethodGet, path, nil, out) return err } // Post performs a POST request. +// +// Usage example: +// +// err := c.Post(ctx, path, body, out) func (c *Client) Post(ctx context.Context, path string, body, out any) error { _, err := c.doJSON(ctx, http.MethodPost, path, body, out) return err } // Patch performs a PATCH request. +// +// Usage example: +// +// err := c.Patch(ctx, path, body, out) func (c *Client) Patch(ctx context.Context, path string, body, out any) error { _, err := c.doJSON(ctx, http.MethodPatch, path, body, out) return err } // Put performs a PUT request. +// +// Usage example: +// +// err := c.Put(ctx, path, body, out) func (c *Client) Put(ctx context.Context, path string, body, out any) error { _, err := c.doJSON(ctx, http.MethodPut, path, body, out) return err } // Delete performs a DELETE request. +// +// Usage example: +// +// err := c.Delete(ctx, path) func (c *Client) Delete(ctx context.Context, path string) error { _, err := c.doJSON(ctx, http.MethodDelete, path, nil, nil) return err } // DeleteWithBody performs a DELETE request with a JSON body. +// +// Usage example: +// +// err := c.DeleteWithBody(ctx, path, body) func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) error { _, err := c.doJSON(ctx, http.MethodDelete, path, body, nil) return err @@ -182,6 +215,10 @@ func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) erro // PostRaw performs a POST request with a JSON body and returns the raw // response body as bytes instead of JSON-decoding. Useful for endpoints // such as /markdown that return raw HTML text. +// +// Usage example: +// +// _, err := c.PostRaw(ctx, path, body) func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, error) { url := c.baseURL + path @@ -225,6 +262,10 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er // GetRaw performs a GET request and returns the raw response body as bytes // instead of JSON-decoding. Useful for endpoints that return raw file content. +// +// Usage example: +// +// _, err := c.GetRaw(ctx, path) func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error) { url := c.baseURL + path diff --git a/cmd/forgegen/generator.go b/cmd/forgegen/generator.go index 3e0a3fb..7c88127 100644 --- a/cmd/forgegen/generator.go +++ b/cmd/forgegen/generator.go @@ -204,7 +204,7 @@ type templateData struct { // Generate writes Go source files for the extracted types, grouped by logical domain. // -// Usage: +// Usage example: // // err := Generate(types, pairs, "types") // _ = err diff --git a/cmd/forgegen/parser.go b/cmd/forgegen/parser.go index f42e77b..83e1c92 100644 --- a/cmd/forgegen/parser.go +++ b/cmd/forgegen/parser.go @@ -11,7 +11,7 @@ import ( // Spec represents a Swagger 2.0 specification document. // -// Usage: +// Usage example: // // spec, err := LoadSpec("testdata/swagger.v1.json") // _ = spec @@ -24,7 +24,7 @@ type Spec struct { // SpecInfo holds metadata about the API specification. // -// Usage: +// Usage example: // // _ = SpecInfo{Title: "Forgejo API", Version: "1.0"} type SpecInfo struct { @@ -34,7 +34,7 @@ type SpecInfo struct { // SchemaDefinition represents a single type definition in the swagger spec. // -// Usage: +// Usage example: // // _ = SchemaDefinition{Type: "object"} type SchemaDefinition struct { @@ -48,7 +48,7 @@ type SchemaDefinition struct { // SchemaProperty represents a single property within a schema definition. // -// Usage: +// Usage example: // // _ = SchemaProperty{Type: "string"} type SchemaProperty struct { @@ -63,7 +63,7 @@ type SchemaProperty struct { // GoType is the intermediate representation for a Go type to be generated. // -// Usage: +// Usage example: // // _ = GoType{Name: "Repository"} type GoType struct { @@ -76,7 +76,7 @@ type GoType struct { // GoField is the intermediate representation for a single struct field. // -// Usage: +// Usage example: // // _ = GoField{GoName: "ID", GoType: "int64"} type GoField struct { @@ -89,7 +89,7 @@ type GoField struct { // CRUDPair groups a base type with its corresponding Create and Edit option types. // -// Usage: +// Usage example: // // _ = CRUDPair{Base: "Repository", Create: "CreateRepoOption", Edit: "EditRepoOption"} type CRUDPair struct { @@ -100,7 +100,7 @@ type CRUDPair struct { // LoadSpec reads and parses a Swagger 2.0 JSON file from the given path. // -// Usage: +// Usage example: // // spec, err := LoadSpec("testdata/swagger.v1.json") // _ = spec @@ -118,7 +118,7 @@ func LoadSpec(path string) (*Spec, error) { // ExtractTypes converts all swagger definitions into Go type intermediate representations. // -// Usage: +// Usage example: // // types := ExtractTypes(spec) // _ = types["Repository"] @@ -164,7 +164,7 @@ func ExtractTypes(spec *Spec) map[string]*GoType { // DetectCRUDPairs finds Create*Option / Edit*Option pairs in the swagger definitions // and maps them back to the base type name. // -// Usage: +// Usage example: // // pairs := DetectCRUDPairs(spec) // _ = pairs diff --git a/commits.go b/commits.go index 8db96cc..855ff89 100644 --- a/commits.go +++ b/commits.go @@ -12,7 +12,7 @@ import ( // No Resource embedding — collection and item commit paths differ, and the // remaining endpoints are heterogeneous across status and note paths. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Commits.GetCombinedStatus(ctx, "core", "go-forge", "main") @@ -30,21 +30,37 @@ func newCommitService(c *Client) *CommitService { } // List returns a single page of commits for a repository. +// +// Usage example: +// +// _, err := s.List(ctx, params, opts) func (s *CommitService) List(ctx context.Context, params Params, opts ListOptions) (*PagedResult[types.Commit], error) { return ListPage[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil, opts) } // ListAll returns all commits for a repository. +// +// Usage example: +// +// _, err := s.ListAll(ctx, params) func (s *CommitService) ListAll(ctx context.Context, params Params) ([]types.Commit, error) { return ListAll[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil) } // Iter returns an iterator over all commits for a repository. +// +// Usage example: +// +// _ = s.Iter(ctx, params) func (s *CommitService) Iter(ctx context.Context, params Params) iter.Seq2[types.Commit, error] { return ListIter[types.Commit](ctx, s.client, ResolvePath(commitCollectionPath, params), nil) } // Get returns a single commit by SHA or ref. +// +// Usage example: +// +// _, err := s.Get(ctx, params) func (s *CommitService) Get(ctx context.Context, params Params) (*types.Commit, error) { var out types.Commit if err := s.client.Get(ctx, ResolvePath(commitItemPath, params), &out); err != nil { @@ -54,6 +70,10 @@ func (s *CommitService) Get(ctx context.Context, params Params) (*types.Commit, } // GetCombinedStatus returns the combined status for a given ref (branch, tag, or SHA). +// +// Usage example: +// +// _, err := s.GetCombinedStatus(ctx, owner, repo, ref) func (s *CommitService) GetCombinedStatus(ctx context.Context, owner, repo, ref string) (*types.CombinedStatus, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/statuses/{ref}", pathParams("owner", owner, "repo", repo, "ref", ref)) var out types.CombinedStatus @@ -64,6 +84,10 @@ func (s *CommitService) GetCombinedStatus(ctx context.Context, owner, repo, ref } // ListStatuses returns all commit statuses for a given ref. +// +// Usage example: +// +// _, err := s.ListStatuses(ctx, owner, repo, ref) func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref string) ([]types.CommitStatus, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/commits/{ref}/statuses", pathParams("owner", owner, "repo", repo, "ref", ref)) var out []types.CommitStatus @@ -74,6 +98,10 @@ func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref strin } // CreateStatus creates a new commit status for the given SHA. +// +// Usage example: +// +// _, err := s.CreateStatus(ctx, owner, repo, sha, opts) 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)) var out types.CommitStatus @@ -84,6 +112,10 @@ func (s *CommitService) CreateStatus(ctx context.Context, owner, repo, sha strin } // GetNote returns the git note for a given commit SHA. +// +// Usage example: +// +// _, err := s.GetNote(ctx, owner, repo, sha) func (s *CommitService) GetNote(ctx context.Context, owner, repo, sha string) (*types.Note, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/git/notes/{sha}", pathParams("owner", owner, "repo", repo, "sha", sha)) var out types.Note diff --git a/config.go b/config.go index d1e442f..8f085b3 100644 --- a/config.go +++ b/config.go @@ -23,7 +23,7 @@ const ( // - FORGE_URL — base URL of the Forgejo instance // - FORGE_TOKEN — API token for authentication // -// Usage: +// Usage example: // // url, token, err := forge.ResolveConfig("", "") // _ = url @@ -47,7 +47,7 @@ func ResolveConfig(flagURL, flagToken string) (url, token string, err error) { // NewForgeFromConfig creates a new Forge client using resolved configuration. // It returns an error if no API token is available from flags or environment. // -// Usage: +// Usage example: // // f, err := forge.NewForgeFromConfig("", "") // _ = f diff --git a/contents.go b/contents.go index 0fdbfc0..e6139cc 100644 --- a/contents.go +++ b/contents.go @@ -9,7 +9,7 @@ import ( // ContentService handles file read/write operations via the Forgejo API. // No Resource embedding — paths vary by operation. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Contents.GetFile(ctx, "core", "go-forge", "README.md") @@ -22,6 +22,10 @@ func newContentService(c *Client) *ContentService { } // GetFile returns metadata and content for a file in a repository. +// +// Usage example: +// +// _, err := s.GetFile(ctx, owner, repo, filepath) 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)) var out types.ContentsResponse @@ -32,6 +36,10 @@ func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath stri } // CreateFile creates a new file in a repository. +// +// Usage example: +// +// _, err := s.CreateFile(ctx, owner, repo, filepath, opts) 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)) var out types.FileResponse @@ -42,6 +50,10 @@ func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath s } // UpdateFile updates an existing file in a repository. +// +// Usage example: +// +// _, err := s.UpdateFile(ctx, owner, repo, filepath, opts) 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)) var out types.FileResponse @@ -52,12 +64,20 @@ func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath s } // DeleteFile deletes a file from a repository. Uses DELETE with a JSON body. +// +// Usage example: +// +// err := s.DeleteFile(ctx, owner, repo, filepath, opts) 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)) return s.client.DeleteWithBody(ctx, path, opts) } // GetRawFile returns the raw file content as bytes. +// +// Usage example: +// +// _, err := s.GetRawFile(ctx, owner, repo, filepath) 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)) return s.client.GetRaw(ctx, path) diff --git a/doc.go b/doc.go index 1a3193d..fbbd6a9 100644 --- a/doc.go +++ b/doc.go @@ -1,6 +1,6 @@ // Package forge provides a full-coverage Go client for the Forgejo API. // -// Usage: +// Usage example: // // ctx := context.Background() // f := forge.NewForge("https://forge.lthn.ai", "your-token") diff --git a/forge.go b/forge.go index becab42..cb5424d 100644 --- a/forge.go +++ b/forge.go @@ -2,7 +2,7 @@ package forge // Forge is the top-level client for the Forgejo API. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _ = f.Repos @@ -32,7 +32,7 @@ type Forge struct { // NewForge creates a new Forge client. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _ = f @@ -62,4 +62,8 @@ func NewForge(url, token string, opts ...Option) *Forge { } // Client returns the underlying HTTP client. +// +// Usage example: +// +// _ = f.Client() func (f *Forge) Client() *Client { return f.client } diff --git a/issues.go b/issues.go index 15306a4..879072b 100644 --- a/issues.go +++ b/issues.go @@ -9,7 +9,7 @@ import ( // IssueService handles issue operations within a repository. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Issues.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) @@ -26,18 +26,30 @@ func newIssueService(c *Client) *IssueService { } // Pin pins an issue. +// +// Usage example: +// +// err := s.Pin(ctx, owner, repo, index) 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))) return s.client.Post(ctx, path, nil, nil) } // Unpin unpins an issue. +// +// Usage example: +// +// err := s.Unpin(ctx, owner, repo, index) 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))) return s.client.Delete(ctx, path) } // SetDeadline sets or updates the deadline on an issue. +// +// Usage example: +// +// err := s.SetDeadline(ctx, owner, repo, index, deadline) 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))) body := map[string]string{"due_date": deadline} @@ -45,6 +57,10 @@ func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, inde } // AddReaction adds a reaction to an issue. +// +// Usage example: +// +// err := s.AddReaction(ctx, owner, repo, index, reaction) func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) body := map[string]string{"content": reaction} @@ -52,6 +68,10 @@ func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, inde } // DeleteReaction removes a reaction from an issue. +// +// Usage example: +// +// err := s.DeleteReaction(ctx, owner, repo, index, reaction) func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, index int64, reaction string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/issues/{index}/reactions", pathParams("owner", owner, "repo", repo, "index", int64String(index))) body := map[string]string{"content": reaction} @@ -59,18 +79,30 @@ func (s *IssueService) DeleteReaction(ctx context.Context, owner, repo string, i } // StartStopwatch starts the stopwatch on an issue. +// +// Usage example: +// +// err := s.StartStopwatch(ctx, owner, repo, index) 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))) return s.client.Post(ctx, path, nil, nil) } // StopStopwatch stops the stopwatch on an issue. +// +// Usage example: +// +// err := s.StopStopwatch(ctx, owner, repo, index) 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))) return s.client.Post(ctx, path, nil, nil) } // AddLabels adds labels to an issue. +// +// Usage example: +// +// err := s.AddLabels(ctx, owner, repo, index, labelIDs) 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))) body := types.IssueLabelsOption{Labels: toAnySlice(labelIDs)} @@ -78,24 +110,40 @@ func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index } // RemoveLabel removes a single label from an issue. +// +// Usage example: +// +// err := s.RemoveLabel(ctx, owner, repo, index, labelID) 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/{labelID}", pathParams("owner", owner, "repo", repo, "index", int64String(index), "labelID", int64String(labelID))) return s.client.Delete(ctx, path) } // ListComments returns all comments on an issue. +// +// Usage example: +// +// _, err := s.ListComments(ctx, owner, repo, index) 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))) return ListAll[types.Comment](ctx, s.client, path, nil) } // IterComments returns an iterator over all comments on an issue. +// +// Usage example: +// +// _ = s.IterComments(ctx, owner, repo, index) 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))) return ListIter[types.Comment](ctx, s.client, path, nil) } // CreateComment creates a comment on an issue. +// +// Usage example: +// +// _, err := s.CreateComment(ctx, owner, repo, index, body) 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))) opts := types.CreateIssueCommentOption{Body: body} diff --git a/labels.go b/labels.go index 341e7d3..800f145 100644 --- a/labels.go +++ b/labels.go @@ -10,7 +10,7 @@ import ( // LabelService handles repository labels, organisation labels, and issue labels. // No Resource embedding — paths are heterogeneous. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Labels.ListRepoLabels(ctx, "core", "go-forge") @@ -23,18 +23,30 @@ func newLabelService(c *Client) *LabelService { } // ListRepoLabels returns all labels for a repository. +// +// Usage example: +// +// _, err := s.ListRepoLabels(ctx, owner, repo) 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)) return ListAll[types.Label](ctx, s.client, path, nil) } // IterRepoLabels returns an iterator over all labels for a repository. +// +// Usage example: +// +// _ = s.IterRepoLabels(ctx, owner, repo) 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)) return ListIter[types.Label](ctx, s.client, path, nil) } // GetRepoLabel returns a single label by ID. +// +// Usage example: +// +// _, err := s.GetRepoLabel(ctx, owner, repo, id) func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id int64) (*types.Label, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/labels/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id))) var out types.Label @@ -45,6 +57,10 @@ func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id } // CreateRepoLabel creates a new label in a repository. +// +// Usage example: +// +// _, err := s.CreateRepoLabel(ctx, owner, repo, opts) 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)) var out types.Label @@ -55,6 +71,10 @@ func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string, } // EditRepoLabel updates an existing label in a repository. +// +// Usage example: +// +// _, err := s.EditRepoLabel(ctx, owner, repo, id, opts) 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))) var out types.Label @@ -65,24 +85,40 @@ func (s *LabelService) EditRepoLabel(ctx context.Context, owner, repo string, id } // DeleteRepoLabel deletes a label from a repository. +// +// Usage example: +// +// err := s.DeleteRepoLabel(ctx, owner, repo, id) 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))) return s.client.Delete(ctx, path) } // ListOrgLabels returns all labels for an organisation. +// +// Usage example: +// +// _, err := s.ListOrgLabels(ctx, org) func (s *LabelService) ListOrgLabels(ctx context.Context, org string) ([]types.Label, error) { path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org)) return ListAll[types.Label](ctx, s.client, path, nil) } // IterOrgLabels returns an iterator over all labels for an organisation. +// +// Usage example: +// +// _ = s.IterOrgLabels(ctx, org) func (s *LabelService) IterOrgLabels(ctx context.Context, org string) iter.Seq2[types.Label, error] { path := ResolvePath("/api/v1/orgs/{org}/labels", pathParams("org", org)) return ListIter[types.Label](ctx, s.client, path, nil) } // CreateOrgLabel creates a new label in an organisation. +// +// Usage example: +// +// _, err := s.CreateOrgLabel(ctx, org, opts) 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)) var out types.Label diff --git a/milestones.go b/milestones.go index 84c3365..a8a0fed 100644 --- a/milestones.go +++ b/milestones.go @@ -8,7 +8,7 @@ import ( // MilestoneService handles repository milestones. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Milestones.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) @@ -21,12 +21,20 @@ func newMilestoneService(c *Client) *MilestoneService { } // ListAll returns all milestones for a repository. +// +// Usage example: +// +// _, err := s.ListAll(ctx, params) func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones", params) return ListAll[types.Milestone](ctx, s.client, path, nil) } // Get returns a single milestone by ID. +// +// Usage example: +// +// _, err := s.Get(ctx, owner, repo, id) func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/milestones/{id}", pathParams("owner", owner, "repo", repo, "id", int64String(id))) var out types.Milestone @@ -37,6 +45,10 @@ func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64 } // Create creates a new milestone. +// +// Usage example: +// +// _, err := s.Create(ctx, owner, repo, opts) 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)) var out types.Milestone diff --git a/misc.go b/misc.go index 77243c2..3bdd4ba 100644 --- a/misc.go +++ b/misc.go @@ -11,7 +11,7 @@ import ( // server metadata. // No Resource embedding — heterogeneous read-only endpoints. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Misc.GetVersion(ctx) @@ -25,6 +25,10 @@ func newMiscService(c *Client) *MiscService { // RenderMarkdown renders markdown text to HTML. The response is raw HTML // text, not JSON. +// +// Usage example: +// +// _, err := s.RenderMarkdown(ctx, text, mode) func (s *MiscService) RenderMarkdown(ctx context.Context, text, mode string) (string, error) { body := types.MarkdownOption{Text: text, Mode: mode} data, err := s.client.PostRaw(ctx, "/api/v1/markdown", body) @@ -35,6 +39,10 @@ func (s *MiscService) RenderMarkdown(ctx context.Context, text, mode string) (st } // ListLicenses returns all available licence templates. +// +// Usage example: +// +// _, err := s.ListLicenses(ctx) func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplateListEntry, error) { var out []types.LicensesTemplateListEntry if err := s.client.Get(ctx, "/api/v1/licenses", &out); err != nil { @@ -44,6 +52,10 @@ func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplat } // GetLicense returns a single licence template by name. +// +// Usage example: +// +// _, err := s.GetLicense(ctx, name) func (s *MiscService) GetLicense(ctx context.Context, name string) (*types.LicenseTemplateInfo, error) { path := ResolvePath("/api/v1/licenses/{name}", pathParams("name", name)) var out types.LicenseTemplateInfo @@ -54,6 +66,10 @@ func (s *MiscService) GetLicense(ctx context.Context, name string) (*types.Licen } // ListGitignoreTemplates returns all available gitignore template names. +// +// Usage example: +// +// _, err := s.ListGitignoreTemplates(ctx) func (s *MiscService) ListGitignoreTemplates(ctx context.Context) ([]string, error) { var out []string if err := s.client.Get(ctx, "/api/v1/gitignore/templates", &out); err != nil { @@ -63,6 +79,10 @@ func (s *MiscService) ListGitignoreTemplates(ctx context.Context) ([]string, err } // GetGitignoreTemplate returns a single gitignore template by name. +// +// Usage example: +// +// _, err := s.GetGitignoreTemplate(ctx, name) func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*types.GitignoreTemplateInfo, error) { path := ResolvePath("/api/v1/gitignore/templates/{name}", pathParams("name", name)) var out types.GitignoreTemplateInfo @@ -73,6 +93,10 @@ func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*t } // GetNodeInfo returns the NodeInfo metadata for the Forgejo instance. +// +// Usage example: +// +// _, err := s.GetNodeInfo(ctx) func (s *MiscService) GetNodeInfo(ctx context.Context) (*types.NodeInfo, error) { var out types.NodeInfo if err := s.client.Get(ctx, "/api/v1/nodeinfo", &out); err != nil { @@ -82,6 +106,10 @@ func (s *MiscService) GetNodeInfo(ctx context.Context) (*types.NodeInfo, error) } // GetVersion returns the server version. +// +// Usage example: +// +// _, err := s.GetVersion(ctx) func (s *MiscService) GetVersion(ctx context.Context) (*types.ServerVersion, error) { var out types.ServerVersion if err := s.client.Get(ctx, "/api/v1/version", &out); err != nil { diff --git a/notifications.go b/notifications.go index d053bbc..dc4ebc1 100644 --- a/notifications.go +++ b/notifications.go @@ -10,7 +10,7 @@ import ( // NotificationService handles notification operations via the Forgejo API. // No Resource embedding — varied endpoint shapes. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Notifications.List(ctx) @@ -23,33 +23,57 @@ func newNotificationService(c *Client) *NotificationService { } // List returns all notifications for the authenticated user. +// +// Usage example: +// +// _, err := s.List(ctx) func (s *NotificationService) List(ctx context.Context) ([]types.NotificationThread, error) { return ListAll[types.NotificationThread](ctx, s.client, "/api/v1/notifications", nil) } // Iter returns an iterator over all notifications for the authenticated user. +// +// Usage example: +// +// _ = s.Iter(ctx) func (s *NotificationService) Iter(ctx context.Context) iter.Seq2[types.NotificationThread, error] { return ListIter[types.NotificationThread](ctx, s.client, "/api/v1/notifications", nil) } // ListRepo returns all notifications for a specific repository. +// +// Usage example: +// +// _, err := s.ListRepo(ctx, owner, repo) 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)) return ListAll[types.NotificationThread](ctx, s.client, path, nil) } // IterRepo returns an iterator over all notifications for a specific repository. +// +// Usage example: +// +// _ = s.IterRepo(ctx, owner, repo) 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)) return ListIter[types.NotificationThread](ctx, s.client, path, nil) } // MarkRead marks all notifications as read. +// +// Usage example: +// +// err := s.MarkRead(ctx) func (s *NotificationService) MarkRead(ctx context.Context) error { return s.client.Put(ctx, "/api/v1/notifications", nil, nil) } // GetThread returns a single notification thread by ID. +// +// Usage example: +// +// _, err := s.GetThread(ctx, id) func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.NotificationThread, error) { path := ResolvePath("/api/v1/notifications/threads/{id}", pathParams("id", int64String(id))) var out types.NotificationThread @@ -60,6 +84,10 @@ func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.N } // MarkThreadRead marks a single notification thread as read. +// +// Usage example: +// +// err := s.MarkThreadRead(ctx, id) func (s *NotificationService) MarkThreadRead(ctx context.Context, id int64) error { path := ResolvePath("/api/v1/notifications/threads/{id}", pathParams("id", int64String(id))) return s.client.Patch(ctx, path, nil, nil) diff --git a/orgs.go b/orgs.go index 8001e86..fa3bd3c 100644 --- a/orgs.go +++ b/orgs.go @@ -9,7 +9,7 @@ import ( // OrgService handles organisation operations. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Orgs.ListMembers(ctx, "core") @@ -26,47 +26,79 @@ func newOrgService(c *Client) *OrgService { } // ListMembers returns all members of an organisation. +// +// Usage example: +// +// _, err := s.ListMembers(ctx, org) func (s *OrgService) ListMembers(ctx context.Context, org string) ([]types.User, error) { path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org)) return ListAll[types.User](ctx, s.client, path, nil) } // IterMembers returns an iterator over all members of an organisation. +// +// Usage example: +// +// _ = s.IterMembers(ctx, org) func (s *OrgService) IterMembers(ctx context.Context, org string) iter.Seq2[types.User, error] { path := ResolvePath("/api/v1/orgs/{org}/members", pathParams("org", org)) return ListIter[types.User](ctx, s.client, path, nil) } // AddMember adds a user to an organisation. +// +// Usage example: +// +// err := s.AddMember(ctx, org, username) func (s *OrgService) AddMember(ctx context.Context, org, username string) error { path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username)) return s.client.Put(ctx, path, nil, nil) } // RemoveMember removes a user from an organisation. +// +// Usage example: +// +// err := s.RemoveMember(ctx, org, username) func (s *OrgService) RemoveMember(ctx context.Context, org, username string) error { path := ResolvePath("/api/v1/orgs/{org}/members/{username}", pathParams("org", org, "username", username)) return s.client.Delete(ctx, path) } // ListUserOrgs returns all organisations for a user. +// +// Usage example: +// +// _, err := s.ListUserOrgs(ctx, username) func (s *OrgService) ListUserOrgs(ctx context.Context, username string) ([]types.Organization, error) { path := ResolvePath("/api/v1/users/{username}/orgs", pathParams("username", username)) return ListAll[types.Organization](ctx, s.client, path, nil) } // IterUserOrgs returns an iterator over all organisations for a user. +// +// Usage example: +// +// _ = s.IterUserOrgs(ctx, username) func (s *OrgService) IterUserOrgs(ctx context.Context, username string) iter.Seq2[types.Organization, error] { path := ResolvePath("/api/v1/users/{username}/orgs", pathParams("username", username)) return ListIter[types.Organization](ctx, s.client, path, nil) } // ListMyOrgs returns all organisations for the authenticated user. +// +// Usage example: +// +// _, err := s.ListMyOrgs(ctx) func (s *OrgService) ListMyOrgs(ctx context.Context) ([]types.Organization, error) { return ListAll[types.Organization](ctx, s.client, "/api/v1/user/orgs", nil) } // IterMyOrgs returns an iterator over all organisations for the authenticated user. +// +// Usage example: +// +// _ = s.IterMyOrgs(ctx) func (s *OrgService) IterMyOrgs(ctx context.Context) iter.Seq2[types.Organization, error] { return ListIter[types.Organization](ctx, s.client, "/api/v1/user/orgs", nil) } diff --git a/packages.go b/packages.go index 2553349..86c726d 100644 --- a/packages.go +++ b/packages.go @@ -10,7 +10,7 @@ import ( // PackageService handles package registry operations via the Forgejo API. // No Resource embedding — paths vary by operation. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Packages.List(ctx, "core") @@ -23,18 +23,30 @@ func newPackageService(c *Client) *PackageService { } // List returns all packages for a given owner. +// +// Usage example: +// +// _, err := s.List(ctx, owner) func (s *PackageService) List(ctx context.Context, owner string) ([]types.Package, error) { path := ResolvePath("/api/v1/packages/{owner}", pathParams("owner", owner)) return ListAll[types.Package](ctx, s.client, path, nil) } // Iter returns an iterator over all packages for a given owner. +// +// Usage example: +// +// _ = s.Iter(ctx, owner) func (s *PackageService) Iter(ctx context.Context, owner string) iter.Seq2[types.Package, error] { path := ResolvePath("/api/v1/packages/{owner}", pathParams("owner", owner)) return ListIter[types.Package](ctx, s.client, path, nil) } // Get returns a single package by owner, type, name, and version. +// +// Usage example: +// +// _, err := s.Get(ctx, owner, pkgType, name, version) func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version string) (*types.Package, error) { path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) var out types.Package @@ -45,18 +57,30 @@ func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version } // Delete removes a package by owner, type, name, and version. +// +// Usage example: +// +// err := s.Delete(ctx, owner, pkgType, name, version) func (s *PackageService) Delete(ctx context.Context, owner, pkgType, name, version string) error { path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) return s.client.Delete(ctx, path) } // ListFiles returns all files for a specific package version. +// +// Usage example: +// +// _, err := s.ListFiles(ctx, owner, pkgType, name, version) func (s *PackageService) ListFiles(ctx context.Context, owner, pkgType, name, version string) ([]types.PackageFile, error) { path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}/files", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) return ListAll[types.PackageFile](ctx, s.client, path, nil) } // IterFiles returns an iterator over all files for a specific package version. +// +// Usage example: +// +// _ = s.IterFiles(ctx, owner, pkgType, name, version) func (s *PackageService) IterFiles(ctx context.Context, owner, pkgType, name, version string) iter.Seq2[types.PackageFile, error] { path := ResolvePath("/api/v1/packages/{owner}/{pkgType}/{name}/{version}/files", pathParams("owner", owner, "pkgType", pkgType, "name", name, "version", version)) return ListIter[types.PackageFile](ctx, s.client, path, nil) diff --git a/pagination.go b/pagination.go index 3c2c468..3c511ad 100644 --- a/pagination.go +++ b/pagination.go @@ -12,7 +12,7 @@ import ( // ListOptions controls pagination. // -// Usage: +// Usage example: // // opts := forge.ListOptions{Page: 1, Limit: 50} // _ = opts @@ -23,7 +23,7 @@ type ListOptions struct { // DefaultList returns sensible default pagination. // -// Usage: +// Usage example: // // page, err := forge.ListPage[types.Repository](ctx, client, path, nil, forge.DefaultList) // _ = page @@ -31,7 +31,7 @@ var DefaultList = ListOptions{Page: 1, Limit: 50} // PagedResult holds a single page of results with metadata. // -// Usage: +// Usage example: // // page, err := forge.ListPage[types.Repository](ctx, client, path, nil, forge.DefaultList) // _ = page @@ -45,7 +45,7 @@ type PagedResult[T any] struct { // ListPage fetches a single page of results. // Extra query params can be passed via the query map. // -// Usage: +// Usage example: // // page, err := forge.ListPage[types.Repository](ctx, client, "/api/v1/user/repos", nil, forge.DefaultList) // _ = page @@ -91,7 +91,7 @@ func ListPage[T any](ctx context.Context, c *Client, path string, query map[stri // ListAll fetches all pages of results. // -// Usage: +// Usage example: // // items, err := forge.ListAll[types.Repository](ctx, client, "/api/v1/user/repos", nil) // _ = items @@ -116,7 +116,7 @@ func ListAll[T any](ctx context.Context, c *Client, path string, query map[strin // ListIter returns an iterator over all resources across all pages. // -// Usage: +// Usage example: // // for item, err := range forge.ListIter[types.Repository](ctx, client, "/api/v1/user/repos", nil) { // _, _ = item, err diff --git a/params.go b/params.go index 9646e97..67588de 100644 --- a/params.go +++ b/params.go @@ -9,7 +9,7 @@ import ( // Params maps path variable names to values. // Example: Params{"owner": "core", "repo": "go-forge"} // -// Usage: +// Usage example: // // params := forge.Params{"owner": "core", "repo": "go-forge"} // _ = params @@ -17,7 +17,7 @@ type Params map[string]string // ResolvePath substitutes {placeholders} in path with values from params. // -// Usage: +// Usage example: // // path := forge.ResolvePath("/api/v1/repos/{owner}/{repo}", forge.Params{"owner": "core", "repo": "go-forge"}) // _ = path diff --git a/pulls.go b/pulls.go index e9d52ee..75c3994 100644 --- a/pulls.go +++ b/pulls.go @@ -9,7 +9,7 @@ import ( // PullService handles pull request operations within a repository. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Pulls.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) @@ -26,6 +26,10 @@ func newPullService(c *Client) *PullService { } // Merge merges a pull request. Method is one of "merge", "rebase", "rebase-merge", "squash", "fast-forward-only", "manually-merged". +// +// Usage example: +// +// err := s.Merge(ctx, owner, repo, index, method) 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))) body := map[string]string{"Do": method} @@ -33,24 +37,40 @@ func (s *PullService) Merge(ctx context.Context, owner, repo string, index int64 } // Update updates a pull request branch with the base branch. +// +// Usage example: +// +// err := s.Update(ctx, owner, repo, index) 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))) return s.client.Post(ctx, path, nil, nil) } // ListReviews returns all reviews on a pull request. +// +// Usage example: +// +// _, err := s.ListReviews(ctx, owner, repo, index) 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))) return ListAll[types.PullReview](ctx, s.client, path, nil) } // IterReviews returns an iterator over all reviews on a pull request. +// +// Usage example: +// +// _ = s.IterReviews(ctx, owner, repo, index) 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))) return ListIter[types.PullReview](ctx, s.client, path, nil) } // SubmitReview creates a new review on a pull request. +// +// Usage example: +// +// _, err := s.SubmitReview(ctx, owner, repo, index, review) 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))) var out types.PullReview @@ -61,6 +81,10 @@ func (s *PullService) SubmitReview(ctx context.Context, owner, repo string, inde } // DismissReview dismisses a pull request review. +// +// Usage example: +// +// err := s.DismissReview(ctx, owner, repo, index, reviewID, msg) 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/{reviewID}/dismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "reviewID", int64String(reviewID))) body := map[string]string{"message": msg} @@ -68,6 +92,10 @@ func (s *PullService) DismissReview(ctx context.Context, owner, repo string, ind } // UndismissReview undismisses a pull request review. +// +// Usage example: +// +// err := s.UndismissReview(ctx, owner, repo, index, reviewID) func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/pulls/{index}/reviews/{reviewID}/undismissals", pathParams("owner", owner, "repo", repo, "index", int64String(index), "reviewID", int64String(reviewID))) return s.client.Post(ctx, path, nil, nil) diff --git a/releases.go b/releases.go index ed3a841..e118dce 100644 --- a/releases.go +++ b/releases.go @@ -9,7 +9,7 @@ import ( // ReleaseService handles release operations within a repository. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Releases.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) @@ -26,6 +26,10 @@ func newReleaseService(c *Client) *ReleaseService { } // GetByTag returns a release by its tag name. +// +// Usage example: +// +// _, err := s.GetByTag(ctx, owner, repo, tag) func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) (*types.Release, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/tags/{tag}", pathParams("owner", owner, "repo", repo, "tag", tag)) var out types.Release @@ -36,24 +40,40 @@ func (s *ReleaseService) GetByTag(ctx context.Context, owner, repo, tag string) } // DeleteByTag deletes a release by its tag name. +// +// Usage example: +// +// err := s.DeleteByTag(ctx, owner, repo, tag) 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)) return s.client.Delete(ctx, path) } // ListAssets returns all assets for a release. +// +// Usage example: +// +// _, err := s.ListAssets(ctx, owner, repo, releaseID) func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, releaseID int64) ([]types.Attachment, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/releases/{releaseID}/assets", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID))) return ListAll[types.Attachment](ctx, s.client, path, nil) } // IterAssets returns an iterator over all assets for a release. +// +// Usage example: +// +// _ = s.IterAssets(ctx, owner, repo, releaseID) 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/{releaseID}/assets", pathParams("owner", owner, "repo", repo, "releaseID", int64String(releaseID))) return ListIter[types.Attachment](ctx, s.client, path, nil) } // GetAsset returns a single asset for a release. +// +// Usage example: +// +// _, err := s.GetAsset(ctx, owner, repo, releaseID, assetID) 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))) var out types.Attachment @@ -64,6 +84,10 @@ func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, relea } // DeleteAsset deletes a single asset from a release. +// +// Usage example: +// +// err := s.DeleteAsset(ctx, owner, repo, releaseID, assetID) 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))) return s.client.Delete(ctx, path) diff --git a/repos.go b/repos.go index 1f31917..049b470 100644 --- a/repos.go +++ b/repos.go @@ -9,7 +9,7 @@ import ( // RepoService handles repository operations. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Repos.ListOrgRepos(ctx, "core") @@ -26,26 +26,46 @@ func newRepoService(c *Client) *RepoService { } // ListOrgRepos returns all repositories for an organisation. +// +// Usage example: +// +// _, err := s.ListOrgRepos(ctx, org) func (s *RepoService) ListOrgRepos(ctx context.Context, org string) ([]types.Repository, error) { return ListAll[types.Repository](ctx, s.client, "/api/v1/orgs/"+org+"/repos", nil) } // IterOrgRepos returns an iterator over all repositories for an organisation. +// +// Usage example: +// +// _ = s.IterOrgRepos(ctx, org) func (s *RepoService) IterOrgRepos(ctx context.Context, org string) iter.Seq2[types.Repository, error] { return ListIter[types.Repository](ctx, s.client, "/api/v1/orgs/"+org+"/repos", nil) } // ListUserRepos returns all repositories for the authenticated user. +// +// Usage example: +// +// _, err := s.ListUserRepos(ctx) func (s *RepoService) ListUserRepos(ctx context.Context) ([]types.Repository, error) { return ListAll[types.Repository](ctx, s.client, "/api/v1/user/repos", nil) } // IterUserRepos returns an iterator over all repositories for the authenticated user. +// +// Usage example: +// +// _ = s.IterUserRepos(ctx) func (s *RepoService) IterUserRepos(ctx context.Context) iter.Seq2[types.Repository, error] { return ListIter[types.Repository](ctx, s.client, "/api/v1/user/repos", nil) } // Fork forks a repository. If org is non-empty, forks into that organisation. +// +// Usage example: +// +// _, err := s.Fork(ctx, owner, repo, org) func (s *RepoService) Fork(ctx context.Context, owner, repo, org string) (*types.Repository, error) { body := map[string]string{} if org != "" { @@ -60,21 +80,37 @@ func (s *RepoService) Fork(ctx context.Context, owner, repo, org string) (*types } // Transfer initiates a repository transfer. +// +// Usage example: +// +// err := s.Transfer(ctx, owner, repo, opts) func (s *RepoService) Transfer(ctx context.Context, owner, repo string, opts map[string]any) error { return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/transfer", opts, nil) } // AcceptTransfer accepts a pending repository transfer. +// +// Usage example: +// +// err := s.AcceptTransfer(ctx, owner, repo) func (s *RepoService) AcceptTransfer(ctx context.Context, owner, repo string) error { return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/transfer/accept", nil, nil) } // RejectTransfer rejects a pending repository transfer. +// +// Usage example: +// +// err := s.RejectTransfer(ctx, owner, repo) func (s *RepoService) RejectTransfer(ctx context.Context, owner, repo string) error { return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/transfer/reject", nil, nil) } // MirrorSync triggers a mirror sync. +// +// Usage example: +// +// err := s.MirrorSync(ctx, owner, repo) func (s *RepoService) MirrorSync(ctx context.Context, owner, repo string) error { return s.client.Post(ctx, "/api/v1/repos/"+owner+"/"+repo+"/mirror-sync", nil, nil) } diff --git a/resource.go b/resource.go index f6fb521..5756890 100644 --- a/resource.go +++ b/resource.go @@ -10,7 +10,7 @@ import ( // Resource provides generic CRUD operations for a Forgejo API resource. // T is the resource type, C is the create options type, U is the update options type. // -// Usage: +// Usage example: // // r := forge.NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](client, "/api/v1/repos/{owner}/{repo}/issues/{index}") // _ = r @@ -24,7 +24,7 @@ type Resource[T any, C any, U any] struct { // The path should be the item path (e.g., /repos/{owner}/{repo}/issues/{index}). // The collection path is derived by stripping the last /{placeholder} segment. // -// Usage: +// Usage example: // // r := forge.NewResource[types.Issue, types.CreateIssueOption, types.EditIssueOption](client, "/api/v1/repos/{owner}/{repo}/issues/{index}") // _ = r @@ -42,21 +42,37 @@ func NewResource[T any, C any, U any](c *Client, path string) *Resource[T, C, U] } // List returns a single page of resources. +// +// Usage example: +// +// _, err := r.List(ctx, params, opts) func (r *Resource[T, C, U]) List(ctx context.Context, params Params, opts ListOptions) (*PagedResult[T], error) { return ListPage[T](ctx, r.client, ResolvePath(r.collection, params), nil, opts) } // ListAll returns all resources across all pages. +// +// Usage example: +// +// _, err := r.ListAll(ctx, params) func (r *Resource[T, C, U]) ListAll(ctx context.Context, params Params) ([]T, error) { return ListAll[T](ctx, r.client, ResolvePath(r.collection, params), nil) } // Iter returns an iterator over all resources across all pages. +// +// Usage example: +// +// _ = r.Iter(ctx, params) func (r *Resource[T, C, U]) Iter(ctx context.Context, params Params) iter.Seq2[T, error] { return ListIter[T](ctx, r.client, ResolvePath(r.collection, params), nil) } // Get returns a single resource by appending id to the path. +// +// Usage example: +// +// _, err := r.Get(ctx, params) func (r *Resource[T, C, U]) Get(ctx context.Context, params Params) (*T, error) { var out T if err := r.client.Get(ctx, ResolvePath(r.path, params), &out); err != nil { @@ -66,6 +82,10 @@ func (r *Resource[T, C, U]) Get(ctx context.Context, params Params) (*T, error) } // Create creates a new resource. +// +// Usage example: +// +// _, err := r.Create(ctx, params, body) func (r *Resource[T, C, U]) Create(ctx context.Context, params Params, body *C) (*T, error) { var out T if err := r.client.Post(ctx, ResolvePath(r.collection, params), body, &out); err != nil { @@ -75,6 +95,10 @@ func (r *Resource[T, C, U]) Create(ctx context.Context, params Params, body *C) } // Update modifies an existing resource. +// +// Usage example: +// +// _, err := r.Update(ctx, params, body) func (r *Resource[T, C, U]) Update(ctx context.Context, params Params, body *U) (*T, error) { var out T if err := r.client.Patch(ctx, ResolvePath(r.path, params), body, &out); err != nil { @@ -84,6 +108,10 @@ func (r *Resource[T, C, U]) Update(ctx context.Context, params Params, body *U) } // Delete removes a resource. +// +// Usage example: +// +// err := r.Delete(ctx, params) func (r *Resource[T, C, U]) Delete(ctx context.Context, params Params) error { return r.client.Delete(ctx, ResolvePath(r.path, params)) } diff --git a/teams.go b/teams.go index 862584b..cbd1c8c 100644 --- a/teams.go +++ b/teams.go @@ -9,7 +9,7 @@ import ( // TeamService handles team operations. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Teams.ListMembers(ctx, 42) @@ -26,60 +26,100 @@ func newTeamService(c *Client) *TeamService { } // ListMembers returns all members of a team. +// +// Usage example: +// +// _, err := s.ListMembers(ctx, teamID) func (s *TeamService) ListMembers(ctx context.Context, teamID int64) ([]types.User, error) { path := ResolvePath("/api/v1/teams/{teamID}/members", pathParams("teamID", int64String(teamID))) return ListAll[types.User](ctx, s.client, path, nil) } // IterMembers returns an iterator over all members of a team. +// +// Usage example: +// +// _ = s.IterMembers(ctx, teamID) func (s *TeamService) IterMembers(ctx context.Context, teamID int64) iter.Seq2[types.User, error] { path := ResolvePath("/api/v1/teams/{teamID}/members", pathParams("teamID", int64String(teamID))) return ListIter[types.User](ctx, s.client, path, nil) } // AddMember adds a user to a team. +// +// Usage example: +// +// err := s.AddMember(ctx, teamID, username) func (s *TeamService) AddMember(ctx context.Context, teamID int64, username string) error { path := ResolvePath("/api/v1/teams/{teamID}/members/{username}", pathParams("teamID", int64String(teamID), "username", username)) return s.client.Put(ctx, path, nil, nil) } // RemoveMember removes a user from a team. +// +// Usage example: +// +// err := s.RemoveMember(ctx, teamID, username) func (s *TeamService) RemoveMember(ctx context.Context, teamID int64, username string) error { path := ResolvePath("/api/v1/teams/{teamID}/members/{username}", pathParams("teamID", int64String(teamID), "username", username)) return s.client.Delete(ctx, path) } // ListRepos returns all repositories managed by a team. +// +// Usage example: +// +// _, err := s.ListRepos(ctx, teamID) func (s *TeamService) ListRepos(ctx context.Context, teamID int64) ([]types.Repository, error) { path := ResolvePath("/api/v1/teams/{teamID}/repos", pathParams("teamID", int64String(teamID))) return ListAll[types.Repository](ctx, s.client, path, nil) } // IterRepos returns an iterator over all repositories managed by a team. +// +// Usage example: +// +// _ = s.IterRepos(ctx, teamID) func (s *TeamService) IterRepos(ctx context.Context, teamID int64) iter.Seq2[types.Repository, error] { path := ResolvePath("/api/v1/teams/{teamID}/repos", pathParams("teamID", int64String(teamID))) return ListIter[types.Repository](ctx, s.client, path, nil) } // AddRepo adds a repository to a team. +// +// Usage example: +// +// err := s.AddRepo(ctx, teamID, org, repo) func (s *TeamService) AddRepo(ctx context.Context, teamID int64, org, repo string) error { path := ResolvePath("/api/v1/teams/{teamID}/repos/{org}/{repo}", pathParams("teamID", int64String(teamID), "org", org, "repo", repo)) return s.client.Put(ctx, path, nil, nil) } // RemoveRepo removes a repository from a team. +// +// Usage example: +// +// err := s.RemoveRepo(ctx, teamID, org, repo) func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error { path := ResolvePath("/api/v1/teams/{teamID}/repos/{org}/{repo}", pathParams("teamID", int64String(teamID), "org", org, "repo", repo)) return s.client.Delete(ctx, path) } // ListOrgTeams returns all teams in an organisation. +// +// Usage example: +// +// _, err := s.ListOrgTeams(ctx, org) func (s *TeamService) ListOrgTeams(ctx context.Context, org string) ([]types.Team, error) { path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org)) return ListAll[types.Team](ctx, s.client, path, nil) } // IterOrgTeams returns an iterator over all teams in an organisation. +// +// Usage example: +// +// _ = s.IterOrgTeams(ctx, org) func (s *TeamService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[types.Team, error] { path := ResolvePath("/api/v1/orgs/{org}/teams", pathParams("org", org)) return ListIter[types.Team](ctx, s.client, path, nil) diff --git a/users.go b/users.go index 0918d04..4bdd697 100644 --- a/users.go +++ b/users.go @@ -9,7 +9,7 @@ import ( // UserService handles user operations. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Users.GetCurrent(ctx) @@ -26,6 +26,10 @@ func newUserService(c *Client) *UserService { } // GetCurrent returns the authenticated user. +// +// Usage example: +// +// _, err := s.GetCurrent(ctx) func (s *UserService) GetCurrent(ctx context.Context) (*types.User, error) { var out types.User if err := s.client.Get(ctx, "/api/v1/user", &out); err != nil { @@ -35,60 +39,100 @@ func (s *UserService) GetCurrent(ctx context.Context) (*types.User, error) { } // ListFollowers returns all followers of a user. +// +// Usage example: +// +// _, err := s.ListFollowers(ctx, username) func (s *UserService) ListFollowers(ctx context.Context, username string) ([]types.User, error) { path := ResolvePath("/api/v1/users/{username}/followers", pathParams("username", username)) return ListAll[types.User](ctx, s.client, path, nil) } // IterFollowers returns an iterator over all followers of a user. +// +// Usage example: +// +// _ = s.IterFollowers(ctx, username) func (s *UserService) IterFollowers(ctx context.Context, username string) iter.Seq2[types.User, error] { path := ResolvePath("/api/v1/users/{username}/followers", pathParams("username", username)) return ListIter[types.User](ctx, s.client, path, nil) } // ListFollowing returns all users that a user is following. +// +// Usage example: +// +// _, err := s.ListFollowing(ctx, username) func (s *UserService) ListFollowing(ctx context.Context, username string) ([]types.User, error) { path := ResolvePath("/api/v1/users/{username}/following", pathParams("username", username)) return ListAll[types.User](ctx, s.client, path, nil) } // IterFollowing returns an iterator over all users that a user is following. +// +// Usage example: +// +// _ = s.IterFollowing(ctx, username) func (s *UserService) IterFollowing(ctx context.Context, username string) iter.Seq2[types.User, error] { path := ResolvePath("/api/v1/users/{username}/following", pathParams("username", username)) return ListIter[types.User](ctx, s.client, path, nil) } // Follow follows a user as the authenticated user. +// +// Usage example: +// +// err := s.Follow(ctx, username) func (s *UserService) Follow(ctx context.Context, username string) error { path := ResolvePath("/api/v1/user/following/{username}", pathParams("username", username)) return s.client.Put(ctx, path, nil, nil) } // Unfollow unfollows a user as the authenticated user. +// +// Usage example: +// +// err := s.Unfollow(ctx, username) func (s *UserService) Unfollow(ctx context.Context, username string) error { path := ResolvePath("/api/v1/user/following/{username}", pathParams("username", username)) return s.client.Delete(ctx, path) } // ListStarred returns all repositories starred by a user. +// +// Usage example: +// +// _, err := s.ListStarred(ctx, username) func (s *UserService) ListStarred(ctx context.Context, username string) ([]types.Repository, error) { path := ResolvePath("/api/v1/users/{username}/starred", pathParams("username", username)) return ListAll[types.Repository](ctx, s.client, path, nil) } // IterStarred returns an iterator over all repositories starred by a user. +// +// Usage example: +// +// _ = s.IterStarred(ctx, username) func (s *UserService) IterStarred(ctx context.Context, username string) iter.Seq2[types.Repository, error] { path := ResolvePath("/api/v1/users/{username}/starred", pathParams("username", username)) return ListIter[types.Repository](ctx, s.client, path, nil) } // Star stars a repository as the authenticated user. +// +// Usage example: +// +// err := s.Star(ctx, owner, repo) func (s *UserService) Star(ctx context.Context, owner, repo string) error { path := ResolvePath("/api/v1/user/starred/{owner}/{repo}", pathParams("owner", owner, "repo", repo)) return s.client.Put(ctx, path, nil, nil) } // Unstar unstars a repository as the authenticated user. +// +// Usage example: +// +// err := s.Unstar(ctx, owner, repo) func (s *UserService) Unstar(ctx context.Context, owner, repo string) error { path := ResolvePath("/api/v1/user/starred/{owner}/{repo}", pathParams("owner", owner, "repo", repo)) return s.client.Delete(ctx, path) diff --git a/webhooks.go b/webhooks.go index 1cc8894..221ca09 100644 --- a/webhooks.go +++ b/webhooks.go @@ -10,7 +10,7 @@ import ( // WebhookService handles webhook (hook) operations within a repository. // Embeds Resource for standard CRUD on /api/v1/repos/{owner}/{repo}/hooks/{id}. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Webhooks.ListAll(ctx, forge.Params{"owner": "core", "repo": "go-forge"}) @@ -27,18 +27,30 @@ func newWebhookService(c *Client) *WebhookService { } // TestHook triggers a test delivery for a webhook. +// +// Usage example: +// +// err := s.TestHook(ctx, owner, repo, id) func (s *WebhookService) TestHook(ctx context.Context, owner, repo string, id int64) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/hooks/{id}/tests", pathParams("owner", owner, "repo", repo, "id", int64String(id))) return s.client.Post(ctx, path, nil, nil) } // ListOrgHooks returns all webhooks for an organisation. +// +// Usage example: +// +// _, err := s.ListOrgHooks(ctx, org) func (s *WebhookService) ListOrgHooks(ctx context.Context, org string) ([]types.Hook, error) { path := ResolvePath("/api/v1/orgs/{org}/hooks", pathParams("org", org)) return ListAll[types.Hook](ctx, s.client, path, nil) } // IterOrgHooks returns an iterator over all webhooks for an organisation. +// +// Usage example: +// +// _ = s.IterOrgHooks(ctx, org) func (s *WebhookService) IterOrgHooks(ctx context.Context, org string) iter.Seq2[types.Hook, error] { path := ResolvePath("/api/v1/orgs/{org}/hooks", pathParams("org", org)) return ListIter[types.Hook](ctx, s.client, path, nil) diff --git a/wiki.go b/wiki.go index 5a5e039..f67173f 100644 --- a/wiki.go +++ b/wiki.go @@ -9,7 +9,7 @@ import ( // WikiService handles wiki page operations for a repository. // No Resource embedding — custom endpoints for wiki CRUD. // -// Usage: +// Usage example: // // f := forge.NewForge("https://forge.lthn.ai", "token") // _, err := f.Wiki.ListPages(ctx, "core", "go-forge") @@ -22,6 +22,10 @@ func newWikiService(c *Client) *WikiService { } // ListPages returns all wiki page metadata for a repository. +// +// Usage example: +// +// _, err := s.ListPages(ctx, owner, repo) func (s *WikiService) ListPages(ctx context.Context, owner, repo string) ([]types.WikiPageMetaData, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/pages", pathParams("owner", owner, "repo", repo)) var out []types.WikiPageMetaData @@ -32,6 +36,10 @@ func (s *WikiService) ListPages(ctx context.Context, owner, repo string) ([]type } // GetPage returns a single wiki page by name. +// +// Usage example: +// +// _, err := s.GetPage(ctx, owner, repo, pageName) func (s *WikiService) GetPage(ctx context.Context, owner, repo, pageName string) (*types.WikiPage, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/page/{pageName}", pathParams("owner", owner, "repo", repo, "pageName", pageName)) var out types.WikiPage @@ -42,6 +50,10 @@ func (s *WikiService) GetPage(ctx context.Context, owner, repo, pageName string) } // CreatePage creates a new wiki page. +// +// Usage example: +// +// _, err := s.CreatePage(ctx, owner, repo, opts) func (s *WikiService) CreatePage(ctx context.Context, owner, repo string, opts *types.CreateWikiPageOptions) (*types.WikiPage, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/new", pathParams("owner", owner, "repo", repo)) var out types.WikiPage @@ -52,6 +64,10 @@ func (s *WikiService) CreatePage(ctx context.Context, owner, repo string, opts * } // EditPage updates an existing wiki page. +// +// Usage example: +// +// _, err := s.EditPage(ctx, owner, repo, pageName, opts) func (s *WikiService) EditPage(ctx context.Context, owner, repo, pageName string, opts *types.CreateWikiPageOptions) (*types.WikiPage, error) { path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/page/{pageName}", pathParams("owner", owner, "repo", repo, "pageName", pageName)) var out types.WikiPage @@ -62,6 +78,10 @@ func (s *WikiService) EditPage(ctx context.Context, owner, repo, pageName string } // DeletePage removes a wiki page. +// +// Usage example: +// +// err := s.DeletePage(ctx, owner, repo, pageName) func (s *WikiService) DeletePage(ctx context.Context, owner, repo, pageName string) error { path := ResolvePath("/api/v1/repos/{owner}/{repo}/wiki/page/{pageName}", pathParams("owner", owner, "repo", repo, "pageName", pageName)) return s.client.Delete(ctx, path) -- 2.45.3 From d73e3148c590b1899255afe35998db929fe8d561 Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 27 Mar 2026 19:38:14 +0000 Subject: [PATCH 4/5] docs(specs): populate package RFCs from source Co-Authored-By: Virgil --- specs/cmd/forgegen.md | 26 +++++ specs/forge.md | 223 ++++++++++++++++++++++++++++++++++++++ specs/types.md | 245 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 494 insertions(+) create mode 100644 specs/cmd/forgegen.md create mode 100644 specs/forge.md create mode 100644 specs/types.md diff --git a/specs/cmd/forgegen.md b/specs/cmd/forgegen.md new file mode 100644 index 0000000..48dce18 --- /dev/null +++ b/specs/cmd/forgegen.md @@ -0,0 +1,26 @@ +# main + +**Import:** `dappco.re/go/core/forge/cmd/forgegen` + +**Files:** 4 + +## Types + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| struct | CRUDPair | `type CRUDPair struct` | CRUDPair groups a base type with its corresponding Create and Edit option types. | +| struct | GoField | `type GoField struct` | GoField is the intermediate representation for a single struct field. | +| struct | GoType | `type GoType struct` | GoType is the intermediate representation for a Go type to be generated. | +| struct | SchemaDefinition | `type SchemaDefinition struct` | SchemaDefinition represents a single type definition in the swagger spec. | +| struct | SchemaProperty | `type SchemaProperty struct` | SchemaProperty represents a single property within a schema definition. | +| struct | Spec | `type Spec struct` | Spec represents a Swagger 2.0 specification document. | +| struct | SpecInfo | `type SpecInfo struct` | SpecInfo holds metadata about the API specification. | + +## Functions + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| function | DetectCRUDPairs | `func DetectCRUDPairs(spec *Spec) []CRUDPair` | DetectCRUDPairs finds Create*Option / Edit*Option pairs in the swagger definitions and maps them back to the base type name. | +| function | ExtractTypes | `func ExtractTypes(spec *Spec) map[string]*GoType` | ExtractTypes converts all swagger definitions into Go type intermediate representations. | +| function | Generate | `func Generate(types map[string]*GoType, pairs []CRUDPair, outDir string) error` | Generate writes Go source files for the extracted types, grouped by logical domain. | +| function | LoadSpec | `func LoadSpec(path string) (*Spec, error)` | LoadSpec reads and parses a Swagger 2.0 JSON file from the given path. | diff --git a/specs/forge.md b/specs/forge.md new file mode 100644 index 0000000..baadf68 --- /dev/null +++ b/specs/forge.md @@ -0,0 +1,223 @@ +# forge + +**Import:** `dappco.re/go/core/forge` + +**Files:** 27 + +## Types + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| struct | APIError | `type APIError struct` | APIError represents an error response from the Forgejo API. | +| struct | 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. | +| struct | 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. | +| struct | BranchService | `type BranchService struct` | BranchService handles branch operations within a repository. | +| struct | Client | `type Client struct` | Client is a low-level HTTP client for the Forgejo API. | +| struct | CommitService | `type CommitService struct` | CommitService handles commit-related operations such as commit statuses and git notes. No Resource embedding — collection and item commit paths differ, and the remaining endpoints are heterogeneous across status and note paths. | +| struct | ContentService | `type ContentService struct` | ContentService handles file read/write operations via the Forgejo API. No Resource embedding — paths vary by operation. | +| struct | Forge | `type Forge struct` | Forge is the top-level client for the Forgejo API. | +| struct | IssueService | `type IssueService struct` | IssueService handles issue operations within a repository. | +| struct | LabelService | `type LabelService struct` | LabelService handles repository labels, organisation labels, and issue labels. No Resource embedding — paths are heterogeneous. | +| struct | ListOptions | `type ListOptions struct` | ListOptions controls pagination. | +| struct | MilestoneService | `type MilestoneService struct` | MilestoneService handles repository milestones. | +| struct | MiscService | `type MiscService struct` | MiscService handles miscellaneous Forgejo API endpoints such as markdown rendering, licence templates, gitignore templates, and server metadata. No Resource embedding — heterogeneous read-only endpoints. | +| struct | NotificationService | `type NotificationService struct` | NotificationService handles notification operations via the Forgejo API. No Resource embedding — varied endpoint shapes. | +| type | Option | `type Option func(*Client)` | Option configures the Client. | +| struct | OrgService | `type OrgService struct` | OrgService handles organisation operations. | +| struct | PackageService | `type PackageService struct` | PackageService handles package registry operations via the Forgejo API. No Resource embedding — paths vary by operation. | +| struct | PagedResult | `type PagedResult[T any] struct` | PagedResult holds a single page of results with metadata. | +| type | Params | `type Params map[string]string` | Params maps path variable names to values. Example: Params{"owner": "core", "repo": "go-forge"} | +| struct | PullService | `type PullService struct` | PullService handles pull request operations within a repository. | +| struct | RateLimit | `type RateLimit struct` | RateLimit represents the rate limit information from the Forgejo API. | +| struct | ReleaseService | `type ReleaseService struct` | ReleaseService handles release operations within a repository. | +| struct | RepoService | `type RepoService struct` | RepoService handles repository operations. | +| struct | 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. | +| struct | TeamService | `type TeamService struct` | TeamService handles team operations. | +| struct | UserService | `type UserService struct` | UserService handles user operations. | +| struct | 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}. | +| struct | WikiService | `type WikiService struct` | WikiService handles wiki page operations for a repository. No Resource embedding — custom endpoints for wiki CRUD. | + +## Functions + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| method | APIError.Error | `func (e *APIError) Error() string` | Error returns the API error as a diagnostic string. | +| method | ActionsService.CreateRepoSecret | `func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error` | CreateRepoSecret creates or updates a secret in a repository. Forgejo expects a PUT with {"data": "secret-value"} body. | +| method | ActionsService.CreateRepoVariable | `func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error` | CreateRepoVariable creates a new action variable in a repository. Forgejo expects a POST with {"value": "var-value"} body. | +| method | ActionsService.DeleteRepoSecret | `func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error` | DeleteRepoSecret removes a secret from a repository. | +| method | ActionsService.DeleteRepoVariable | `func (s *ActionsService) DeleteRepoVariable(ctx context.Context, owner, repo, name string) error` | DeleteRepoVariable removes an action variable from a repository. | +| method | ActionsService.DispatchWorkflow | `func (s *ActionsService) DispatchWorkflow(ctx context.Context, owner, repo, workflow string, opts map[string]any) error` | DispatchWorkflow triggers a workflow run. | +| 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. | +| 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. | +| 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. | +| method | ActionsService.IterRepoVariables | `func (s *ActionsService) IterRepoVariables(ctx context.Context, owner, repo string) iter.Seq2[types.ActionVariable, error]` | IterRepoVariables returns an iterator over all action variables for a repository. | +| method | ActionsService.ListOrgSecrets | `func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string) ([]types.Secret, error)` | ListOrgSecrets returns all secrets for an organisation. | +| method | ActionsService.ListOrgVariables | `func (s *ActionsService) ListOrgVariables(ctx context.Context, org string) ([]types.ActionVariable, error)` | ListOrgVariables returns all action variables for an organisation. | +| method | ActionsService.ListRepoSecrets | `func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string) ([]types.Secret, error)` | ListRepoSecrets returns all secrets for a repository. | +| method | ActionsService.ListRepoVariables | `func (s *ActionsService) ListRepoVariables(ctx context.Context, owner, repo string) ([]types.ActionVariable, error)` | ListRepoVariables returns all action variables for a repository. | +| method | AdminService.AdoptRepo | `func (s *AdminService) AdoptRepo(ctx context.Context, owner, repo string) error` | AdoptRepo adopts an unadopted repository (admin only). | +| method | AdminService.CreateUser | `func (s *AdminService) CreateUser(ctx context.Context, opts *types.CreateUserOption) (*types.User, error)` | CreateUser creates a new user (admin only). | +| method | AdminService.DeleteUser | `func (s *AdminService) DeleteUser(ctx context.Context, username string) error` | DeleteUser deletes a user (admin only). | +| method | AdminService.EditUser | `func (s *AdminService) EditUser(ctx context.Context, username string, opts map[string]any) error` | EditUser edits an existing user (admin only). | +| method | AdminService.GenerateRunnerToken | `func (s *AdminService) GenerateRunnerToken(ctx context.Context) (string, error)` | GenerateRunnerToken generates an actions runner registration token. | +| 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). | +| method | AdminService.IterOrgs | `func (s *AdminService) IterOrgs(ctx context.Context) iter.Seq2[types.Organization, error]` | IterOrgs returns an iterator over all organisations (admin only). | +| method | AdminService.IterUsers | `func (s *AdminService) IterUsers(ctx context.Context) iter.Seq2[types.User, error]` | IterUsers returns an iterator over all users (admin only). | +| method | AdminService.ListCron | `func (s *AdminService) ListCron(ctx context.Context) ([]types.Cron, error)` | ListCron returns all cron tasks (admin only). | +| method | AdminService.ListOrgs | `func (s *AdminService) ListOrgs(ctx context.Context) ([]types.Organization, error)` | ListOrgs returns all organisations (admin only). | +| method | AdminService.ListUsers | `func (s *AdminService) ListUsers(ctx context.Context) ([]types.User, error)` | ListUsers returns all users (admin only). | +| method | AdminService.RenameUser | `func (s *AdminService) RenameUser(ctx context.Context, username, newName string) error` | RenameUser renames a user (admin only). | +| method | AdminService.RunCron | `func (s *AdminService) RunCron(ctx context.Context, task string) error` | RunCron runs a cron task by name (admin only). | +| 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. | +| method | BranchService.DeleteBranchProtection | `func (s *BranchService) DeleteBranchProtection(ctx context.Context, owner, repo, name string) error` | DeleteBranchProtection deletes a branch protection rule. | +| method | BranchService.EditBranchProtection | `func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, name string, opts *types.EditBranchProtectionOption) (*types.BranchProtection, error)` | EditBranchProtection updates an existing branch protection rule. | +| method | BranchService.GetBranchProtection | `func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, name string) (*types.BranchProtection, error)` | GetBranchProtection returns a single branch protection by name. | +| method | BranchService.IterBranchProtections | `func (s *BranchService) IterBranchProtections(ctx context.Context, owner, repo string) iter.Seq2[types.BranchProtection, error]` | IterBranchProtections returns an iterator over all branch protections for a repository. | +| method | BranchService.ListBranchProtections | `func (s *BranchService) ListBranchProtections(ctx context.Context, owner, repo string) ([]types.BranchProtection, error)` | ListBranchProtections returns all branch protections for a repository. | +| method | Client.Delete | `func (c *Client) Delete(ctx context.Context, path string) error` | Delete performs a DELETE request. | +| method | Client.DeleteWithBody | `func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) error` | DeleteWithBody performs a DELETE request with a JSON body. | +| method | Client.Get | `func (c *Client) Get(ctx context.Context, path string, out any) error` | Get performs a GET request. | +| 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. | +| method | Client.Patch | `func (c *Client) Patch(ctx context.Context, path string, body, out any) error` | Patch performs a PATCH request. | +| method | Client.Post | `func (c *Client) Post(ctx context.Context, path string, body, out any) error` | Post performs a POST request. | +| 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. | +| method | Client.Put | `func (c *Client) Put(ctx context.Context, path string, body, out any) error` | Put performs a PUT request. | +| method | Client.RateLimit | `func (c *Client) RateLimit() RateLimit` | RateLimit returns the last known rate limit information. | +| 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. | +| method | CommitService.Get | `func (s *CommitService) Get(ctx context.Context, params Params) (*types.Commit, error)` | Get returns a single commit by SHA or ref. | +| 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). | +| method | CommitService.GetNote | `func (s *CommitService) GetNote(ctx context.Context, owner, repo, sha string) (*types.Note, error)` | GetNote returns the git note for a given commit SHA. | +| method | CommitService.Iter | `func (s *CommitService) Iter(ctx context.Context, params Params) iter.Seq2[types.Commit, error]` | Iter returns an iterator over all commits for a repository. | +| method | CommitService.List | `func (s *CommitService) List(ctx context.Context, params Params, opts ListOptions) (*PagedResult[types.Commit], error)` | List returns a single page of commits for a repository. | +| method | CommitService.ListAll | `func (s *CommitService) ListAll(ctx context.Context, params Params) ([]types.Commit, error)` | ListAll returns all commits for a repository. | +| method | CommitService.ListStatuses | `func (s *CommitService) ListStatuses(ctx context.Context, owner, repo, ref string) ([]types.CommitStatus, error)` | ListStatuses returns all commit statuses for a given ref. | +| method | ContentService.CreateFile | `func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath string, opts *types.CreateFileOptions) (*types.FileResponse, error)` | CreateFile creates a new file in a repository. | +| method | ContentService.DeleteFile | `func (s *ContentService) DeleteFile(ctx context.Context, owner, repo, filepath string, opts *types.DeleteFileOptions) error` | DeleteFile deletes a file from a repository. Uses DELETE with a JSON body. | +| method | ContentService.GetFile | `func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath string) (*types.ContentsResponse, error)` | GetFile returns metadata and content for a file in a repository. | +| method | ContentService.GetRawFile | `func (s *ContentService) GetRawFile(ctx context.Context, owner, repo, filepath string) ([]byte, error)` | GetRawFile returns the raw file content as bytes. | +| 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. | +| method | Forge.Client | `func (f *Forge) Client() *Client` | Client returns the underlying HTTP client. | +| function | IsConflict | `func IsConflict(err error) bool` | IsConflict returns true if the error is a 409 response. | +| function | IsForbidden | `func IsForbidden(err error) bool` | IsForbidden returns true if the error is a 403 response. | +| function | IsNotFound | `func IsNotFound(err error) bool` | IsNotFound returns true if the error is a 404 response. | +| method | IssueService.AddLabels | `func (s *IssueService) AddLabels(ctx context.Context, owner, repo string, index int64, labelIDs []int64) error` | AddLabels adds labels to an issue. | +| 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. | +| 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. | +| 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. | +| 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. | +| 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. | +| method | IssueService.Pin | `func (s *IssueService) Pin(ctx context.Context, owner, repo string, index int64) error` | Pin pins an issue. | +| 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. | +| 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. | +| method | IssueService.StartStopwatch | `func (s *IssueService) StartStopwatch(ctx context.Context, owner, repo string, index int64) error` | StartStopwatch starts the stopwatch on an issue. | +| method | IssueService.StopStopwatch | `func (s *IssueService) StopStopwatch(ctx context.Context, owner, repo string, index int64) error` | StopStopwatch stops the stopwatch on an issue. | +| method | IssueService.Unpin | `func (s *IssueService) Unpin(ctx context.Context, owner, repo string, index int64) error` | Unpin unpins an issue. | +| 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. | +| 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. | +| method | LabelService.DeleteRepoLabel | `func (s *LabelService) DeleteRepoLabel(ctx context.Context, owner, repo string, id int64) error` | DeleteRepoLabel deletes a label from a repository. | +| method | LabelService.EditRepoLabel | `func (s *LabelService) EditRepoLabel(ctx context.Context, owner, repo string, id int64, opts *types.EditLabelOption) (*types.Label, error)` | EditRepoLabel updates an existing label in a repository. | +| method | LabelService.GetRepoLabel | `func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id int64) (*types.Label, error)` | GetRepoLabel returns a single label by ID. | +| method | LabelService.IterOrgLabels | `func (s *LabelService) IterOrgLabels(ctx context.Context, org string) iter.Seq2[types.Label, error]` | IterOrgLabels returns an iterator over all labels for an organisation. | +| method | LabelService.IterRepoLabels | `func (s *LabelService) IterRepoLabels(ctx context.Context, owner, repo string) iter.Seq2[types.Label, error]` | IterRepoLabels returns an iterator over all labels for a repository. | +| method | LabelService.ListOrgLabels | `func (s *LabelService) ListOrgLabels(ctx context.Context, org string) ([]types.Label, error)` | ListOrgLabels returns all labels for an organisation. | +| method | LabelService.ListRepoLabels | `func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) ([]types.Label, error)` | ListRepoLabels returns all labels for a repository. | +| function | ListAll | `func ListAll[T any](ctx context.Context, c *Client, path string, query map[string]string) ([]T, error)` | ListAll fetches all pages of results. | +| function | ListIter | `func ListIter[T any](ctx context.Context, c *Client, path string, query map[string]string) iter.Seq2[T, error]` | ListIter returns an iterator over all resources across all pages. | +| function | ListPage | `func ListPage[T any](ctx context.Context, c *Client, path string, query map[string]string, opts ListOptions) (*PagedResult[T], error)` | ListPage fetches a single page of results. Extra query params can be passed via the query map. | +| method | MilestoneService.Create | `func (s *MilestoneService) Create(ctx context.Context, owner, repo string, opts *types.CreateMilestoneOption) (*types.Milestone, error)` | Create creates a new milestone. | +| 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. | +| method | MilestoneService.ListAll | `func (s *MilestoneService) ListAll(ctx context.Context, params Params) ([]types.Milestone, error)` | ListAll returns all milestones for a repository. | +| method | MiscService.GetGitignoreTemplate | `func (s *MiscService) GetGitignoreTemplate(ctx context.Context, name string) (*types.GitignoreTemplateInfo, error)` | GetGitignoreTemplate returns a single gitignore template by name. | +| method | MiscService.GetLicense | `func (s *MiscService) GetLicense(ctx context.Context, name string) (*types.LicenseTemplateInfo, error)` | GetLicense returns a single licence template by name. | +| method | MiscService.GetNodeInfo | `func (s *MiscService) GetNodeInfo(ctx context.Context) (*types.NodeInfo, error)` | GetNodeInfo returns the NodeInfo metadata for the Forgejo instance. | +| method | MiscService.GetVersion | `func (s *MiscService) GetVersion(ctx context.Context) (*types.ServerVersion, error)` | GetVersion returns the server version. | +| method | MiscService.ListGitignoreTemplates | `func (s *MiscService) ListGitignoreTemplates(ctx context.Context) ([]string, error)` | ListGitignoreTemplates returns all available gitignore template names. | +| method | MiscService.ListLicenses | `func (s *MiscService) ListLicenses(ctx context.Context) ([]types.LicensesTemplateListEntry, error)` | ListLicenses returns all available licence templates. | +| 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. | +| function | NewClient | `func NewClient(url, token string, opts ...Option) *Client` | NewClient creates a new Forgejo API client. | +| function | NewForge | `func NewForge(url, token string, opts ...Option) *Forge` | NewForge creates a new Forge client. | +| function | NewForgeFromConfig | `func NewForgeFromConfig(flagURL, flagToken string, opts ...Option) (*Forge, error)` | NewForgeFromConfig creates a new Forge client using resolved configuration. It returns an error if no API token is available from flags or environment. | +| function | NewResource | `func NewResource[T any, C any, U any](c *Client, path string) *Resource[T, C, U]` | NewResource creates a new Resource for the given path pattern. The path should be the item path (e.g., /repos/{owner}/{repo}/issues/{index}). The collection path is derived by stripping the last /{placeholder} segment. | +| method | NotificationService.GetThread | `func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.NotificationThread, error)` | GetThread returns a single notification thread by ID. | +| 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. | +| 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. | +| method | NotificationService.List | `func (s *NotificationService) List(ctx context.Context) ([]types.NotificationThread, error)` | List returns all notifications for the authenticated user. | +| method | NotificationService.ListRepo | `func (s *NotificationService) ListRepo(ctx context.Context, owner, repo string) ([]types.NotificationThread, error)` | ListRepo returns all notifications for a specific repository. | +| method | NotificationService.MarkRead | `func (s *NotificationService) MarkRead(ctx context.Context) error` | MarkRead marks all notifications as read. | +| method | NotificationService.MarkThreadRead | `func (s *NotificationService) MarkThreadRead(ctx context.Context, id int64) error` | MarkThreadRead marks a single notification thread as read. | +| method | OrgService.AddMember | `func (s *OrgService) AddMember(ctx context.Context, org, username string) error` | AddMember adds a user to an organisation. | +| method | OrgService.IterMembers | `func (s *OrgService) IterMembers(ctx context.Context, org string) iter.Seq2[types.User, error]` | IterMembers returns an iterator over all members of an organisation. | +| method | OrgService.IterMyOrgs | `func (s *OrgService) IterMyOrgs(ctx context.Context) iter.Seq2[types.Organization, error]` | IterMyOrgs returns an iterator over all organisations for the authenticated user. | +| method | OrgService.IterUserOrgs | `func (s *OrgService) IterUserOrgs(ctx context.Context, username string) iter.Seq2[types.Organization, error]` | IterUserOrgs returns an iterator over all organisations for a user. | +| method | OrgService.ListMembers | `func (s *OrgService) ListMembers(ctx context.Context, org string) ([]types.User, error)` | ListMembers returns all members of an organisation. | +| method | OrgService.ListMyOrgs | `func (s *OrgService) ListMyOrgs(ctx context.Context) ([]types.Organization, error)` | ListMyOrgs returns all organisations for the authenticated user. | +| method | OrgService.ListUserOrgs | `func (s *OrgService) ListUserOrgs(ctx context.Context, username string) ([]types.Organization, error)` | ListUserOrgs returns all organisations for a user. | +| method | OrgService.RemoveMember | `func (s *OrgService) RemoveMember(ctx context.Context, org, username string) error` | RemoveMember removes a user from an organisation. | +| method | PackageService.Delete | `func (s *PackageService) Delete(ctx context.Context, owner, pkgType, name, version string) error` | Delete removes a package by owner, type, name, and version. | +| method | PackageService.Get | `func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version string) (*types.Package, error)` | Get returns a single package by owner, type, name, and version. | +| method | PackageService.Iter | `func (s *PackageService) Iter(ctx context.Context, owner string) iter.Seq2[types.Package, error]` | Iter returns an iterator over all packages for a given owner. | +| method | PackageService.IterFiles | `func (s *PackageService) IterFiles(ctx context.Context, owner, pkgType, name, version string) iter.Seq2[types.PackageFile, error]` | IterFiles returns an iterator over all files for a specific package version. | +| method | PackageService.List | `func (s *PackageService) List(ctx context.Context, owner string) ([]types.Package, error)` | List returns all packages for a given owner. | +| 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. | +| 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. | +| 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. | +| 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. | +| 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". | +| 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. | +| method | PullService.UndismissReview | `func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error` | UndismissReview undismisses a pull request review. | +| 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. | +| 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. | +| method | ReleaseService.DeleteByTag | `func (s *ReleaseService) DeleteByTag(ctx context.Context, owner, repo, tag string) error` | DeleteByTag deletes a release by its tag name. | +| 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. | +| 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. | +| 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. | +| 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. | +| method | RepoService.AcceptTransfer | `func (s *RepoService) AcceptTransfer(ctx context.Context, owner, repo string) error` | AcceptTransfer accepts a pending repository transfer. | +| 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. | +| 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. | +| 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. | +| method | RepoService.ListOrgRepos | `func (s *RepoService) ListOrgRepos(ctx context.Context, org string) ([]types.Repository, error)` | ListOrgRepos returns all repositories for an organisation. | +| method | RepoService.ListUserRepos | `func (s *RepoService) ListUserRepos(ctx context.Context) ([]types.Repository, error)` | ListUserRepos returns all repositories for the authenticated user. | +| method | RepoService.MirrorSync | `func (s *RepoService) MirrorSync(ctx context.Context, owner, repo string) error` | MirrorSync triggers a mirror sync. | +| method | RepoService.RejectTransfer | `func (s *RepoService) RejectTransfer(ctx context.Context, owner, repo string) error` | RejectTransfer rejects a pending repository transfer. | +| method | RepoService.Transfer | `func (s *RepoService) Transfer(ctx context.Context, owner, repo string, opts map[string]any) error` | Transfer initiates a repository transfer. | +| function | ResolveConfig | `func ResolveConfig(flagURL, flagToken string) (url, token string, err error)` | ResolveConfig resolves the Forgejo URL and API token from flags, environment variables, and built-in defaults. Priority order: flags > env > defaults. | +| function | ResolvePath | `func ResolvePath(path string, params Params) string` | ResolvePath substitutes {placeholders} in path with values from params. | +| method | Resource.Create | `func (r *Resource[T, C, U]) Create(ctx context.Context, params Params, body *C) (*T, error)` | Create creates a new resource. | +| method | Resource.Delete | `func (r *Resource[T, C, U]) Delete(ctx context.Context, params Params) error` | Delete removes a resource. | +| method | Resource.Get | `func (r *Resource[T, C, U]) Get(ctx context.Context, params Params) (*T, error)` | Get returns a single resource by appending id to the path. | +| method | Resource.Iter | `func (r *Resource[T, C, U]) Iter(ctx context.Context, params Params) iter.Seq2[T, error]` | Iter returns an iterator over all resources across all pages. | +| method | Resource.List | `func (r *Resource[T, C, U]) List(ctx context.Context, params Params, opts ListOptions) (*PagedResult[T], error)` | List returns a single page of resources. | +| method | Resource.ListAll | `func (r *Resource[T, C, U]) ListAll(ctx context.Context, params Params) ([]T, error)` | ListAll returns all resources across all pages. | +| method | Resource.Update | `func (r *Resource[T, C, U]) Update(ctx context.Context, params Params, body *U) (*T, error)` | Update modifies an existing resource. | +| method | TeamService.AddMember | `func (s *TeamService) AddMember(ctx context.Context, teamID int64, username string) error` | AddMember adds a user to a team. | +| method | TeamService.AddRepo | `func (s *TeamService) AddRepo(ctx context.Context, teamID int64, org, repo string) error` | AddRepo adds a repository to a team. | +| method | TeamService.IterMembers | `func (s *TeamService) IterMembers(ctx context.Context, teamID int64) iter.Seq2[types.User, error]` | IterMembers returns an iterator over all members of a team. | +| method | TeamService.IterOrgTeams | `func (s *TeamService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[types.Team, error]` | IterOrgTeams returns an iterator over all teams in an organisation. | +| method | TeamService.IterRepos | `func (s *TeamService) IterRepos(ctx context.Context, teamID int64) iter.Seq2[types.Repository, error]` | IterRepos returns an iterator over all repositories managed by a team. | +| method | TeamService.ListMembers | `func (s *TeamService) ListMembers(ctx context.Context, teamID int64) ([]types.User, error)` | ListMembers returns all members of a team. | +| method | TeamService.ListOrgTeams | `func (s *TeamService) ListOrgTeams(ctx context.Context, org string) ([]types.Team, error)` | ListOrgTeams returns all teams in an organisation. | +| method | TeamService.ListRepos | `func (s *TeamService) ListRepos(ctx context.Context, teamID int64) ([]types.Repository, error)` | ListRepos returns all repositories managed by a team. | +| method | TeamService.RemoveMember | `func (s *TeamService) RemoveMember(ctx context.Context, teamID int64, username string) error` | RemoveMember removes a user from a team. | +| method | TeamService.RemoveRepo | `func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error` | RemoveRepo removes a repository from a team. | +| method | UserService.Follow | `func (s *UserService) Follow(ctx context.Context, username string) error` | Follow follows a user as the authenticated user. | +| method | UserService.GetCurrent | `func (s *UserService) GetCurrent(ctx context.Context) (*types.User, error)` | GetCurrent returns the authenticated user. | +| 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. | +| 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. | +| 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. | +| method | UserService.ListFollowers | `func (s *UserService) ListFollowers(ctx context.Context, username string) ([]types.User, error)` | ListFollowers returns all followers of a user. | +| method | UserService.ListFollowing | `func (s *UserService) ListFollowing(ctx context.Context, username string) ([]types.User, error)` | ListFollowing returns all users that a user is following. | +| method | UserService.ListStarred | `func (s *UserService) ListStarred(ctx context.Context, username string) ([]types.Repository, error)` | ListStarred returns all repositories starred by a user. | +| method | UserService.Star | `func (s *UserService) Star(ctx context.Context, owner, repo string) error` | Star stars a repository as the authenticated user. | +| method | UserService.Unfollow | `func (s *UserService) Unfollow(ctx context.Context, username string) error` | Unfollow unfollows a user as the authenticated user. | +| method | UserService.Unstar | `func (s *UserService) Unstar(ctx context.Context, owner, repo string) error` | Unstar unstars a repository as the authenticated user. | +| 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. | +| method | WebhookService.ListOrgHooks | `func (s *WebhookService) ListOrgHooks(ctx context.Context, org string) ([]types.Hook, error)` | ListOrgHooks returns all webhooks for an organisation. | +| method | WebhookService.TestHook | `func (s *WebhookService) TestHook(ctx context.Context, owner, repo string, id int64) error` | TestHook triggers a test delivery for a webhook. | +| 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. | +| method | WikiService.DeletePage | `func (s *WikiService) DeletePage(ctx context.Context, owner, repo, pageName string) error` | DeletePage removes a wiki page. | +| method | WikiService.EditPage | `func (s *WikiService) EditPage(ctx context.Context, owner, repo, pageName string, opts *types.CreateWikiPageOptions) (*types.WikiPage, error)` | EditPage updates an existing wiki page. | +| method | WikiService.GetPage | `func (s *WikiService) GetPage(ctx context.Context, owner, repo, pageName string) (*types.WikiPage, error)` | GetPage returns a single wiki page by name. | +| method | WikiService.ListPages | `func (s *WikiService) ListPages(ctx context.Context, owner, repo string) ([]types.WikiPageMetaData, error)` | ListPages returns all wiki page metadata for a repository. | +| function | WithHTTPClient | `func WithHTTPClient(hc *http.Client) Option` | WithHTTPClient sets a custom http.Client. | +| function | WithUserAgent | `func WithUserAgent(ua string) Option` | WithUserAgent sets the User-Agent header. | diff --git a/specs/types.md b/specs/types.md new file mode 100644 index 0000000..46a030a --- /dev/null +++ b/specs/types.md @@ -0,0 +1,245 @@ +# types + +**Import:** `dappco.re/go/core/forge/types` + +**Files:** 36 + +## Types + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| struct | APIError | `type APIError struct` | APIError is an api error with a message | +| struct | APIForbiddenError | `type APIForbiddenError struct` | No doc comment. | +| struct | APIInvalidTopicsError | `type APIInvalidTopicsError struct` | No doc comment. | +| struct | APINotFound | `type APINotFound struct` | No doc comment. | +| struct | APIRepoArchivedError | `type APIRepoArchivedError struct` | No doc comment. | +| struct | APIUnauthorizedError | `type APIUnauthorizedError struct` | No doc comment. | +| struct | APIValidationError | `type APIValidationError struct` | No doc comment. | +| struct | AccessToken | `type AccessToken struct` | No doc comment. | +| struct | ActionTask | `type ActionTask struct` | ActionTask represents a ActionTask | +| struct | ActionTaskResponse | `type ActionTaskResponse struct` | ActionTaskResponse returns a ActionTask | +| struct | ActionVariable | `type ActionVariable struct` | ActionVariable return value of the query API | +| struct | Activity | `type Activity struct` | No doc comment. | +| struct | ActivityPub | `type ActivityPub struct` | ActivityPub type | +| struct | AddCollaboratorOption | `type AddCollaboratorOption struct` | AddCollaboratorOption options when adding a user as a collaborator of a repository | +| struct | AddTimeOption | `type AddTimeOption struct` | AddTimeOption options for adding time to an issue | +| struct | AnnotatedTag | `type AnnotatedTag struct` | AnnotatedTag represents an annotated tag | +| struct | AnnotatedTagObject | `type AnnotatedTagObject struct` | AnnotatedTagObject contains meta information of the tag object | +| struct | Attachment | `type Attachment struct` | Attachment a generic attachment | +| struct | BlockedUser | `type BlockedUser struct` | No doc comment. | +| struct | Branch | `type Branch struct` | Branch represents a repository branch | +| struct | BranchProtection | `type BranchProtection struct` | BranchProtection represents a branch protection for a repository | +| struct | ChangeFileOperation | `type ChangeFileOperation struct` | ChangeFileOperation for creating, updating or deleting a file | +| struct | ChangeFilesOptions | `type ChangeFilesOptions struct` | 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) | +| struct | ChangedFile | `type ChangedFile struct` | ChangedFile store information about files affected by the pull request | +| struct | CombinedStatus | `type CombinedStatus struct` | CombinedStatus holds the combined state of several statuses for a single commit | +| struct | Comment | `type Comment struct` | Comment represents a comment on a commit or issue | +| struct | Commit | `type Commit struct` | No doc comment. | +| struct | CommitAffectedFiles | `type CommitAffectedFiles struct` | CommitAffectedFiles store information about files affected by the commit | +| struct | CommitDateOptions | `type CommitDateOptions struct` | CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE | +| struct | CommitMeta | `type CommitMeta struct` | No doc comment. | +| struct | CommitStats | `type CommitStats struct` | CommitStats is statistics for a RepoCommit | +| struct | CommitStatus | `type CommitStatus struct` | CommitStatus holds a single status of a single Commit | +| struct | CommitStatusState | `type CommitStatusState struct{}` | CommitStatusState holds the state of a CommitStatus It can be "pending", "success", "error" and "failure" CommitStatusState has no fields in the swagger spec. | +| struct | CommitUser | `type CommitUser struct` | No doc comment. | +| struct | Compare | `type Compare struct` | No doc comment. | +| struct | ContentsResponse | `type ContentsResponse struct` | ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content | +| struct | CreateAccessTokenOption | `type CreateAccessTokenOption struct` | CreateAccessTokenOption options when create access token | +| struct | CreateBranchProtectionOption | `type CreateBranchProtectionOption struct` | CreateBranchProtectionOption options for creating a branch protection | +| struct | CreateBranchRepoOption | `type CreateBranchRepoOption struct` | CreateBranchRepoOption options when creating a branch in a repository | +| struct | CreateEmailOption | `type CreateEmailOption struct` | CreateEmailOption options when creating email addresses | +| struct | CreateFileOptions | `type CreateFileOptions struct` | 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) | +| struct | CreateForkOption | `type CreateForkOption struct` | CreateForkOption options for creating a fork | +| struct | CreateGPGKeyOption | `type CreateGPGKeyOption struct` | CreateGPGKeyOption options create user GPG key | +| struct | CreateHookOption | `type CreateHookOption struct` | CreateHookOption options when create a hook | +| struct | CreateHookOptionConfig | `type CreateHookOptionConfig struct{}` | CreateHookOptionConfig has all config options in it required are "content_type" and "url" Required CreateHookOptionConfig has no fields in the swagger spec. | +| struct | CreateIssueCommentOption | `type CreateIssueCommentOption struct` | CreateIssueCommentOption options for creating a comment on an issue | +| struct | CreateIssueOption | `type CreateIssueOption struct` | CreateIssueOption options to create one issue | +| struct | CreateKeyOption | `type CreateKeyOption struct` | CreateKeyOption options when creating a key | +| struct | CreateLabelOption | `type CreateLabelOption struct` | CreateLabelOption options for creating a label | +| struct | CreateMilestoneOption | `type CreateMilestoneOption struct` | CreateMilestoneOption options for creating a milestone | +| struct | CreateOAuth2ApplicationOptions | `type CreateOAuth2ApplicationOptions struct` | CreateOAuth2ApplicationOptions holds options to create an oauth2 application | +| struct | CreateOrUpdateSecretOption | `type CreateOrUpdateSecretOption struct` | CreateOrUpdateSecretOption options when creating or updating secret | +| struct | CreateOrgOption | `type CreateOrgOption struct` | CreateOrgOption options for creating an organization | +| struct | CreatePullRequestOption | `type CreatePullRequestOption struct` | CreatePullRequestOption options when creating a pull request | +| struct | CreatePullReviewComment | `type CreatePullReviewComment struct` | CreatePullReviewComment represent a review comment for creation api | +| struct | CreatePullReviewCommentOptions | `type CreatePullReviewCommentOptions struct{}` | CreatePullReviewCommentOptions are options to create a pull review comment CreatePullReviewCommentOptions has no fields in the swagger spec. | +| struct | CreatePullReviewOptions | `type CreatePullReviewOptions struct` | CreatePullReviewOptions are options to create a pull review | +| struct | CreatePushMirrorOption | `type CreatePushMirrorOption struct` | No doc comment. | +| struct | CreateQuotaGroupOptions | `type CreateQuotaGroupOptions struct` | CreateQutaGroupOptions represents the options for creating a quota group | +| struct | CreateQuotaRuleOptions | `type CreateQuotaRuleOptions struct` | CreateQuotaRuleOptions represents the options for creating a quota rule | +| struct | CreateReleaseOption | `type CreateReleaseOption struct` | CreateReleaseOption options when creating a release | +| struct | CreateRepoOption | `type CreateRepoOption struct` | CreateRepoOption options when creating repository | +| struct | CreateStatusOption | `type CreateStatusOption struct` | CreateStatusOption holds the information needed to create a new CommitStatus for a Commit | +| struct | CreateTagOption | `type CreateTagOption struct` | CreateTagOption options when creating a tag | +| struct | CreateTagProtectionOption | `type CreateTagProtectionOption struct` | CreateTagProtectionOption options for creating a tag protection | +| struct | CreateTeamOption | `type CreateTeamOption struct` | CreateTeamOption options for creating a team | +| struct | CreateUserOption | `type CreateUserOption struct` | CreateUserOption create user options | +| struct | CreateVariableOption | `type CreateVariableOption struct` | CreateVariableOption the option when creating variable | +| struct | CreateWikiPageOptions | `type CreateWikiPageOptions struct` | CreateWikiPageOptions form for creating wiki | +| struct | Cron | `type Cron struct` | Cron represents a Cron task | +| struct | DeleteEmailOption | `type DeleteEmailOption struct` | DeleteEmailOption options when deleting email addresses | +| struct | DeleteFileOptions | `type DeleteFileOptions struct` | 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) | +| struct | DeleteLabelsOption | `type DeleteLabelsOption struct` | DeleteLabelOption options for deleting a label | +| struct | DeployKey | `type DeployKey struct` | DeployKey a deploy key | +| struct | DismissPullReviewOptions | `type DismissPullReviewOptions struct` | DismissPullReviewOptions are options to dismiss a pull review | +| struct | DispatchWorkflowOption | `type DispatchWorkflowOption struct` | DispatchWorkflowOption options when dispatching a workflow | +| struct | EditAttachmentOptions | `type EditAttachmentOptions struct` | EditAttachmentOptions options for editing attachments | +| struct | EditBranchProtectionOption | `type EditBranchProtectionOption struct` | EditBranchProtectionOption options for editing a branch protection | +| struct | EditDeadlineOption | `type EditDeadlineOption struct` | EditDeadlineOption options for creating a deadline | +| struct | EditGitHookOption | `type EditGitHookOption struct` | EditGitHookOption options when modifying one Git hook | +| struct | EditHookOption | `type EditHookOption struct` | EditHookOption options when modify one hook | +| struct | EditIssueCommentOption | `type EditIssueCommentOption struct` | EditIssueCommentOption options for editing a comment | +| struct | EditIssueOption | `type EditIssueOption struct` | EditIssueOption options for editing an issue | +| struct | EditLabelOption | `type EditLabelOption struct` | EditLabelOption options for editing a label | +| struct | EditMilestoneOption | `type EditMilestoneOption struct` | EditMilestoneOption options for editing a milestone | +| struct | EditOrgOption | `type EditOrgOption struct` | EditOrgOption options for editing an organization | +| struct | EditPullRequestOption | `type EditPullRequestOption struct` | EditPullRequestOption options when modify pull request | +| struct | EditQuotaRuleOptions | `type EditQuotaRuleOptions struct` | EditQuotaRuleOptions represents the options for editing a quota rule | +| struct | EditReactionOption | `type EditReactionOption struct` | EditReactionOption contain the reaction type | +| struct | EditReleaseOption | `type EditReleaseOption struct` | EditReleaseOption options when editing a release | +| struct | EditRepoOption | `type EditRepoOption struct` | EditRepoOption options when editing a repository's properties | +| struct | EditTagProtectionOption | `type EditTagProtectionOption struct` | EditTagProtectionOption options for editing a tag protection | +| struct | EditTeamOption | `type EditTeamOption struct` | EditTeamOption options for editing a team | +| struct | EditUserOption | `type EditUserOption struct` | EditUserOption edit user options | +| struct | Email | `type Email struct` | Email an email address belonging to a user | +| struct | ExternalTracker | `type ExternalTracker struct` | ExternalTracker represents settings for external tracker | +| struct | ExternalWiki | `type ExternalWiki struct` | ExternalWiki represents setting for external wiki | +| struct | FileCommitResponse | `type FileCommitResponse struct` | No doc comment. | +| struct | FileDeleteResponse | `type FileDeleteResponse struct` | FileDeleteResponse contains information about a repo's file that was deleted | +| struct | FileLinksResponse | `type FileLinksResponse struct` | FileLinksResponse contains the links for a repo's file | +| struct | FileResponse | `type FileResponse struct` | FileResponse contains information about a repo's file | +| struct | FilesResponse | `type FilesResponse struct` | FilesResponse contains information about multiple files from a repo | +| struct | ForgeLike | `type ForgeLike struct{}` | ForgeLike activity data type ForgeLike has no fields in the swagger spec. | +| struct | GPGKey | `type GPGKey struct` | GPGKey a user GPG key to sign commit and tag in repository | +| struct | GPGKeyEmail | `type GPGKeyEmail struct` | GPGKeyEmail an email attached to a GPGKey | +| struct | GeneralAPISettings | `type GeneralAPISettings struct` | GeneralAPISettings contains global api settings exposed by it | +| struct | GeneralAttachmentSettings | `type GeneralAttachmentSettings struct` | GeneralAttachmentSettings contains global Attachment settings exposed by API | +| struct | GeneralRepoSettings | `type GeneralRepoSettings struct` | GeneralRepoSettings contains global repository settings exposed by API | +| struct | GeneralUISettings | `type GeneralUISettings struct` | GeneralUISettings contains global ui settings exposed by API | +| struct | GenerateRepoOption | `type GenerateRepoOption struct` | GenerateRepoOption options when creating repository using a template | +| struct | GitBlobResponse | `type GitBlobResponse struct` | GitBlobResponse represents a git blob | +| struct | GitEntry | `type GitEntry struct` | GitEntry represents a git tree | +| struct | GitHook | `type GitHook struct` | GitHook represents a Git repository hook | +| struct | GitObject | `type GitObject struct` | No doc comment. | +| struct | GitTreeResponse | `type GitTreeResponse struct` | GitTreeResponse returns a git tree | +| struct | GitignoreTemplateInfo | `type GitignoreTemplateInfo struct` | GitignoreTemplateInfo name and text of a gitignore template | +| struct | Hook | `type Hook struct` | Hook a hook is a web hook when one repository changed | +| struct | Identity | `type Identity struct` | Identity for a person's identity like an author or committer | +| struct | InternalTracker | `type InternalTracker struct` | InternalTracker represents settings for internal tracker | +| struct | Issue | `type Issue struct` | Issue represents an issue in a repository | +| struct | IssueConfig | `type IssueConfig struct` | No doc comment. | +| struct | IssueConfigContactLink | `type IssueConfigContactLink struct` | No doc comment. | +| struct | IssueConfigValidation | `type IssueConfigValidation struct` | No doc comment. | +| struct | IssueDeadline | `type IssueDeadline struct` | IssueDeadline represents an issue deadline | +| struct | IssueFormField | `type IssueFormField struct` | IssueFormField represents a form field | +| struct | IssueFormFieldType | `type IssueFormFieldType struct{}` | IssueFormFieldType has no fields in the swagger spec. | +| struct | IssueFormFieldVisible | `type IssueFormFieldVisible struct{}` | IssueFormFieldVisible defines issue form field visible IssueFormFieldVisible has no fields in the swagger spec. | +| struct | IssueLabelsOption | `type IssueLabelsOption struct` | IssueLabelsOption a collection of labels | +| struct | IssueMeta | `type IssueMeta struct` | IssueMeta basic issue information | +| struct | IssueTemplate | `type IssueTemplate struct` | IssueTemplate represents an issue template for a repository | +| struct | IssueTemplateLabels | `type IssueTemplateLabels struct{}` | IssueTemplateLabels has no fields in the swagger spec. | +| struct | Label | `type Label struct` | Label a label to an issue or a pr | +| struct | LabelTemplate | `type LabelTemplate struct` | LabelTemplate info of a Label template | +| struct | LicenseTemplateInfo | `type LicenseTemplateInfo struct` | LicensesInfo contains information about a License | +| struct | LicensesTemplateListEntry | `type LicensesTemplateListEntry struct` | LicensesListEntry is used for the API | +| struct | MarkdownOption | `type MarkdownOption struct` | MarkdownOption markdown options | +| struct | MarkupOption | `type MarkupOption struct` | MarkupOption markup options | +| struct | MergePullRequestOption | `type MergePullRequestOption struct` | MergePullRequestForm form for merging Pull Request | +| struct | MigrateRepoOptions | `type MigrateRepoOptions struct` | MigrateRepoOptions options for migrating repository's this is used to interact with api v1 | +| struct | Milestone | `type Milestone struct` | Milestone milestone is a collection of issues on one repository | +| struct | NewIssuePinsAllowed | `type NewIssuePinsAllowed struct` | NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed | +| struct | NodeInfo | `type NodeInfo struct` | NodeInfo contains standardized way of exposing metadata about a server running one of the distributed social networks | +| struct | NodeInfoServices | `type NodeInfoServices struct` | NodeInfoServices contains the third party sites this server can connect to via their application API | +| struct | NodeInfoSoftware | `type NodeInfoSoftware struct` | NodeInfoSoftware contains Metadata about server software in use | +| struct | NodeInfoUsage | `type NodeInfoUsage struct` | NodeInfoUsage contains usage statistics for this server | +| struct | NodeInfoUsageUsers | `type NodeInfoUsageUsers struct` | NodeInfoUsageUsers contains statistics about the users of this server | +| struct | Note | `type Note struct` | Note contains information related to a git note | +| struct | NoteOptions | `type NoteOptions struct` | No doc comment. | +| struct | NotificationCount | `type NotificationCount struct` | NotificationCount number of unread notifications | +| struct | NotificationSubject | `type NotificationSubject struct` | NotificationSubject contains the notification subject (Issue/Pull/Commit) | +| struct | NotificationThread | `type NotificationThread struct` | NotificationThread expose Notification on API | +| struct | NotifySubjectType | `type NotifySubjectType struct{}` | NotifySubjectType represent type of notification subject NotifySubjectType has no fields in the swagger spec. | +| struct | OAuth2Application | `type OAuth2Application struct` | No doc comment. | +| struct | Organization | `type Organization struct` | Organization represents an organization | +| struct | OrganizationPermissions | `type OrganizationPermissions struct` | OrganizationPermissions list different users permissions on an organization | +| struct | PRBranchInfo | `type PRBranchInfo struct` | PRBranchInfo information about a branch | +| struct | Package | `type Package struct` | Package represents a package | +| struct | PackageFile | `type PackageFile struct` | PackageFile represents a package file | +| struct | PayloadCommit | `type PayloadCommit struct` | PayloadCommit represents a commit | +| struct | PayloadCommitVerification | `type PayloadCommitVerification struct` | PayloadCommitVerification represents the GPG verification of a commit | +| struct | PayloadUser | `type PayloadUser struct` | PayloadUser represents the author or committer of a commit | +| struct | Permission | `type Permission struct` | Permission represents a set of permissions | +| struct | PublicKey | `type PublicKey struct` | PublicKey publickey is a user key to push code to repository | +| struct | PullRequest | `type PullRequest struct` | PullRequest represents a pull request | +| struct | PullRequestMeta | `type PullRequestMeta struct` | PullRequestMeta PR info if an issue is a PR | +| struct | PullReview | `type PullReview struct` | PullReview represents a pull request review | +| struct | PullReviewComment | `type PullReviewComment struct` | PullReviewComment represents a comment on a pull request review | +| struct | PullReviewRequestOptions | `type PullReviewRequestOptions struct` | PullReviewRequestOptions are options to add or remove pull review requests | +| struct | PushMirror | `type PushMirror struct` | PushMirror represents information of a push mirror | +| struct | QuotaGroup | `type QuotaGroup struct` | QuotaGroup represents a quota group | +| struct | QuotaGroupList | `type QuotaGroupList struct{}` | QuotaGroupList represents a list of quota groups QuotaGroupList has no fields in the swagger spec. | +| struct | QuotaInfo | `type QuotaInfo struct` | QuotaInfo represents information about a user's quota | +| struct | QuotaRuleInfo | `type QuotaRuleInfo struct` | QuotaRuleInfo contains information about a quota rule | +| struct | QuotaUsed | `type QuotaUsed struct` | QuotaUsed represents the quota usage of a user | +| struct | QuotaUsedArtifact | `type QuotaUsedArtifact struct` | QuotaUsedArtifact represents an artifact counting towards a user's quota | +| struct | QuotaUsedArtifactList | `type QuotaUsedArtifactList struct{}` | QuotaUsedArtifactList represents a list of artifacts counting towards a user's quota QuotaUsedArtifactList has no fields in the swagger spec. | +| struct | QuotaUsedAttachment | `type QuotaUsedAttachment struct` | QuotaUsedAttachment represents an attachment counting towards a user's quota | +| struct | QuotaUsedAttachmentList | `type QuotaUsedAttachmentList struct{}` | QuotaUsedAttachmentList represents a list of attachment counting towards a user's quota QuotaUsedAttachmentList has no fields in the swagger spec. | +| struct | QuotaUsedPackage | `type QuotaUsedPackage struct` | QuotaUsedPackage represents a package counting towards a user's quota | +| struct | QuotaUsedPackageList | `type QuotaUsedPackageList struct{}` | QuotaUsedPackageList represents a list of packages counting towards a user's quota QuotaUsedPackageList has no fields in the swagger spec. | +| struct | QuotaUsedSize | `type QuotaUsedSize struct` | QuotaUsedSize represents the size-based quota usage of a user | +| struct | QuotaUsedSizeAssets | `type QuotaUsedSizeAssets struct` | QuotaUsedSizeAssets represents the size-based asset usage of a user | +| struct | QuotaUsedSizeAssetsAttachments | `type QuotaUsedSizeAssetsAttachments struct` | QuotaUsedSizeAssetsAttachments represents the size-based attachment quota usage of a user | +| struct | QuotaUsedSizeAssetsPackages | `type QuotaUsedSizeAssetsPackages struct` | QuotaUsedSizeAssetsPackages represents the size-based package quota usage of a user | +| struct | QuotaUsedSizeGit | `type QuotaUsedSizeGit struct` | QuotaUsedSizeGit represents the size-based git (lfs) quota usage of a user | +| struct | QuotaUsedSizeRepos | `type QuotaUsedSizeRepos struct` | QuotaUsedSizeRepos represents the size-based repository quota usage of a user | +| struct | Reaction | `type Reaction struct` | Reaction contain one reaction | +| struct | Reference | `type Reference struct` | No doc comment. | +| struct | Release | `type Release struct` | Release represents a repository release | +| struct | RenameUserOption | `type RenameUserOption struct` | RenameUserOption options when renaming a user | +| struct | ReplaceFlagsOption | `type ReplaceFlagsOption struct` | ReplaceFlagsOption options when replacing the flags of a repository | +| struct | RepoCollaboratorPermission | `type RepoCollaboratorPermission struct` | RepoCollaboratorPermission to get repository permission for a collaborator | +| struct | RepoCommit | `type RepoCommit struct` | No doc comment. | +| struct | RepoTopicOptions | `type RepoTopicOptions struct` | RepoTopicOptions a collection of repo topic names | +| struct | RepoTransfer | `type RepoTransfer struct` | RepoTransfer represents a pending repo transfer | +| struct | Repository | `type Repository struct` | Repository represents a repository | +| struct | RepositoryMeta | `type RepositoryMeta struct` | RepositoryMeta basic repository information | +| struct | ReviewStateType | `type ReviewStateType struct{}` | ReviewStateType review state type ReviewStateType has no fields in the swagger spec. | +| struct | SearchResults | `type SearchResults struct` | SearchResults results of a successful search | +| struct | Secret | `type Secret struct` | Secret represents a secret | +| struct | ServerVersion | `type ServerVersion struct` | ServerVersion wraps the version of the server | +| struct | SetUserQuotaGroupsOptions | `type SetUserQuotaGroupsOptions struct` | SetUserQuotaGroupsOptions represents the quota groups of a user | +| type | StateType | `type StateType string` | StateType is the state of an issue or PR: "open", "closed". | +| struct | StopWatch | `type StopWatch struct` | StopWatch represent a running stopwatch | +| struct | SubmitPullReviewOptions | `type SubmitPullReviewOptions struct` | SubmitPullReviewOptions are options to submit a pending pull review | +| struct | Tag | `type Tag struct` | Tag represents a repository tag | +| struct | TagArchiveDownloadCount | `type TagArchiveDownloadCount struct` | TagArchiveDownloadCount counts how many times a archive was downloaded | +| struct | TagProtection | `type TagProtection struct` | TagProtection represents a tag protection | +| struct | Team | `type Team struct` | Team represents a team in an organization | +| type | TimeStamp | `type TimeStamp string` | TimeStamp is a Forgejo timestamp string. | +| struct | TimelineComment | `type TimelineComment struct` | TimelineComment represents a timeline comment (comment of any type) on a commit or issue | +| struct | TopicName | `type TopicName struct` | TopicName a list of repo topic names | +| struct | TopicResponse | `type TopicResponse struct` | TopicResponse for returning topics | +| struct | TrackedTime | `type TrackedTime struct` | TrackedTime worked time for an issue / pr | +| struct | TransferRepoOption | `type TransferRepoOption struct` | TransferRepoOption options when transfer a repository's ownership | +| struct | UpdateBranchRepoOption | `type UpdateBranchRepoOption struct` | UpdateBranchRepoOption options when updating a branch in a repository | +| struct | UpdateFileOptions | `type UpdateFileOptions struct` | 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) | +| struct | UpdateRepoAvatarOption | `type UpdateRepoAvatarOption struct` | UpdateRepoAvatarUserOption options when updating the repo avatar | +| struct | UpdateUserAvatarOption | `type UpdateUserAvatarOption struct` | UpdateUserAvatarUserOption options when updating the user avatar | +| struct | UpdateVariableOption | `type UpdateVariableOption struct` | UpdateVariableOption the option when updating variable | +| struct | User | `type User struct` | User represents a user | +| struct | UserHeatmapData | `type UserHeatmapData struct` | UserHeatmapData represents the data needed to create a heatmap | +| struct | UserSettings | `type UserSettings struct` | UserSettings represents user settings | +| struct | UserSettingsOptions | `type UserSettingsOptions struct` | UserSettingsOptions represents options to change user settings | +| struct | WatchInfo | `type WatchInfo struct` | WatchInfo represents an API watch status of one repository | +| struct | WikiCommit | `type WikiCommit struct` | WikiCommit page commit/revision | +| struct | WikiCommitList | `type WikiCommitList struct` | WikiCommitList commit/revision list | +| struct | WikiPage | `type WikiPage struct` | WikiPage a wiki page | +| struct | WikiPageMetaData | `type WikiPageMetaData struct` | WikiPageMetaData wiki page meta information | + +## Functions + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| - | - | - | None. | -- 2.45.3 From 565e807fb265c5e01e061134abecb89249c569f6 Mon Sep 17 00:00:00 2001 From: Virgil Date: Fri, 27 Mar 2026 21:51:01 +0000 Subject: [PATCH 5/5] docs(specs): add types RFC --- specs/types/RFC.md | 245 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 specs/types/RFC.md diff --git a/specs/types/RFC.md b/specs/types/RFC.md new file mode 100644 index 0000000..46a030a --- /dev/null +++ b/specs/types/RFC.md @@ -0,0 +1,245 @@ +# types + +**Import:** `dappco.re/go/core/forge/types` + +**Files:** 36 + +## Types + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| struct | APIError | `type APIError struct` | APIError is an api error with a message | +| struct | APIForbiddenError | `type APIForbiddenError struct` | No doc comment. | +| struct | APIInvalidTopicsError | `type APIInvalidTopicsError struct` | No doc comment. | +| struct | APINotFound | `type APINotFound struct` | No doc comment. | +| struct | APIRepoArchivedError | `type APIRepoArchivedError struct` | No doc comment. | +| struct | APIUnauthorizedError | `type APIUnauthorizedError struct` | No doc comment. | +| struct | APIValidationError | `type APIValidationError struct` | No doc comment. | +| struct | AccessToken | `type AccessToken struct` | No doc comment. | +| struct | ActionTask | `type ActionTask struct` | ActionTask represents a ActionTask | +| struct | ActionTaskResponse | `type ActionTaskResponse struct` | ActionTaskResponse returns a ActionTask | +| struct | ActionVariable | `type ActionVariable struct` | ActionVariable return value of the query API | +| struct | Activity | `type Activity struct` | No doc comment. | +| struct | ActivityPub | `type ActivityPub struct` | ActivityPub type | +| struct | AddCollaboratorOption | `type AddCollaboratorOption struct` | AddCollaboratorOption options when adding a user as a collaborator of a repository | +| struct | AddTimeOption | `type AddTimeOption struct` | AddTimeOption options for adding time to an issue | +| struct | AnnotatedTag | `type AnnotatedTag struct` | AnnotatedTag represents an annotated tag | +| struct | AnnotatedTagObject | `type AnnotatedTagObject struct` | AnnotatedTagObject contains meta information of the tag object | +| struct | Attachment | `type Attachment struct` | Attachment a generic attachment | +| struct | BlockedUser | `type BlockedUser struct` | No doc comment. | +| struct | Branch | `type Branch struct` | Branch represents a repository branch | +| struct | BranchProtection | `type BranchProtection struct` | BranchProtection represents a branch protection for a repository | +| struct | ChangeFileOperation | `type ChangeFileOperation struct` | ChangeFileOperation for creating, updating or deleting a file | +| struct | ChangeFilesOptions | `type ChangeFilesOptions struct` | 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) | +| struct | ChangedFile | `type ChangedFile struct` | ChangedFile store information about files affected by the pull request | +| struct | CombinedStatus | `type CombinedStatus struct` | CombinedStatus holds the combined state of several statuses for a single commit | +| struct | Comment | `type Comment struct` | Comment represents a comment on a commit or issue | +| struct | Commit | `type Commit struct` | No doc comment. | +| struct | CommitAffectedFiles | `type CommitAffectedFiles struct` | CommitAffectedFiles store information about files affected by the commit | +| struct | CommitDateOptions | `type CommitDateOptions struct` | CommitDateOptions store dates for GIT_AUTHOR_DATE and GIT_COMMITTER_DATE | +| struct | CommitMeta | `type CommitMeta struct` | No doc comment. | +| struct | CommitStats | `type CommitStats struct` | CommitStats is statistics for a RepoCommit | +| struct | CommitStatus | `type CommitStatus struct` | CommitStatus holds a single status of a single Commit | +| struct | CommitStatusState | `type CommitStatusState struct{}` | CommitStatusState holds the state of a CommitStatus It can be "pending", "success", "error" and "failure" CommitStatusState has no fields in the swagger spec. | +| struct | CommitUser | `type CommitUser struct` | No doc comment. | +| struct | Compare | `type Compare struct` | No doc comment. | +| struct | ContentsResponse | `type ContentsResponse struct` | ContentsResponse contains information about a repo's entry's (dir, file, symlink, submodule) metadata and content | +| struct | CreateAccessTokenOption | `type CreateAccessTokenOption struct` | CreateAccessTokenOption options when create access token | +| struct | CreateBranchProtectionOption | `type CreateBranchProtectionOption struct` | CreateBranchProtectionOption options for creating a branch protection | +| struct | CreateBranchRepoOption | `type CreateBranchRepoOption struct` | CreateBranchRepoOption options when creating a branch in a repository | +| struct | CreateEmailOption | `type CreateEmailOption struct` | CreateEmailOption options when creating email addresses | +| struct | CreateFileOptions | `type CreateFileOptions struct` | 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) | +| struct | CreateForkOption | `type CreateForkOption struct` | CreateForkOption options for creating a fork | +| struct | CreateGPGKeyOption | `type CreateGPGKeyOption struct` | CreateGPGKeyOption options create user GPG key | +| struct | CreateHookOption | `type CreateHookOption struct` | CreateHookOption options when create a hook | +| struct | CreateHookOptionConfig | `type CreateHookOptionConfig struct{}` | CreateHookOptionConfig has all config options in it required are "content_type" and "url" Required CreateHookOptionConfig has no fields in the swagger spec. | +| struct | CreateIssueCommentOption | `type CreateIssueCommentOption struct` | CreateIssueCommentOption options for creating a comment on an issue | +| struct | CreateIssueOption | `type CreateIssueOption struct` | CreateIssueOption options to create one issue | +| struct | CreateKeyOption | `type CreateKeyOption struct` | CreateKeyOption options when creating a key | +| struct | CreateLabelOption | `type CreateLabelOption struct` | CreateLabelOption options for creating a label | +| struct | CreateMilestoneOption | `type CreateMilestoneOption struct` | CreateMilestoneOption options for creating a milestone | +| struct | CreateOAuth2ApplicationOptions | `type CreateOAuth2ApplicationOptions struct` | CreateOAuth2ApplicationOptions holds options to create an oauth2 application | +| struct | CreateOrUpdateSecretOption | `type CreateOrUpdateSecretOption struct` | CreateOrUpdateSecretOption options when creating or updating secret | +| struct | CreateOrgOption | `type CreateOrgOption struct` | CreateOrgOption options for creating an organization | +| struct | CreatePullRequestOption | `type CreatePullRequestOption struct` | CreatePullRequestOption options when creating a pull request | +| struct | CreatePullReviewComment | `type CreatePullReviewComment struct` | CreatePullReviewComment represent a review comment for creation api | +| struct | CreatePullReviewCommentOptions | `type CreatePullReviewCommentOptions struct{}` | CreatePullReviewCommentOptions are options to create a pull review comment CreatePullReviewCommentOptions has no fields in the swagger spec. | +| struct | CreatePullReviewOptions | `type CreatePullReviewOptions struct` | CreatePullReviewOptions are options to create a pull review | +| struct | CreatePushMirrorOption | `type CreatePushMirrorOption struct` | No doc comment. | +| struct | CreateQuotaGroupOptions | `type CreateQuotaGroupOptions struct` | CreateQutaGroupOptions represents the options for creating a quota group | +| struct | CreateQuotaRuleOptions | `type CreateQuotaRuleOptions struct` | CreateQuotaRuleOptions represents the options for creating a quota rule | +| struct | CreateReleaseOption | `type CreateReleaseOption struct` | CreateReleaseOption options when creating a release | +| struct | CreateRepoOption | `type CreateRepoOption struct` | CreateRepoOption options when creating repository | +| struct | CreateStatusOption | `type CreateStatusOption struct` | CreateStatusOption holds the information needed to create a new CommitStatus for a Commit | +| struct | CreateTagOption | `type CreateTagOption struct` | CreateTagOption options when creating a tag | +| struct | CreateTagProtectionOption | `type CreateTagProtectionOption struct` | CreateTagProtectionOption options for creating a tag protection | +| struct | CreateTeamOption | `type CreateTeamOption struct` | CreateTeamOption options for creating a team | +| struct | CreateUserOption | `type CreateUserOption struct` | CreateUserOption create user options | +| struct | CreateVariableOption | `type CreateVariableOption struct` | CreateVariableOption the option when creating variable | +| struct | CreateWikiPageOptions | `type CreateWikiPageOptions struct` | CreateWikiPageOptions form for creating wiki | +| struct | Cron | `type Cron struct` | Cron represents a Cron task | +| struct | DeleteEmailOption | `type DeleteEmailOption struct` | DeleteEmailOption options when deleting email addresses | +| struct | DeleteFileOptions | `type DeleteFileOptions struct` | 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) | +| struct | DeleteLabelsOption | `type DeleteLabelsOption struct` | DeleteLabelOption options for deleting a label | +| struct | DeployKey | `type DeployKey struct` | DeployKey a deploy key | +| struct | DismissPullReviewOptions | `type DismissPullReviewOptions struct` | DismissPullReviewOptions are options to dismiss a pull review | +| struct | DispatchWorkflowOption | `type DispatchWorkflowOption struct` | DispatchWorkflowOption options when dispatching a workflow | +| struct | EditAttachmentOptions | `type EditAttachmentOptions struct` | EditAttachmentOptions options for editing attachments | +| struct | EditBranchProtectionOption | `type EditBranchProtectionOption struct` | EditBranchProtectionOption options for editing a branch protection | +| struct | EditDeadlineOption | `type EditDeadlineOption struct` | EditDeadlineOption options for creating a deadline | +| struct | EditGitHookOption | `type EditGitHookOption struct` | EditGitHookOption options when modifying one Git hook | +| struct | EditHookOption | `type EditHookOption struct` | EditHookOption options when modify one hook | +| struct | EditIssueCommentOption | `type EditIssueCommentOption struct` | EditIssueCommentOption options for editing a comment | +| struct | EditIssueOption | `type EditIssueOption struct` | EditIssueOption options for editing an issue | +| struct | EditLabelOption | `type EditLabelOption struct` | EditLabelOption options for editing a label | +| struct | EditMilestoneOption | `type EditMilestoneOption struct` | EditMilestoneOption options for editing a milestone | +| struct | EditOrgOption | `type EditOrgOption struct` | EditOrgOption options for editing an organization | +| struct | EditPullRequestOption | `type EditPullRequestOption struct` | EditPullRequestOption options when modify pull request | +| struct | EditQuotaRuleOptions | `type EditQuotaRuleOptions struct` | EditQuotaRuleOptions represents the options for editing a quota rule | +| struct | EditReactionOption | `type EditReactionOption struct` | EditReactionOption contain the reaction type | +| struct | EditReleaseOption | `type EditReleaseOption struct` | EditReleaseOption options when editing a release | +| struct | EditRepoOption | `type EditRepoOption struct` | EditRepoOption options when editing a repository's properties | +| struct | EditTagProtectionOption | `type EditTagProtectionOption struct` | EditTagProtectionOption options for editing a tag protection | +| struct | EditTeamOption | `type EditTeamOption struct` | EditTeamOption options for editing a team | +| struct | EditUserOption | `type EditUserOption struct` | EditUserOption edit user options | +| struct | Email | `type Email struct` | Email an email address belonging to a user | +| struct | ExternalTracker | `type ExternalTracker struct` | ExternalTracker represents settings for external tracker | +| struct | ExternalWiki | `type ExternalWiki struct` | ExternalWiki represents setting for external wiki | +| struct | FileCommitResponse | `type FileCommitResponse struct` | No doc comment. | +| struct | FileDeleteResponse | `type FileDeleteResponse struct` | FileDeleteResponse contains information about a repo's file that was deleted | +| struct | FileLinksResponse | `type FileLinksResponse struct` | FileLinksResponse contains the links for a repo's file | +| struct | FileResponse | `type FileResponse struct` | FileResponse contains information about a repo's file | +| struct | FilesResponse | `type FilesResponse struct` | FilesResponse contains information about multiple files from a repo | +| struct | ForgeLike | `type ForgeLike struct{}` | ForgeLike activity data type ForgeLike has no fields in the swagger spec. | +| struct | GPGKey | `type GPGKey struct` | GPGKey a user GPG key to sign commit and tag in repository | +| struct | GPGKeyEmail | `type GPGKeyEmail struct` | GPGKeyEmail an email attached to a GPGKey | +| struct | GeneralAPISettings | `type GeneralAPISettings struct` | GeneralAPISettings contains global api settings exposed by it | +| struct | GeneralAttachmentSettings | `type GeneralAttachmentSettings struct` | GeneralAttachmentSettings contains global Attachment settings exposed by API | +| struct | GeneralRepoSettings | `type GeneralRepoSettings struct` | GeneralRepoSettings contains global repository settings exposed by API | +| struct | GeneralUISettings | `type GeneralUISettings struct` | GeneralUISettings contains global ui settings exposed by API | +| struct | GenerateRepoOption | `type GenerateRepoOption struct` | GenerateRepoOption options when creating repository using a template | +| struct | GitBlobResponse | `type GitBlobResponse struct` | GitBlobResponse represents a git blob | +| struct | GitEntry | `type GitEntry struct` | GitEntry represents a git tree | +| struct | GitHook | `type GitHook struct` | GitHook represents a Git repository hook | +| struct | GitObject | `type GitObject struct` | No doc comment. | +| struct | GitTreeResponse | `type GitTreeResponse struct` | GitTreeResponse returns a git tree | +| struct | GitignoreTemplateInfo | `type GitignoreTemplateInfo struct` | GitignoreTemplateInfo name and text of a gitignore template | +| struct | Hook | `type Hook struct` | Hook a hook is a web hook when one repository changed | +| struct | Identity | `type Identity struct` | Identity for a person's identity like an author or committer | +| struct | InternalTracker | `type InternalTracker struct` | InternalTracker represents settings for internal tracker | +| struct | Issue | `type Issue struct` | Issue represents an issue in a repository | +| struct | IssueConfig | `type IssueConfig struct` | No doc comment. | +| struct | IssueConfigContactLink | `type IssueConfigContactLink struct` | No doc comment. | +| struct | IssueConfigValidation | `type IssueConfigValidation struct` | No doc comment. | +| struct | IssueDeadline | `type IssueDeadline struct` | IssueDeadline represents an issue deadline | +| struct | IssueFormField | `type IssueFormField struct` | IssueFormField represents a form field | +| struct | IssueFormFieldType | `type IssueFormFieldType struct{}` | IssueFormFieldType has no fields in the swagger spec. | +| struct | IssueFormFieldVisible | `type IssueFormFieldVisible struct{}` | IssueFormFieldVisible defines issue form field visible IssueFormFieldVisible has no fields in the swagger spec. | +| struct | IssueLabelsOption | `type IssueLabelsOption struct` | IssueLabelsOption a collection of labels | +| struct | IssueMeta | `type IssueMeta struct` | IssueMeta basic issue information | +| struct | IssueTemplate | `type IssueTemplate struct` | IssueTemplate represents an issue template for a repository | +| struct | IssueTemplateLabels | `type IssueTemplateLabels struct{}` | IssueTemplateLabels has no fields in the swagger spec. | +| struct | Label | `type Label struct` | Label a label to an issue or a pr | +| struct | LabelTemplate | `type LabelTemplate struct` | LabelTemplate info of a Label template | +| struct | LicenseTemplateInfo | `type LicenseTemplateInfo struct` | LicensesInfo contains information about a License | +| struct | LicensesTemplateListEntry | `type LicensesTemplateListEntry struct` | LicensesListEntry is used for the API | +| struct | MarkdownOption | `type MarkdownOption struct` | MarkdownOption markdown options | +| struct | MarkupOption | `type MarkupOption struct` | MarkupOption markup options | +| struct | MergePullRequestOption | `type MergePullRequestOption struct` | MergePullRequestForm form for merging Pull Request | +| struct | MigrateRepoOptions | `type MigrateRepoOptions struct` | MigrateRepoOptions options for migrating repository's this is used to interact with api v1 | +| struct | Milestone | `type Milestone struct` | Milestone milestone is a collection of issues on one repository | +| struct | NewIssuePinsAllowed | `type NewIssuePinsAllowed struct` | NewIssuePinsAllowed represents an API response that says if new Issue Pins are allowed | +| struct | NodeInfo | `type NodeInfo struct` | NodeInfo contains standardized way of exposing metadata about a server running one of the distributed social networks | +| struct | NodeInfoServices | `type NodeInfoServices struct` | NodeInfoServices contains the third party sites this server can connect to via their application API | +| struct | NodeInfoSoftware | `type NodeInfoSoftware struct` | NodeInfoSoftware contains Metadata about server software in use | +| struct | NodeInfoUsage | `type NodeInfoUsage struct` | NodeInfoUsage contains usage statistics for this server | +| struct | NodeInfoUsageUsers | `type NodeInfoUsageUsers struct` | NodeInfoUsageUsers contains statistics about the users of this server | +| struct | Note | `type Note struct` | Note contains information related to a git note | +| struct | NoteOptions | `type NoteOptions struct` | No doc comment. | +| struct | NotificationCount | `type NotificationCount struct` | NotificationCount number of unread notifications | +| struct | NotificationSubject | `type NotificationSubject struct` | NotificationSubject contains the notification subject (Issue/Pull/Commit) | +| struct | NotificationThread | `type NotificationThread struct` | NotificationThread expose Notification on API | +| struct | NotifySubjectType | `type NotifySubjectType struct{}` | NotifySubjectType represent type of notification subject NotifySubjectType has no fields in the swagger spec. | +| struct | OAuth2Application | `type OAuth2Application struct` | No doc comment. | +| struct | Organization | `type Organization struct` | Organization represents an organization | +| struct | OrganizationPermissions | `type OrganizationPermissions struct` | OrganizationPermissions list different users permissions on an organization | +| struct | PRBranchInfo | `type PRBranchInfo struct` | PRBranchInfo information about a branch | +| struct | Package | `type Package struct` | Package represents a package | +| struct | PackageFile | `type PackageFile struct` | PackageFile represents a package file | +| struct | PayloadCommit | `type PayloadCommit struct` | PayloadCommit represents a commit | +| struct | PayloadCommitVerification | `type PayloadCommitVerification struct` | PayloadCommitVerification represents the GPG verification of a commit | +| struct | PayloadUser | `type PayloadUser struct` | PayloadUser represents the author or committer of a commit | +| struct | Permission | `type Permission struct` | Permission represents a set of permissions | +| struct | PublicKey | `type PublicKey struct` | PublicKey publickey is a user key to push code to repository | +| struct | PullRequest | `type PullRequest struct` | PullRequest represents a pull request | +| struct | PullRequestMeta | `type PullRequestMeta struct` | PullRequestMeta PR info if an issue is a PR | +| struct | PullReview | `type PullReview struct` | PullReview represents a pull request review | +| struct | PullReviewComment | `type PullReviewComment struct` | PullReviewComment represents a comment on a pull request review | +| struct | PullReviewRequestOptions | `type PullReviewRequestOptions struct` | PullReviewRequestOptions are options to add or remove pull review requests | +| struct | PushMirror | `type PushMirror struct` | PushMirror represents information of a push mirror | +| struct | QuotaGroup | `type QuotaGroup struct` | QuotaGroup represents a quota group | +| struct | QuotaGroupList | `type QuotaGroupList struct{}` | QuotaGroupList represents a list of quota groups QuotaGroupList has no fields in the swagger spec. | +| struct | QuotaInfo | `type QuotaInfo struct` | QuotaInfo represents information about a user's quota | +| struct | QuotaRuleInfo | `type QuotaRuleInfo struct` | QuotaRuleInfo contains information about a quota rule | +| struct | QuotaUsed | `type QuotaUsed struct` | QuotaUsed represents the quota usage of a user | +| struct | QuotaUsedArtifact | `type QuotaUsedArtifact struct` | QuotaUsedArtifact represents an artifact counting towards a user's quota | +| struct | QuotaUsedArtifactList | `type QuotaUsedArtifactList struct{}` | QuotaUsedArtifactList represents a list of artifacts counting towards a user's quota QuotaUsedArtifactList has no fields in the swagger spec. | +| struct | QuotaUsedAttachment | `type QuotaUsedAttachment struct` | QuotaUsedAttachment represents an attachment counting towards a user's quota | +| struct | QuotaUsedAttachmentList | `type QuotaUsedAttachmentList struct{}` | QuotaUsedAttachmentList represents a list of attachment counting towards a user's quota QuotaUsedAttachmentList has no fields in the swagger spec. | +| struct | QuotaUsedPackage | `type QuotaUsedPackage struct` | QuotaUsedPackage represents a package counting towards a user's quota | +| struct | QuotaUsedPackageList | `type QuotaUsedPackageList struct{}` | QuotaUsedPackageList represents a list of packages counting towards a user's quota QuotaUsedPackageList has no fields in the swagger spec. | +| struct | QuotaUsedSize | `type QuotaUsedSize struct` | QuotaUsedSize represents the size-based quota usage of a user | +| struct | QuotaUsedSizeAssets | `type QuotaUsedSizeAssets struct` | QuotaUsedSizeAssets represents the size-based asset usage of a user | +| struct | QuotaUsedSizeAssetsAttachments | `type QuotaUsedSizeAssetsAttachments struct` | QuotaUsedSizeAssetsAttachments represents the size-based attachment quota usage of a user | +| struct | QuotaUsedSizeAssetsPackages | `type QuotaUsedSizeAssetsPackages struct` | QuotaUsedSizeAssetsPackages represents the size-based package quota usage of a user | +| struct | QuotaUsedSizeGit | `type QuotaUsedSizeGit struct` | QuotaUsedSizeGit represents the size-based git (lfs) quota usage of a user | +| struct | QuotaUsedSizeRepos | `type QuotaUsedSizeRepos struct` | QuotaUsedSizeRepos represents the size-based repository quota usage of a user | +| struct | Reaction | `type Reaction struct` | Reaction contain one reaction | +| struct | Reference | `type Reference struct` | No doc comment. | +| struct | Release | `type Release struct` | Release represents a repository release | +| struct | RenameUserOption | `type RenameUserOption struct` | RenameUserOption options when renaming a user | +| struct | ReplaceFlagsOption | `type ReplaceFlagsOption struct` | ReplaceFlagsOption options when replacing the flags of a repository | +| struct | RepoCollaboratorPermission | `type RepoCollaboratorPermission struct` | RepoCollaboratorPermission to get repository permission for a collaborator | +| struct | RepoCommit | `type RepoCommit struct` | No doc comment. | +| struct | RepoTopicOptions | `type RepoTopicOptions struct` | RepoTopicOptions a collection of repo topic names | +| struct | RepoTransfer | `type RepoTransfer struct` | RepoTransfer represents a pending repo transfer | +| struct | Repository | `type Repository struct` | Repository represents a repository | +| struct | RepositoryMeta | `type RepositoryMeta struct` | RepositoryMeta basic repository information | +| struct | ReviewStateType | `type ReviewStateType struct{}` | ReviewStateType review state type ReviewStateType has no fields in the swagger spec. | +| struct | SearchResults | `type SearchResults struct` | SearchResults results of a successful search | +| struct | Secret | `type Secret struct` | Secret represents a secret | +| struct | ServerVersion | `type ServerVersion struct` | ServerVersion wraps the version of the server | +| struct | SetUserQuotaGroupsOptions | `type SetUserQuotaGroupsOptions struct` | SetUserQuotaGroupsOptions represents the quota groups of a user | +| type | StateType | `type StateType string` | StateType is the state of an issue or PR: "open", "closed". | +| struct | StopWatch | `type StopWatch struct` | StopWatch represent a running stopwatch | +| struct | SubmitPullReviewOptions | `type SubmitPullReviewOptions struct` | SubmitPullReviewOptions are options to submit a pending pull review | +| struct | Tag | `type Tag struct` | Tag represents a repository tag | +| struct | TagArchiveDownloadCount | `type TagArchiveDownloadCount struct` | TagArchiveDownloadCount counts how many times a archive was downloaded | +| struct | TagProtection | `type TagProtection struct` | TagProtection represents a tag protection | +| struct | Team | `type Team struct` | Team represents a team in an organization | +| type | TimeStamp | `type TimeStamp string` | TimeStamp is a Forgejo timestamp string. | +| struct | TimelineComment | `type TimelineComment struct` | TimelineComment represents a timeline comment (comment of any type) on a commit or issue | +| struct | TopicName | `type TopicName struct` | TopicName a list of repo topic names | +| struct | TopicResponse | `type TopicResponse struct` | TopicResponse for returning topics | +| struct | TrackedTime | `type TrackedTime struct` | TrackedTime worked time for an issue / pr | +| struct | TransferRepoOption | `type TransferRepoOption struct` | TransferRepoOption options when transfer a repository's ownership | +| struct | UpdateBranchRepoOption | `type UpdateBranchRepoOption struct` | UpdateBranchRepoOption options when updating a branch in a repository | +| struct | UpdateFileOptions | `type UpdateFileOptions struct` | 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) | +| struct | UpdateRepoAvatarOption | `type UpdateRepoAvatarOption struct` | UpdateRepoAvatarUserOption options when updating the repo avatar | +| struct | UpdateUserAvatarOption | `type UpdateUserAvatarOption struct` | UpdateUserAvatarUserOption options when updating the user avatar | +| struct | UpdateVariableOption | `type UpdateVariableOption struct` | UpdateVariableOption the option when updating variable | +| struct | User | `type User struct` | User represents a user | +| struct | UserHeatmapData | `type UserHeatmapData struct` | UserHeatmapData represents the data needed to create a heatmap | +| struct | UserSettings | `type UserSettings struct` | UserSettings represents user settings | +| struct | UserSettingsOptions | `type UserSettingsOptions struct` | UserSettingsOptions represents options to change user settings | +| struct | WatchInfo | `type WatchInfo struct` | WatchInfo represents an API watch status of one repository | +| struct | WikiCommit | `type WikiCommit struct` | WikiCommit page commit/revision | +| struct | WikiCommitList | `type WikiCommitList struct` | WikiCommitList commit/revision list | +| struct | WikiPage | `type WikiPage struct` | WikiPage a wiki page | +| struct | WikiPageMetaData | `type WikiPageMetaData struct` | WikiPageMetaData wiki page meta information | + +## Functions + +| Kind | Name | Signature | Description | +| --- | --- | --- | --- | +| - | - | - | None. | -- 2.45.3