feat: upgrade to core v0.8.0-alpha.1, replace banned stdlib imports
All checks were successful
Security Scan / security (push) Successful in 8s
Test / test (push) Successful in 1m20s

Replace fmt, errors, strings, path/filepath, encoding/json with Core
primitives across 23 files. Keep encoding/json for streaming
NewDecoder/NewEncoder, strings for Fields/FieldsFuncSeq.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude 2026-03-26 13:27:06 +00:00
parent 35792507ad
commit 1ffb4bee5a
No known key found for this signature in database
GPG key ID: AF404715446AEB41
27 changed files with 218 additions and 195 deletions

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -21,82 +21,82 @@ func newActionsService(c *Client) *ActionsService {
// ListRepoSecrets returns all secrets for a repository.
func (s *ActionsService) ListRepoSecrets(ctx context.Context, owner, repo string) ([]types.Secret, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo)
return ListAll[types.Secret](ctx, s.client, path, nil)
}
// IterRepoSecrets returns an iterator over all secrets for a repository.
func (s *ActionsService) IterRepoSecrets(ctx context.Context, owner, repo string) iter.Seq2[types.Secret, error] {
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/actions/secrets", owner, repo)
return ListIter[types.Secret](ctx, s.client, path, nil)
}
// CreateRepoSecret creates or updates a secret in a repository.
// Forgejo expects a PUT with {"data": "secret-value"} body.
func (s *ActionsService) CreateRepoSecret(ctx context.Context, owner, repo, name string, data string) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name)
path := core.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name)
body := map[string]string{"data": data}
return s.client.Put(ctx, path, body, nil)
}
// DeleteRepoSecret removes a secret from a repository.
func (s *ActionsService) DeleteRepoSecret(ctx context.Context, owner, repo, name string) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name)
path := core.Sprintf("/api/v1/repos/%s/%s/actions/secrets/%s", owner, repo, name)
return s.client.Delete(ctx, path)
}
// ListRepoVariables returns all action variables for a repository.
func (s *ActionsService) ListRepoVariables(ctx context.Context, owner, repo string) ([]types.ActionVariable, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo)
return ListAll[types.ActionVariable](ctx, s.client, path, nil)
}
// IterRepoVariables returns an iterator over all action variables for a repository.
func (s *ActionsService) IterRepoVariables(ctx context.Context, owner, repo string) iter.Seq2[types.ActionVariable, error] {
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/actions/variables", owner, repo)
return ListIter[types.ActionVariable](ctx, s.client, path, nil)
}
// CreateRepoVariable creates a new action variable in a repository.
// Forgejo expects a POST with {"value": "var-value"} body.
func (s *ActionsService) CreateRepoVariable(ctx context.Context, owner, repo, name, value string) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/actions/variables/%s", owner, repo, name)
path := core.Sprintf("/api/v1/repos/%s/%s/actions/variables/%s", owner, repo, name)
body := types.CreateVariableOption{Value: value}
return s.client.Post(ctx, path, body, nil)
}
// 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 := core.Sprintf("/api/v1/repos/%s/%s/actions/variables/%s", owner, repo, name)
return s.client.Delete(ctx, path)
}
// ListOrgSecrets returns all secrets for an organisation.
func (s *ActionsService) ListOrgSecrets(ctx context.Context, org string) ([]types.Secret, error) {
path := fmt.Sprintf("/api/v1/orgs/%s/actions/secrets", org)
path := core.Sprintf("/api/v1/orgs/%s/actions/secrets", org)
return ListAll[types.Secret](ctx, s.client, path, nil)
}
// IterOrgSecrets returns an iterator over all secrets for an organisation.
func (s *ActionsService) IterOrgSecrets(ctx context.Context, org string) iter.Seq2[types.Secret, error] {
path := fmt.Sprintf("/api/v1/orgs/%s/actions/secrets", org)
path := core.Sprintf("/api/v1/orgs/%s/actions/secrets", org)
return ListIter[types.Secret](ctx, s.client, path, nil)
}
// ListOrgVariables returns all action variables for an organisation.
func (s *ActionsService) ListOrgVariables(ctx context.Context, org string) ([]types.ActionVariable, error) {
path := fmt.Sprintf("/api/v1/orgs/%s/actions/variables", org)
path := core.Sprintf("/api/v1/orgs/%s/actions/variables", org)
return ListAll[types.ActionVariable](ctx, s.client, path, nil)
}
// IterOrgVariables returns an iterator over all action variables for an organisation.
func (s *ActionsService) IterOrgVariables(ctx context.Context, org string) iter.Seq2[types.ActionVariable, error] {
path := fmt.Sprintf("/api/v1/orgs/%s/actions/variables", org)
path := core.Sprintf("/api/v1/orgs/%s/actions/variables", 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 := core.Sprintf("/api/v1/repos/%s/%s/actions/workflows/%s/dispatches", owner, repo, workflow)
return s.client.Post(ctx, path, opts, nil)
}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -23,19 +23,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 := core.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
return ListAll[types.BranchProtection](ctx, s.client, path, nil)
}
// IterBranchProtections returns an iterator over all branch protections for a repository.
func (s *BranchService) IterBranchProtections(ctx context.Context, owner, repo string) iter.Seq2[types.BranchProtection, error] {
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
return ListIter[types.BranchProtection](ctx, s.client, path, nil)
}
// GetBranchProtection returns a single branch protection by name.
func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, name string) (*types.BranchProtection, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
path := core.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
var out types.BranchProtection
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -45,7 +45,7 @@ func (s *BranchService) GetBranchProtection(ctx context.Context, owner, repo, na
// CreateBranchProtection creates a new branch protection rule.
func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo string, opts *types.CreateBranchProtectionOption) (*types.BranchProtection, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/branch_protections", owner, repo)
var out types.BranchProtection
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err
@ -55,7 +55,7 @@ func (s *BranchService) CreateBranchProtection(ctx context.Context, owner, repo
// EditBranchProtection updates an existing branch protection rule.
func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, name string, opts *types.EditBranchProtectionOption) (*types.BranchProtection, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
path := core.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
var out types.BranchProtection
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
return nil, err
@ -65,6 +65,6 @@ func (s *BranchService) EditBranchProtection(ctx context.Context, owner, repo, n
// DeleteBranchProtection deletes a branch protection rule.
func (s *BranchService) DeleteBranchProtection(ctx context.Context, owner, repo, name string) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
path := core.Sprintf("/api/v1/repos/%s/%s/branch_protections/%s", owner, repo, name)
return s.client.Delete(ctx, path)
}

View file

@ -4,13 +4,11 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"strconv"
"strings"
core "dappco.re/go/core"
coreerr "dappco.re/go/core/log"
)
@ -22,25 +20,25 @@ type APIError struct {
}
func (e *APIError) Error() string {
return fmt.Sprintf("forge: %s %d: %s", e.URL, e.StatusCode, e.Message)
return core.Sprintf("forge: %s %d: %s", e.URL, e.StatusCode, e.Message)
}
// IsNotFound returns true if the error is a 404 response.
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.
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.
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.
@ -80,7 +78,7 @@ func (c *Client) RateLimit() RateLimit {
// NewClient creates a new Forgejo API client.
func NewClient(url, token string, opts ...Option) *Client {
c := &Client{
baseURL: strings.TrimRight(url, "/"),
baseURL: core.TrimSuffix(url, "/"),
token: token,
httpClient: &http.Client{
CheckRedirect: func(req *http.Request, via []*http.Request) error {
@ -139,11 +137,11 @@ func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, er
var bodyReader io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, coreerr.E("Client.PostRaw", "forge: marshal body", err)
r := core.JSONMarshal(body)
if !r.OK {
return nil, coreerr.E("Client.PostRaw", "forge: marshal body", nil)
}
bodyReader = bytes.NewReader(data)
bodyReader = bytes.NewReader(r.Value.([]byte))
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bodyReader)
@ -218,11 +216,11 @@ func (c *Client) doJSON(ctx context.Context, method, path string, body, out any)
var bodyReader io.Reader
if body != nil {
data, err := json.Marshal(body)
if err != nil {
return nil, coreerr.E("Client.doJSON", "forge: marshal body", err)
r := core.JSONMarshal(body)
if !r.OK {
return nil, coreerr.E("Client.doJSON", "forge: marshal body", nil)
}
bodyReader = bytes.NewReader(data)
bodyReader = bytes.NewReader(r.Value.([]byte))
}
req, err := http.NewRequestWithContext(ctx, method, url, bodyReader)
@ -267,7 +265,7 @@ func (c *Client) parseError(resp *http.Response, path string) error {
// Read a bit of the body to see if we can get a message
data, _ := io.ReadAll(io.LimitReader(resp.Body, 1024))
_ = json.Unmarshal(data, &errBody)
core.JSONUnmarshal(data, &errBody)
msg := errBody.Message
if msg == "" && len(data) > 0 {

View file

@ -3,10 +3,11 @@ package forge
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
core "dappco.re/go/core"
)
func TestClient_Good_Get(t *testing.T) {
@ -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 {

View file

@ -3,11 +3,11 @@ package main
import (
"bytes"
"maps"
"path/filepath"
"slices"
"strings"
"text/template"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
)
@ -110,7 +110,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 +122,10 @@ func classifyType(name string) string {
// Strip CRUD prefixes and Option suffix, then retry.
base := name
for _, prefix := range []string{"Create", "Edit", "Delete", "Update", "Add", "Submit", "Replace", "Set", "Transfer"} {
base = 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 +135,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 +151,7 @@ func classifyType(name string) string {
// sanitiseLine collapses a multi-line string into a single line,
// replacing newlines and consecutive whitespace with a single space.
func sanitiseLine(s string) string {
return strings.Join(strings.Fields(s), " ")
return core.Join(" ", strings.Fields(s)...)
}
// enumConstName generates a Go constant name for an enum value.
@ -219,7 +219,13 @@ 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)
if a.Name < b.Name {
return -1
}
if a.Name > b.Name {
return 1
}
return 0
})
}
@ -228,7 +234,7 @@ 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)
}
@ -241,7 +247,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")
})
})

View file

@ -2,10 +2,9 @@ package main
import (
"os"
"path/filepath"
"strings"
"testing"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
)
@ -26,7 +25,7 @@ func TestGenerate_Good_CreatesFiles(t *testing.T) {
entries, _ := os.ReadDir(outDir)
goFiles := 0
for _, e := range entries {
if strings.HasSuffix(e.Name(), ".go") {
if core.HasSuffix(e.Name(), ".go") {
goFiles++
}
}
@ -52,8 +51,8 @@ func TestGenerate_Good_ValidGoSyntax(t *testing.T) {
entries, _ := os.ReadDir(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,10 +61,10 @@ 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")
}
}
@ -87,8 +86,8 @@ func TestGenerate_Good_RepositoryType(t *testing.T) {
var content string
entries, _ := os.ReadDir(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,7 +106,7 @@ 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)
}
}
@ -129,8 +128,8 @@ func TestGenerate_Good_TimeImport(t *testing.T) {
entries, _ := os.ReadDir(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())
}
}

View file

@ -2,8 +2,9 @@ package main
import (
"flag"
"fmt"
"os"
core "dappco.re/go/core"
)
func main() {
@ -13,18 +14,18 @@ func main() {
spec, err := LoadSpec(*specPath)
if err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
core.Print(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
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(os.Stdout, "Loaded %d types, %d CRUD pairs\n", len(types), len(pairs))
core.Print(os.Stdout, "Output dir: %s\n", *outDir)
if err := Generate(types, pairs, *outDir); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
core.Print(os.Stderr, "Error: %v\n", err)
os.Exit(1)
}
}

View file

@ -1,11 +1,10 @@
package main
import (
"encoding/json"
"fmt"
"slices"
"strings"
core "dappco.re/go/core"
coreio "dappco.re/go/core/io"
coreerr "dappco.re/go/core/log"
)
@ -77,8 +76,9 @@ func LoadSpec(path string) (*Spec, error) {
return nil, coreerr.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)
r := core.JSONUnmarshal([]byte(content), &spec)
if !r.OK {
return nil, coreerr.E("LoadSpec", "parse spec", nil)
}
return &spec, nil
}
@ -91,7 +91,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.Sprintf("%v", v))
}
slices.Sort(gt.EnumValues)
result[name] = gt
@ -116,7 +116,13 @@ 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)
if a.GoName < b.GoName {
return -1
}
if a.GoName > b.GoName {
return 1
}
return 0
})
result[name] = gt
}
@ -128,11 +134,11 @@ func ExtractTypes(spec *Spec) map[string]*GoType {
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")
inner := core.TrimPrefix(name, "Create")
inner = core.TrimSuffix(inner, "Option")
editName := "Edit" + inner + "Option"
pair := CRUDPair{Base: inner, Create: name}
if _, ok := spec.Definitions[editName]; ok {
@ -141,7 +147,13 @@ func DetectCRUDPairs(spec *Spec) []CRUDPair {
pairs = append(pairs, pair)
}
slices.SortFunc(pairs, func(a, b CRUDPair) int {
return strings.Compare(a.Base, b.Base)
if a.Base < b.Base {
return -1
}
if a.Base > b.Base {
return 1
}
return 0
})
return pairs
}
@ -149,7 +161,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 {
@ -202,13 +214,13 @@ func pascalCase(s string) string {
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.Upper(p[:1])+p[1:])
}
}
return strings.Join(parts, "")
return core.Join("", parts...)
}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -51,7 +51,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 := core.Sprintf("/api/v1/repos/%s/%s/statuses/%s", owner, repo, ref)
var out types.CombinedStatus
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -61,7 +61,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 := core.Sprintf("/api/v1/repos/%s/%s/commits/%s/statuses", owner, repo, ref)
var out []types.CommitStatus
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -71,7 +71,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 := core.Sprintf("/api/v1/repos/%s/%s/statuses/%s", owner, repo, sha)
var out types.CommitStatus
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err
@ -81,7 +81,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 := core.Sprintf("/api/v1/repos/%s/%s/git/notes/%s", owner, repo, sha)
var out types.Note
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err

View file

@ -2,8 +2,8 @@ package forge
import (
"context"
"fmt"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -19,7 +19,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 := core.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
var out types.ContentsResponse
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -29,7 +29,7 @@ func (s *ContentService) GetFile(ctx context.Context, owner, repo, filepath stri
// CreateFile creates a new file in a repository.
func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath string, opts *types.CreateFileOptions) (*types.FileResponse, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
path := core.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
var out types.FileResponse
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err
@ -39,7 +39,7 @@ func (s *ContentService) CreateFile(ctx context.Context, owner, repo, filepath s
// UpdateFile updates an existing file in a repository.
func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath string, opts *types.UpdateFileOptions) (*types.FileResponse, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
path := core.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
var out types.FileResponse
if err := s.client.Put(ctx, path, opts, &out); err != nil {
return nil, err
@ -49,12 +49,12 @@ func (s *ContentService) UpdateFile(ctx context.Context, owner, repo, filepath s
// DeleteFile deletes a file from a repository. Uses DELETE with a JSON body.
func (s *ContentService) DeleteFile(ctx context.Context, owner, repo, filepath string, opts *types.DeleteFileOptions) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
path := core.Sprintf("/api/v1/repos/%s/%s/contents/%s", owner, repo, filepath)
return s.client.DeleteWithBody(ctx, path, opts)
}
// GetRawFile returns the raw file content as bytes.
func (s *ContentService) GetRawFile(ctx context.Context, owner, repo, filepath string) ([]byte, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/raw/%s", owner, repo, filepath)
path := core.Sprintf("/api/v1/repos/%s/%s/raw/%s", owner, repo, filepath)
return s.client.GetRaw(ctx, path)
}

1
go.mod
View file

@ -3,6 +3,7 @@ module dappco.re/go/core/forge
go 1.26.0
require (
dappco.re/go/core v0.8.0-alpha.1
dappco.re/go/core/io v0.2.0
dappco.re/go/core/log v0.1.0
)

2
go.sum
View file

@ -1,3 +1,5 @@
dappco.re/go/core v0.8.0-alpha.1 h1:gj7+Scv+L63Z7wMxbJYHhaRFkHJo2u4MMPuUSv/Dhtk=
dappco.re/go/core v0.8.0-alpha.1/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=

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -23,77 +23,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 := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", owner, repo, 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 := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/pin", owner, repo, index)
return s.client.Delete(ctx, path)
}
// SetDeadline sets or updates the deadline on an issue.
func (s *IssueService) SetDeadline(ctx context.Context, owner, repo string, index int64, deadline string) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/deadline", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/deadline", owner, repo, index)
body := map[string]string{"due_date": deadline}
return s.client.Post(ctx, path, body, nil)
}
// AddReaction adds a reaction to an issue.
func (s *IssueService) AddReaction(ctx context.Context, owner, repo string, index int64, reaction string) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index)
body := map[string]string{"content": reaction}
return s.client.Post(ctx, path, body, nil)
}
// 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 := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/reactions", owner, repo, index)
body := map[string]string{"content": reaction}
return s.client.DeleteWithBody(ctx, path, body)
}
// StartStopwatch starts the stopwatch on an issue.
func (s *IssueService) StartStopwatch(ctx context.Context, owner, repo string, index int64) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/start", owner, repo, index)
return s.client.Post(ctx, path, nil, nil)
}
// StopStopwatch stops the stopwatch on an issue.
func (s *IssueService) StopStopwatch(ctx context.Context, owner, repo string, index int64) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/stopwatch/stop", owner, repo, 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 := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels", owner, repo, index)
body := types.IssueLabelsOption{Labels: toAnySlice(labelIDs)}
return s.client.Post(ctx, path, body, nil)
}
// RemoveLabel removes a single label from an issue.
func (s *IssueService) RemoveLabel(ctx context.Context, owner, repo string, index int64, labelID int64) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, labelID)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/labels/%d", owner, repo, index, labelID)
return s.client.Delete(ctx, path)
}
// ListComments returns all comments on an issue.
func (s *IssueService) ListComments(ctx context.Context, owner, repo string, index int64) ([]types.Comment, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
return ListAll[types.Comment](ctx, s.client, path, nil)
}
// IterComments returns an iterator over all comments on an issue.
func (s *IssueService) IterComments(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.Comment, error] {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
return ListIter[types.Comment](ctx, s.client, path, nil)
}
// CreateComment creates a comment on an issue.
func (s *IssueService) CreateComment(ctx context.Context, owner, repo string, index int64, body string) (*types.Comment, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/issues/%d/comments", owner, repo, index)
opts := types.CreateIssueCommentOption{Body: body}
var out types.Comment
if err := s.client.Post(ctx, path, opts, &out); err != nil {

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -20,19 +20,19 @@ func newLabelService(c *Client) *LabelService {
// ListRepoLabels returns all labels for a repository.
func (s *LabelService) ListRepoLabels(ctx context.Context, owner, repo string) ([]types.Label, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
return ListAll[types.Label](ctx, s.client, path, nil)
}
// IterRepoLabels returns an iterator over all labels for a repository.
func (s *LabelService) IterRepoLabels(ctx context.Context, owner, repo string) iter.Seq2[types.Label, error] {
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
return ListIter[types.Label](ctx, s.client, path, nil)
}
// GetRepoLabel returns a single label by ID.
func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id int64) (*types.Label, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
path := core.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
var out types.Label
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -42,7 +42,7 @@ func (s *LabelService) GetRepoLabel(ctx context.Context, owner, repo string, id
// CreateRepoLabel creates a new label in a repository.
func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string, opts *types.CreateLabelOption) (*types.Label, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/labels", owner, repo)
var out types.Label
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err
@ -52,7 +52,7 @@ func (s *LabelService) CreateRepoLabel(ctx context.Context, owner, repo string,
// EditRepoLabel updates an existing label in a repository.
func (s *LabelService) EditRepoLabel(ctx context.Context, owner, repo string, id int64, opts *types.EditLabelOption) (*types.Label, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
path := core.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
var out types.Label
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
return nil, err
@ -62,25 +62,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 := core.Sprintf("/api/v1/repos/%s/%s/labels/%d", owner, repo, id)
return s.client.Delete(ctx, path)
}
// ListOrgLabels returns all labels for an organisation.
func (s *LabelService) ListOrgLabels(ctx context.Context, org string) ([]types.Label, error) {
path := fmt.Sprintf("/api/v1/orgs/%s/labels", org)
path := core.Sprintf("/api/v1/orgs/%s/labels", org)
return ListAll[types.Label](ctx, s.client, path, nil)
}
// IterOrgLabels returns an iterator over all labels for an organisation.
func (s *LabelService) IterOrgLabels(ctx context.Context, org string) iter.Seq2[types.Label, error] {
path := fmt.Sprintf("/api/v1/orgs/%s/labels", org)
path := core.Sprintf("/api/v1/orgs/%s/labels", org)
return ListIter[types.Label](ctx, s.client, path, nil)
}
// CreateOrgLabel creates a new label in an organisation.
func (s *LabelService) CreateOrgLabel(ctx context.Context, org string, opts *types.CreateLabelOption) (*types.Label, error) {
path := fmt.Sprintf("/api/v1/orgs/%s/labels", org)
path := core.Sprintf("/api/v1/orgs/%s/labels", org)
var out types.Label
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err

View file

@ -2,8 +2,8 @@ package forge
import (
"context"
"fmt"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -18,13 +18,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 := core.Sprintf("/api/v1/repos/%s/%s/milestones", params["owner"], params["repo"])
return ListAll[types.Milestone](ctx, s.client, path, nil)
}
// Get returns a single milestone by ID.
func (s *MilestoneService) Get(ctx context.Context, owner, repo string, id int64) (*types.Milestone, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/milestones/%d", owner, repo, id)
path := core.Sprintf("/api/v1/repos/%s/%s/milestones/%d", owner, repo, id)
var out types.Milestone
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -34,7 +34,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 := core.Sprintf("/api/v1/repos/%s/%s/milestones", owner, repo)
var out types.Milestone
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err

View file

@ -2,8 +2,8 @@ package forge
import (
"context"
"fmt"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -41,7 +41,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 := core.Sprintf("/api/v1/licenses/%s", name)
var out types.LicenseTemplateInfo
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -60,7 +60,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 := core.Sprintf("/api/v1/gitignore/templates/%s", name)
var out types.GitignoreTemplateInfo
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -30,13 +30,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 := core.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo)
return ListAll[types.NotificationThread](ctx, s.client, path, nil)
}
// IterRepo returns an iterator over all notifications for a specific repository.
func (s *NotificationService) IterRepo(ctx context.Context, owner, repo string) iter.Seq2[types.NotificationThread, error] {
path := fmt.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo)
path := core.Sprintf("/api/v1/repos/%s/%s/notifications", owner, repo)
return ListIter[types.NotificationThread](ctx, s.client, path, nil)
}
@ -47,7 +47,7 @@ func (s *NotificationService) MarkRead(ctx context.Context) error {
// GetThread returns a single notification thread by ID.
func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.NotificationThread, error) {
path := fmt.Sprintf("/api/v1/notifications/threads/%d", id)
path := core.Sprintf("/api/v1/notifications/threads/%d", id)
var out types.NotificationThread
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -57,6 +57,6 @@ func (s *NotificationService) GetThread(ctx context.Context, id int64) (*types.N
// MarkThreadRead marks a single notification thread as read.
func (s *NotificationService) MarkThreadRead(ctx context.Context, id int64) error {
path := fmt.Sprintf("/api/v1/notifications/threads/%d", id)
path := core.Sprintf("/api/v1/notifications/threads/%d", id)
return s.client.Patch(ctx, path, nil, nil)
}

14
orgs.go
View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -23,37 +23,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 := core.Sprintf("/api/v1/orgs/%s/members", org)
return ListAll[types.User](ctx, s.client, path, nil)
}
// IterMembers returns an iterator over all members of an organisation.
func (s *OrgService) IterMembers(ctx context.Context, org string) iter.Seq2[types.User, error] {
path := fmt.Sprintf("/api/v1/orgs/%s/members", org)
path := core.Sprintf("/api/v1/orgs/%s/members", org)
return ListIter[types.User](ctx, s.client, path, nil)
}
// AddMember adds a user to an organisation.
func (s *OrgService) AddMember(ctx context.Context, org, username string) error {
path := fmt.Sprintf("/api/v1/orgs/%s/members/%s", org, username)
path := core.Sprintf("/api/v1/orgs/%s/members/%s", org, username)
return s.client.Put(ctx, path, nil, nil)
}
// RemoveMember removes a user from an organisation.
func (s *OrgService) RemoveMember(ctx context.Context, org, username string) error {
path := fmt.Sprintf("/api/v1/orgs/%s/members/%s", org, username)
path := core.Sprintf("/api/v1/orgs/%s/members/%s", org, 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 := core.Sprintf("/api/v1/users/%s/orgs", username)
return ListAll[types.Organization](ctx, s.client, path, nil)
}
// IterUserOrgs returns an iterator over all organisations for a user.
func (s *OrgService) IterUserOrgs(ctx context.Context, username string) iter.Seq2[types.Organization, error] {
path := fmt.Sprintf("/api/v1/users/%s/orgs", username)
path := core.Sprintf("/api/v1/users/%s/orgs", username)
return ListIter[types.Organization](ctx, s.client, path, nil)
}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -20,19 +20,19 @@ func newPackageService(c *Client) *PackageService {
// List returns all packages for a given owner.
func (s *PackageService) List(ctx context.Context, owner string) ([]types.Package, error) {
path := fmt.Sprintf("/api/v1/packages/%s", owner)
path := core.Sprintf("/api/v1/packages/%s", owner)
return ListAll[types.Package](ctx, s.client, path, nil)
}
// Iter returns an iterator over all packages for a given owner.
func (s *PackageService) Iter(ctx context.Context, owner string) iter.Seq2[types.Package, error] {
path := fmt.Sprintf("/api/v1/packages/%s", owner)
path := core.Sprintf("/api/v1/packages/%s", owner)
return ListIter[types.Package](ctx, s.client, path, nil)
}
// Get returns a single package by owner, type, name, and version.
func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version string) (*types.Package, error) {
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version)
path := core.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version)
var out types.Package
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -42,18 +42,18 @@ func (s *PackageService) Get(ctx context.Context, owner, pkgType, name, version
// Delete removes a package by owner, type, name, and version.
func (s *PackageService) Delete(ctx context.Context, owner, pkgType, name, version string) error {
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version)
path := core.Sprintf("/api/v1/packages/%s/%s/%s/%s", owner, pkgType, name, version)
return s.client.Delete(ctx, path)
}
// ListFiles returns all files for a specific package version.
func (s *PackageService) ListFiles(ctx context.Context, owner, pkgType, name, version string) ([]types.PackageFile, error) {
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version)
path := core.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version)
return ListAll[types.PackageFile](ctx, s.client, path, nil)
}
// IterFiles returns an iterator over all files for a specific package version.
func (s *PackageService) IterFiles(ctx context.Context, owner, pkgType, name, version string) iter.Seq2[types.PackageFile, error] {
path := fmt.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version)
path := core.Sprintf("/api/v1/packages/%s/%s/%s/%s/files", owner, pkgType, name, version)
return ListIter[types.PackageFile](ctx, s.client, path, nil)
}

View file

@ -2,7 +2,8 @@ package forge
import (
"net/url"
"strings"
core "dappco.re/go/core"
)
// Params maps path variable names to values.
@ -12,7 +13,7 @@ type Params map[string]string
// ResolvePath substitutes {placeholders} in path with values from params.
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
}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -23,32 +23,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 := core.Sprintf("/api/v1/repos/%s/%s/pulls/%d/merge", owner, repo, index)
body := map[string]string{"Do": method}
return s.client.Post(ctx, path, body, nil)
}
// 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 := core.Sprintf("/api/v1/repos/%s/%s/pulls/%d/update", owner, repo, 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 := core.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index)
return ListAll[types.PullReview](ctx, s.client, path, nil)
}
// IterReviews returns an iterator over all reviews on a pull request.
func (s *PullService) IterReviews(ctx context.Context, owner, repo string, index int64) iter.Seq2[types.PullReview, error] {
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index)
path := core.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, 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 := core.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index)
var out types.PullReview
if err := s.client.Post(ctx, path, review, &out); err != nil {
return nil, err
@ -58,13 +58,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 := core.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/dismissals", owner, repo, index, reviewID)
body := map[string]string{"message": msg}
return s.client.Post(ctx, path, body, nil)
}
// UndismissReview undismisses a pull request review.
func (s *PullService) UndismissReview(ctx context.Context, owner, repo string, index, reviewID int64) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, reviewID)
path := core.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/%d/undismissals", owner, repo, index, reviewID)
return s.client.Post(ctx, path, nil, nil)
}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -23,7 +23,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 := core.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner, repo, tag)
var out types.Release
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -33,25 +33,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 := core.Sprintf("/api/v1/repos/%s/%s/releases/tags/%s", owner, repo, tag)
return s.client.Delete(ctx, path)
}
// ListAssets returns all assets for a release.
func (s *ReleaseService) ListAssets(ctx context.Context, owner, repo string, releaseID int64) ([]types.Attachment, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner, repo, releaseID)
path := core.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner, repo, 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 := core.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets", owner, repo, releaseID)
return ListIter[types.Attachment](ctx, s.client, path, nil)
}
// GetAsset returns a single asset for a release.
func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) (*types.Attachment, error) {
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID)
path := core.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID)
var out types.Attachment
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -61,6 +61,6 @@ func (s *ReleaseService) GetAsset(ctx context.Context, owner, repo string, relea
// DeleteAsset deletes a single asset from a release.
func (s *ReleaseService) DeleteAsset(ctx context.Context, owner, repo string, releaseID, assetID int64) error {
path := fmt.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID)
path := core.Sprintf("/api/v1/repos/%s/%s/releases/%d/assets/%d", owner, repo, releaseID, assetID)
return s.client.Delete(ctx, path)
}

View file

@ -3,7 +3,8 @@ package forge
import (
"context"
"iter"
"strings"
core "dappco.re/go/core"
)
// Resource provides generic CRUD operations for a Forgejo API resource.
@ -21,10 +22,11 @@ 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 {
lastSeg := path[i+1:]
if strings.HasPrefix(lastSeg, "{") && strings.HasSuffix(lastSeg, "}") {
collection = path[:i]
parts := core.Split(path, "/")
if len(parts) > 0 {
lastSeg := parts[len(parts)-1]
if core.HasPrefix(lastSeg, "{") && core.HasSuffix(lastSeg, "}") {
collection = path[:len(path)-len(lastSeg)-1]
}
}
return &Resource[T, C, U]{client: c, path: path, collection: collection}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -23,60 +23,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 := core.Sprintf("/api/v1/teams/%d/members", teamID)
return ListAll[types.User](ctx, s.client, path, nil)
}
// IterMembers returns an iterator over all members of a team.
func (s *TeamService) IterMembers(ctx context.Context, teamID int64) iter.Seq2[types.User, error] {
path := fmt.Sprintf("/api/v1/teams/%d/members", teamID)
path := core.Sprintf("/api/v1/teams/%d/members", teamID)
return ListIter[types.User](ctx, s.client, path, nil)
}
// AddMember adds a user to a team.
func (s *TeamService) AddMember(ctx context.Context, teamID int64, username string) error {
path := fmt.Sprintf("/api/v1/teams/%d/members/%s", teamID, username)
path := core.Sprintf("/api/v1/teams/%d/members/%s", teamID, 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 := core.Sprintf("/api/v1/teams/%d/members/%s", teamID, username)
return s.client.Delete(ctx, path)
}
// ListRepos returns all repositories managed by a team.
func (s *TeamService) ListRepos(ctx context.Context, teamID int64) ([]types.Repository, error) {
path := fmt.Sprintf("/api/v1/teams/%d/repos", teamID)
path := core.Sprintf("/api/v1/teams/%d/repos", teamID)
return ListAll[types.Repository](ctx, s.client, path, nil)
}
// IterRepos returns an iterator over all repositories managed by a team.
func (s *TeamService) IterRepos(ctx context.Context, teamID int64) iter.Seq2[types.Repository, error] {
path := fmt.Sprintf("/api/v1/teams/%d/repos", teamID)
path := core.Sprintf("/api/v1/teams/%d/repos", teamID)
return ListIter[types.Repository](ctx, s.client, path, nil)
}
// AddRepo adds a repository to a team.
func (s *TeamService) AddRepo(ctx context.Context, teamID int64, org, repo string) error {
path := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, repo)
path := core.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, repo)
return s.client.Put(ctx, path, nil, nil)
}
// RemoveRepo removes a repository from a team.
func (s *TeamService) RemoveRepo(ctx context.Context, teamID int64, org, repo string) error {
path := fmt.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, repo)
path := core.Sprintf("/api/v1/teams/%d/repos/%s/%s", teamID, org, 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 := core.Sprintf("/api/v1/orgs/%s/teams", org)
return ListAll[types.Team](ctx, s.client, path, nil)
}
// IterOrgTeams returns an iterator over all teams in an organisation.
func (s *TeamService) IterOrgTeams(ctx context.Context, org string) iter.Seq2[types.Team, error] {
path := fmt.Sprintf("/api/v1/orgs/%s/teams", org)
path := core.Sprintf("/api/v1/orgs/%s/teams", org)
return ListIter[types.Team](ctx, s.client, path, nil)
}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -32,60 +32,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 := core.Sprintf("/api/v1/users/%s/followers", 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 := core.Sprintf("/api/v1/users/%s/followers", 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 := core.Sprintf("/api/v1/users/%s/following", 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 := core.Sprintf("/api/v1/users/%s/following", 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 := core.Sprintf("/api/v1/user/following/%s", 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 := core.Sprintf("/api/v1/user/following/%s", 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 := core.Sprintf("/api/v1/users/%s/starred", 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 := core.Sprintf("/api/v1/users/%s/starred", 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 := core.Sprintf("/api/v1/user/starred/%s/%s", owner, 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 := core.Sprintf("/api/v1/user/starred/%s/%s", owner, repo)
return s.client.Delete(ctx, path)
}

View file

@ -2,9 +2,9 @@ package forge
import (
"context"
"fmt"
"iter"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -24,18 +24,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 := core.Sprintf("/api/v1/repos/%s/%s/hooks/%d/tests", owner, repo, 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 := core.Sprintf("/api/v1/orgs/%s/hooks", 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 := core.Sprintf("/api/v1/orgs/%s/hooks", org)
return ListIter[types.Hook](ctx, s.client, path, nil)
}

12
wiki.go
View file

@ -2,8 +2,8 @@ package forge
import (
"context"
"fmt"
core "dappco.re/go/core"
"dappco.re/go/core/forge/types"
)
@ -19,7 +19,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 := core.Sprintf("/api/v1/repos/%s/%s/wiki/pages", owner, repo)
var out []types.WikiPageMetaData
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -29,7 +29,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 := core.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", owner, repo, pageName)
var out types.WikiPage
if err := s.client.Get(ctx, path, &out); err != nil {
return nil, err
@ -39,7 +39,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 := core.Sprintf("/api/v1/repos/%s/%s/wiki/new", owner, repo)
var out types.WikiPage
if err := s.client.Post(ctx, path, opts, &out); err != nil {
return nil, err
@ -49,7 +49,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 := core.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", owner, repo, pageName)
var out types.WikiPage
if err := s.client.Patch(ctx, path, opts, &out); err != nil {
return nil, err
@ -59,6 +59,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 := core.Sprintf("/api/v1/repos/%s/%s/wiki/page/%s", owner, repo, pageName)
return s.client.Delete(ctx, path)
}