docs: add human-friendly documentation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
72fb4e3b8e
commit
761493c7f5
3 changed files with 664 additions and 0 deletions
281
docs/architecture.md
Normal file
281
docs/architecture.md
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
---
|
||||
title: Architecture
|
||||
description: Internals of go-forge — the HTTP client, generic Resource pattern, pagination, code generation, and error handling.
|
||||
---
|
||||
|
||||
# Architecture
|
||||
|
||||
This document explains how go-forge is structured internally. It covers the layered design, key types, data flow for a typical API call, and how types are generated from the Forgejo swagger specification.
|
||||
|
||||
|
||||
## Layered design
|
||||
|
||||
go-forge is organised in three layers, each building on the one below:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────┐
|
||||
│ Forge (top-level client) │
|
||||
│ Aggregates 18 service structs │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Service layer │
|
||||
│ RepoService, IssueService, PullService, ... │
|
||||
│ Embed Resource[T,C,U] or hold a *Client │
|
||||
├─────────────────────────────────────────────────┤
|
||||
│ Foundation layer │
|
||||
│ Client (HTTP), Resource[T,C,U] (generics), │
|
||||
│ Pagination, Params, Config │
|
||||
└─────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
|
||||
## Client — the HTTP layer
|
||||
|
||||
`Client` (`client.go`) is the lowest-level component. It handles:
|
||||
|
||||
- **Authentication** — every request includes an `Authorization: token <token>` header.
|
||||
- **JSON marshalling** — request bodies are marshalled to JSON; responses are decoded from JSON.
|
||||
- **Error parsing** — HTTP 4xx/5xx responses are converted to `*APIError` with status code, message, and URL.
|
||||
- **Rate limit tracking** — `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `X-RateLimit-Reset` headers are captured after each request.
|
||||
- **Context propagation** — all methods accept `context.Context` as their first parameter.
|
||||
|
||||
### Key methods
|
||||
|
||||
```go
|
||||
func (c *Client) Get(ctx context.Context, path string, out any) error
|
||||
func (c *Client) Post(ctx context.Context, path string, body, out any) error
|
||||
func (c *Client) Patch(ctx context.Context, path string, body, out any) error
|
||||
func (c *Client) Put(ctx context.Context, path string, body, out any) error
|
||||
func (c *Client) Delete(ctx context.Context, path string) error
|
||||
func (c *Client) DeleteWithBody(ctx context.Context, path string, body any) error
|
||||
func (c *Client) GetRaw(ctx context.Context, path string) ([]byte, error)
|
||||
func (c *Client) PostRaw(ctx context.Context, path string, body any) ([]byte, error)
|
||||
```
|
||||
|
||||
The `Raw` variants return the response body as `[]byte` instead of decoding JSON. This is used by endpoints that return non-JSON content (e.g. the markdown rendering endpoint returns raw HTML).
|
||||
|
||||
### Options
|
||||
|
||||
The client supports functional options:
|
||||
|
||||
```go
|
||||
f := forge.NewForge("https://forge.lthn.ai", "token",
|
||||
forge.WithHTTPClient(customHTTPClient),
|
||||
forge.WithUserAgent("my-agent/1.0"),
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
## APIError — structured error handling
|
||||
|
||||
All API errors are returned as `*APIError`:
|
||||
|
||||
```go
|
||||
type APIError struct {
|
||||
StatusCode int
|
||||
Message string
|
||||
URL string
|
||||
}
|
||||
```
|
||||
|
||||
Three helper functions allow classification without type-asserting:
|
||||
|
||||
```go
|
||||
forge.IsNotFound(err) // 404
|
||||
forge.IsForbidden(err) // 403
|
||||
forge.IsConflict(err) // 409
|
||||
```
|
||||
|
||||
These use `errors.As` internally, so they work correctly with wrapped errors.
|
||||
|
||||
|
||||
## Params — path variable resolution
|
||||
|
||||
API paths contain `{placeholders}` (e.g. `/api/v1/repos/{owner}/{repo}`). The `Params` type is a `map[string]string` that resolves these:
|
||||
|
||||
```go
|
||||
type Params map[string]string
|
||||
|
||||
path := forge.ResolvePath("/api/v1/repos/{owner}/{repo}", forge.Params{
|
||||
"owner": "core",
|
||||
"repo": "go-forge",
|
||||
})
|
||||
// Result: "/api/v1/repos/core/go-forge"
|
||||
```
|
||||
|
||||
Values are URL-path-escaped to handle special characters safely.
|
||||
|
||||
|
||||
## Resource[T, C, U] — the generic CRUD core
|
||||
|
||||
The heart of the library is `Resource[T, C, U]` (`resource.go`), a generic struct parameterised on three types:
|
||||
|
||||
- **T** — the resource type (e.g. `types.Repository`)
|
||||
- **C** — the create-options type (e.g. `types.CreateRepoOption`)
|
||||
- **U** — the update-options type (e.g. `types.EditRepoOption`)
|
||||
|
||||
It provides seven methods that map directly to REST operations:
|
||||
|
||||
| Method | HTTP verb | Description |
|
||||
|-----------|-----------|------------------------------------------|
|
||||
| `List` | GET | Single page of results with metadata |
|
||||
| `ListAll` | GET | All results across all pages |
|
||||
| `Iter` | GET | `iter.Seq2[T, error]` iterator over all |
|
||||
| `Get` | GET | Single resource by path params |
|
||||
| `Create` | POST | Create a new resource |
|
||||
| `Update` | PATCH | Modify an existing resource |
|
||||
| `Delete` | DELETE | Remove a resource |
|
||||
|
||||
Services embed this struct to inherit CRUD for free:
|
||||
|
||||
```go
|
||||
type RepoService struct {
|
||||
Resource[types.Repository, types.CreateRepoOption, types.EditRepoOption]
|
||||
}
|
||||
|
||||
func newRepoService(c *Client) *RepoService {
|
||||
return &RepoService{
|
||||
Resource: *NewResource[types.Repository, types.CreateRepoOption, types.EditRepoOption](
|
||||
c, "/api/v1/repos/{owner}/{repo}",
|
||||
),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Services that do not fit the CRUD pattern (e.g. `AdminService`, `LabelService`, `NotificationService`) hold a `*Client` directly and implement methods by hand.
|
||||
|
||||
|
||||
## Pagination
|
||||
|
||||
`pagination.go` provides three generic functions for paginated endpoints:
|
||||
|
||||
### ListPage — single page
|
||||
|
||||
```go
|
||||
func ListPage[T any](ctx context.Context, c *Client, path string, query map[string]string, opts ListOptions) (*PagedResult[T], error)
|
||||
```
|
||||
|
||||
Returns a `PagedResult[T]` containing:
|
||||
- `Items []T` — the results on this page
|
||||
- `TotalCount int` — from the `X-Total-Count` response header
|
||||
- `Page int` — the current page number
|
||||
- `HasMore bool` — whether more pages exist
|
||||
|
||||
`ListOptions` controls the page number (1-based) and items per page:
|
||||
|
||||
```go
|
||||
var DefaultList = ListOptions{Page: 1, Limit: 50}
|
||||
```
|
||||
|
||||
### ListAll — all pages
|
||||
|
||||
```go
|
||||
func ListAll[T any](ctx context.Context, c *Client, path string, query map[string]string) ([]T, error)
|
||||
```
|
||||
|
||||
Fetches every page sequentially and returns the concatenated slice. Uses a page size of 50.
|
||||
|
||||
### ListIter — range-over-func iterator
|
||||
|
||||
```go
|
||||
func ListIter[T any](ctx context.Context, c *Client, path string, query map[string]string) iter.Seq2[T, error]
|
||||
```
|
||||
|
||||
Returns a Go 1.23+ range-over-func iterator that lazily fetches pages as you consume items:
|
||||
|
||||
```go
|
||||
for repo, err := range f.Repos.IterOrgRepos(ctx, "core") {
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Println(repo.Name)
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Data flow of a typical API call
|
||||
|
||||
Here is the path a call like `f.Repos.Get(ctx, params)` takes:
|
||||
|
||||
```
|
||||
1. Caller invokes f.Repos.Get(ctx, Params{"owner":"core", "repo":"go-forge"})
|
||||
2. Resource.Get calls ResolvePath(r.path, params)
|
||||
"/api/v1/repos/{owner}/{repo}" -> "/api/v1/repos/core/go-forge"
|
||||
3. Resource.Get calls Client.Get(ctx, resolvedPath, &out)
|
||||
4. Client.doJSON builds http.Request:
|
||||
- Method: GET
|
||||
- URL: baseURL + resolvedPath
|
||||
- Headers: Authorization, Accept, User-Agent
|
||||
5. Client.httpClient.Do(req) sends the request
|
||||
6. Client reads response:
|
||||
- Updates rate limit from headers
|
||||
- If status >= 400: parseError -> return *APIError
|
||||
- If status < 400: json.Decode into &out
|
||||
7. Resource.Get returns (*T, error) to caller
|
||||
```
|
||||
|
||||
|
||||
## Code generation — types/
|
||||
|
||||
The `types/` package contains 229 Go types generated from Forgejo's `swagger.v1.json` specification. The code generator lives at `cmd/forgegen/`.
|
||||
|
||||
### Pipeline
|
||||
|
||||
```
|
||||
swagger.v1.json --> parser.go --> GoType/GoField IR --> generator.go --> types/*.go
|
||||
LoadSpec() ExtractTypes() Generate()
|
||||
DetectCRUDPairs()
|
||||
```
|
||||
|
||||
1. **parser.go** — `LoadSpec()` reads the JSON spec. `ExtractTypes()` converts swagger definitions into an intermediate representation (`GoType` with `GoField` children). `DetectCRUDPairs()` finds matching `Create*Option`/`Edit*Option` pairs.
|
||||
|
||||
2. **generator.go** — `Generate()` groups types by domain using prefix matching (the `typeGrouping` map), then renders each group into a separate `.go` file using `text/template`.
|
||||
|
||||
### Running the generator
|
||||
|
||||
```bash
|
||||
go generate ./types/...
|
||||
```
|
||||
|
||||
Or manually:
|
||||
|
||||
```bash
|
||||
go run ./cmd/forgegen/ -spec testdata/swagger.v1.json -out types/
|
||||
```
|
||||
|
||||
The `generate.go` file in `types/` contains the `//go:generate` directive:
|
||||
|
||||
```go
|
||||
//go:generate go run ../cmd/forgegen/ -spec ../testdata/swagger.v1.json -out .
|
||||
```
|
||||
|
||||
### Type grouping
|
||||
|
||||
Types are distributed across 36 files based on a name-prefix mapping. For example, types starting with `Repository` or `Repo` go into `repo.go`, types starting with `Issue` go into `issue.go`, and so on. The `classifyType()` function also strips `Create`/`Edit`/`Delete` prefixes and `Option` suffixes before matching, so `CreateRepoOption` lands in `repo.go` alongside `Repository`.
|
||||
|
||||
### Type mapping from swagger to Go
|
||||
|
||||
| Swagger type | Swagger format | Go type |
|
||||
|---------------|----------------|-------------|
|
||||
| string | (none) | `string` |
|
||||
| string | date-time | `time.Time` |
|
||||
| string | binary | `[]byte` |
|
||||
| integer | int64 | `int64` |
|
||||
| integer | int32 | `int32` |
|
||||
| integer | (none) | `int` |
|
||||
| number | float | `float32` |
|
||||
| number | (none) | `float64` |
|
||||
| boolean | — | `bool` |
|
||||
| array | — | `[]T` |
|
||||
| object | — | `map[string]any` |
|
||||
| `$ref` | — | `*RefType` |
|
||||
|
||||
|
||||
## Config resolution
|
||||
|
||||
`config.go` provides `ResolveConfig()` which resolves the Forgejo URL and API token with the following priority:
|
||||
|
||||
1. Explicit flag values (passed as function arguments)
|
||||
2. Environment variables (`FORGE_URL`, `FORGE_TOKEN`)
|
||||
3. Built-in defaults (`http://localhost:3000` for URL; no default for token)
|
||||
|
||||
`NewForgeFromConfig()` wraps this into a one-liner that returns an error if no token is available from any source.
|
||||
232
docs/development.md
Normal file
232
docs/development.md
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
---
|
||||
title: Development
|
||||
description: Building, testing, linting, and contributing to go-forge.
|
||||
---
|
||||
|
||||
# Development
|
||||
|
||||
This guide covers everything needed to build, test, and contribute to go-forge.
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Go 1.26** or later
|
||||
- **golangci-lint** (recommended for linting)
|
||||
- A Forgejo instance and API token (only needed for manual/integration testing — the test suite uses `httptest` and requires no live server)
|
||||
|
||||
|
||||
## Building
|
||||
|
||||
go-forge is a library, so there is nothing to compile for normal use. The only binary in the repository is the code generator:
|
||||
|
||||
```bash
|
||||
go build ./cmd/forgegen/
|
||||
```
|
||||
|
||||
The `core build` CLI can also produce cross-compiled binaries of the generator for distribution. Build configuration is in `.core/build.yaml`:
|
||||
|
||||
```bash
|
||||
core build # Builds forgegen for all configured targets
|
||||
```
|
||||
|
||||
|
||||
## Running tests
|
||||
|
||||
All tests use the standard `testing` package with `net/http/httptest` for HTTP stubbing. No live Forgejo instance is required.
|
||||
|
||||
```bash
|
||||
# Run the full suite
|
||||
go test ./...
|
||||
|
||||
# Run a specific test by name
|
||||
go test -v -run TestClient_Good_Get ./...
|
||||
|
||||
# Run tests with race detection
|
||||
go test -race ./...
|
||||
|
||||
# Generate a coverage report
|
||||
go test -coverprofile=coverage.out ./...
|
||||
go tool cover -html=coverage.out
|
||||
```
|
||||
|
||||
Alternatively, if you have the `core` CLI installed:
|
||||
|
||||
```bash
|
||||
core go test
|
||||
core go cov # Generate coverage
|
||||
core go cov --open # Open coverage report in browser
|
||||
```
|
||||
|
||||
### Test naming convention
|
||||
|
||||
Tests follow the `_Good`, `_Bad`, `_Ugly` suffix pattern:
|
||||
|
||||
- **`_Good`** — Happy-path tests confirming correct behaviour.
|
||||
- **`_Bad`** — Expected error conditions (e.g. 404, 500 responses).
|
||||
- **`_Ugly`** — Edge cases, panics, and boundary conditions.
|
||||
|
||||
Examples:
|
||||
```
|
||||
TestClient_Good_Get
|
||||
TestClient_Bad_ServerError
|
||||
TestClient_Bad_NotFound
|
||||
TestClient_Good_ContextCancellation
|
||||
TestResource_Good_ListAll
|
||||
```
|
||||
|
||||
|
||||
## Linting
|
||||
|
||||
The project uses `golangci-lint` with the configuration in `.golangci.yml`:
|
||||
|
||||
```bash
|
||||
golangci-lint run ./...
|
||||
```
|
||||
|
||||
Or with the `core` CLI:
|
||||
|
||||
```bash
|
||||
core go lint
|
||||
```
|
||||
|
||||
Enabled linters: `govet`, `errcheck`, `staticcheck`, `unused`, `gosimple`, `ineffassign`, `typecheck`, `gocritic`, `gofmt`.
|
||||
|
||||
|
||||
## Formatting and vetting
|
||||
|
||||
```bash
|
||||
gofmt -w .
|
||||
go vet ./...
|
||||
```
|
||||
|
||||
Or:
|
||||
|
||||
```bash
|
||||
core go fmt
|
||||
core go vet
|
||||
```
|
||||
|
||||
For a full quality-assurance pass (format, vet, lint, and test):
|
||||
|
||||
```bash
|
||||
core go qa # Standard checks
|
||||
core go qa full # Adds race detection, vulnerability scanning, and security checks
|
||||
```
|
||||
|
||||
|
||||
## Regenerating types
|
||||
|
||||
When the Forgejo API changes (after a Forgejo upgrade), regenerate the types:
|
||||
|
||||
1. Download the updated `swagger.v1.json` from your Forgejo instance at `/swagger.json` and place it in `testdata/swagger.v1.json`.
|
||||
|
||||
2. Run the generator:
|
||||
```bash
|
||||
go generate ./types/...
|
||||
```
|
||||
|
||||
Or manually:
|
||||
```bash
|
||||
go run ./cmd/forgegen/ -spec testdata/swagger.v1.json -out types/
|
||||
```
|
||||
|
||||
3. Review the diff. The generator produces deterministic output (types are sorted alphabetically within each file), so the diff will show only genuine API changes.
|
||||
|
||||
4. Run the test suite to confirm nothing is broken.
|
||||
|
||||
The `types/generate.go` file holds the `//go:generate` directive that wires everything together:
|
||||
|
||||
```go
|
||||
//go:generate go run ../cmd/forgegen/ -spec ../testdata/swagger.v1.json -out .
|
||||
```
|
||||
|
||||
|
||||
## Adding a new service
|
||||
|
||||
To add coverage for a new Forgejo API domain:
|
||||
|
||||
1. **Create the service file** (e.g. `topics.go`). If the API follows a standard CRUD pattern, embed `Resource[T, C, U]`:
|
||||
|
||||
```go
|
||||
type TopicService struct {
|
||||
Resource[types.Topic, types.CreateTopicOption, types.EditTopicOption]
|
||||
}
|
||||
|
||||
func newTopicService(c *Client) *TopicService {
|
||||
return &TopicService{
|
||||
Resource: *NewResource[types.Topic, types.CreateTopicOption, types.EditTopicOption](
|
||||
c, "/api/v1/repos/{owner}/{repo}/topics/{topic}",
|
||||
),
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If the endpoints are heterogeneous, hold a `*Client` directly instead:
|
||||
|
||||
```go
|
||||
type TopicService struct {
|
||||
client *Client
|
||||
}
|
||||
```
|
||||
|
||||
2. **Add action methods** for any operations beyond standard CRUD:
|
||||
|
||||
```go
|
||||
func (s *TopicService) ListRepoTopics(ctx context.Context, owner, repo string) ([]types.Topic, error) {
|
||||
path := fmt.Sprintf("/api/v1/repos/%s/%s/topics", owner, repo)
|
||||
return ListAll[types.Topic](ctx, s.client, path, nil)
|
||||
}
|
||||
```
|
||||
|
||||
3. **Wire it up** in `forge.go`:
|
||||
- Add a field to the `Forge` struct.
|
||||
- Initialise it in `NewForge()`.
|
||||
|
||||
4. **Write tests** in `topics_test.go` using `httptest.NewServer` to stub the HTTP responses.
|
||||
|
||||
5. Every list method should provide both a `ListX` (returns `[]T`) and an `IterX` (returns `iter.Seq2[T, error]`) variant.
|
||||
|
||||
|
||||
## Coding standards
|
||||
|
||||
- **UK English** in all comments and documentation: organisation, colour, centre, licence (noun).
|
||||
- **`context.Context`** as the first parameter of every exported method.
|
||||
- **Errors** wrapped as `*APIError` with `StatusCode`, `Message`, and `URL`.
|
||||
- **Conventional commits**: `feat:`, `fix:`, `docs:`, `refactor:`, `chore:`.
|
||||
- **Generated code** must not be edited by hand. All changes go through the swagger spec and the generator.
|
||||
- **Licence**: EUPL-1.2. All contributions are licensed under the same terms.
|
||||
|
||||
|
||||
## Commit messages
|
||||
|
||||
Follow the [Conventional Commits](https://www.conventionalcommits.org/) specification:
|
||||
|
||||
```
|
||||
feat: add topic service for repository topics
|
||||
fix: handle empty response body in DeleteWithBody
|
||||
docs: update architecture diagram for pagination
|
||||
refactor: extract rate limit parsing into helper
|
||||
chore: regenerate types from Forgejo 10.1 swagger spec
|
||||
```
|
||||
|
||||
Include the co-author trailer:
|
||||
|
||||
```
|
||||
Co-Authored-By: Virgil <virgil@lethean.io>
|
||||
```
|
||||
|
||||
|
||||
## Project structure at a glance
|
||||
|
||||
```
|
||||
.
|
||||
├── .core/
|
||||
│ ├── build.yaml Build targets for the forgegen binary
|
||||
│ └── release.yaml Release publishing configuration
|
||||
├── .golangci.yml Linter configuration
|
||||
├── cmd/forgegen/ Code generator (swagger -> Go types)
|
||||
├── testdata/ swagger.v1.json spec file
|
||||
├── types/ 229 generated types (36 files)
|
||||
├── *.go Library source (client, services, pagination)
|
||||
└── *_test.go Tests (httptest-based, no live server needed)
|
||||
```
|
||||
151
docs/index.md
Normal file
151
docs/index.md
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
title: go-forge
|
||||
description: Full-coverage Go client for the Forgejo API with generics-based CRUD, pagination, and code-generated types.
|
||||
---
|
||||
|
||||
# go-forge
|
||||
|
||||
`forge.lthn.ai/core/go-forge` is a Go client library for the [Forgejo](https://forgejo.org) REST API. It provides typed access to 18 API domains (repositories, issues, pull requests, organisations, and more) through a single top-level `Forge` client. Types are generated directly from Forgejo's `swagger.v1.json` specification, keeping the library in lockstep with the server.
|
||||
|
||||
**Module path:** `forge.lthn.ai/core/go-forge`
|
||||
**Go version:** 1.26+
|
||||
**Licence:** EUPL-1.2
|
||||
|
||||
|
||||
## Quick start
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"forge.lthn.ai/core/go-forge"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Create a client with your Forgejo URL and API token.
|
||||
f := forge.NewForge("https://forge.lthn.ai", "your-token")
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
// List repositories for an organisation (first page, 50 per page).
|
||||
result, err := f.Repos.List(ctx, forge.Params{"org": "core"}, forge.DefaultList)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
for _, repo := range result.Items {
|
||||
fmt.Println(repo.Name)
|
||||
}
|
||||
|
||||
// Get a single repository.
|
||||
repo, err := f.Repos.Get(ctx, forge.Params{"owner": "core", "repo": "go-forge"})
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("%s — %s\n", repo.FullName, repo.Description)
|
||||
}
|
||||
```
|
||||
|
||||
### Configuration from environment
|
||||
|
||||
If you prefer to resolve the URL and token from environment variables rather than hard-coding them, use `NewForgeFromConfig`:
|
||||
|
||||
```go
|
||||
// Priority: flags > env (FORGE_URL, FORGE_TOKEN) > defaults (http://localhost:3000)
|
||||
f, err := forge.NewForgeFromConfig("", "", forge.WithUserAgent("my-tool/1.0"))
|
||||
if err != nil {
|
||||
log.Fatal(err) // no token configured
|
||||
}
|
||||
```
|
||||
|
||||
Environment variables:
|
||||
|
||||
| Variable | Purpose | Default |
|
||||
|---------------|--------------------------------------|--------------------------|
|
||||
| `FORGE_URL` | Base URL of the Forgejo instance | `http://localhost:3000` |
|
||||
| `FORGE_TOKEN` | API token for authentication | (none -- required) |
|
||||
|
||||
|
||||
## Package layout
|
||||
|
||||
```
|
||||
go-forge/
|
||||
├── client.go HTTP client, auth, error handling, rate limits
|
||||
├── config.go Config resolution: flags > env > defaults
|
||||
├── forge.go Top-level Forge struct aggregating all 18 services
|
||||
├── resource.go Generic Resource[T, C, U] for CRUD operations
|
||||
├── pagination.go ListPage, ListAll, ListIter — paginated requests
|
||||
├── params.go Path variable resolution ({owner}/{repo} -> values)
|
||||
├── repos.go RepoService — repositories, forks, transfers, mirrors
|
||||
├── issues.go IssueService — issues, comments, labels, reactions
|
||||
├── pulls.go PullService — pull requests, merges, reviews
|
||||
├── orgs.go OrgService — organisations, members
|
||||
├── users.go UserService — users, followers, stars
|
||||
├── teams.go TeamService — teams, members, repositories
|
||||
├── admin.go AdminService — site admin, cron, user management
|
||||
├── branches.go BranchService — branches, branch protections
|
||||
├── releases.go ReleaseService — releases, assets, tags
|
||||
├── labels.go LabelService — repo and org labels
|
||||
├── webhooks.go WebhookService — repo and org webhooks
|
||||
├── notifications.go NotificationService — notifications, threads
|
||||
├── packages.go PackageService — package registry
|
||||
├── actions.go ActionsService — CI/CD secrets, variables, dispatches
|
||||
├── contents.go ContentService — file read/write/delete
|
||||
├── wiki.go WikiService — wiki pages
|
||||
├── commits.go CommitService — statuses, notes
|
||||
├── misc.go MiscService — markdown, licences, gitignore, version
|
||||
├── types/ 229 generated Go types from swagger.v1.json
|
||||
│ ├── generate.go go:generate directive
|
||||
│ ├── repo.go Repository, CreateRepoOption, EditRepoOption, ...
|
||||
│ ├── issue.go Issue, CreateIssueOption, ...
|
||||
│ ├── pr.go PullRequest, CreatePullRequestOption, ...
|
||||
│ └── ... (36 files total, grouped by domain)
|
||||
├── cmd/forgegen/ Code generator: swagger spec -> types/*.go
|
||||
│ ├── main.go CLI entry point
|
||||
│ ├── parser.go Swagger spec parsing, type extraction, CRUD pair detection
|
||||
│ └── generator.go Template-based Go source file generation
|
||||
└── testdata/
|
||||
└── swagger.v1.json Forgejo API specification (input for codegen)
|
||||
```
|
||||
|
||||
|
||||
## Services
|
||||
|
||||
The `Forge` struct exposes 18 service fields, each handling a different API domain:
|
||||
|
||||
| Service | Struct | Embedding | Domain |
|
||||
|-----------------|---------------------|----------------------------------|--------------------------------------|
|
||||
| `Repos` | `RepoService` | `Resource[Repository, ...]` | Repositories, forks, transfers |
|
||||
| `Issues` | `IssueService` | `Resource[Issue, ...]` | Issues, comments, labels, reactions |
|
||||
| `Pulls` | `PullService` | `Resource[PullRequest, ...]` | Pull requests, merges, reviews |
|
||||
| `Orgs` | `OrgService` | `Resource[Organization, ...]` | Organisations, members |
|
||||
| `Users` | `UserService` | `Resource[User, ...]` | Users, followers, stars |
|
||||
| `Teams` | `TeamService` | `Resource[Team, ...]` | Teams, members, repos |
|
||||
| `Admin` | `AdminService` | (standalone) | Site admin, cron, user management |
|
||||
| `Branches` | `BranchService` | `Resource[Branch, ...]` | Branches, protections |
|
||||
| `Releases` | `ReleaseService` | `Resource[Release, ...]` | Releases, assets, tags |
|
||||
| `Labels` | `LabelService` | (standalone) | Repo and org labels |
|
||||
| `Webhooks` | `WebhookService` | `Resource[Hook, ...]` | Repo and org webhooks |
|
||||
| `Notifications` | `NotificationService` | (standalone) | Notifications, threads |
|
||||
| `Packages` | `PackageService` | (standalone) | Package registry |
|
||||
| `Actions` | `ActionsService` | (standalone) | CI/CD secrets, variables, dispatches |
|
||||
| `Contents` | `ContentService` | (standalone) | File read/write/delete |
|
||||
| `Wiki` | `WikiService` | (standalone) | Wiki pages |
|
||||
| `Commits` | `CommitService` | (standalone) | Commit statuses, git notes |
|
||||
| `Misc` | `MiscService` | (standalone) | Markdown, licences, gitignore, version |
|
||||
|
||||
Services that embed `Resource[T, C, U]` inherit `List`, `ListAll`, `Iter`, `Get`, `Create`, `Update`, and `Delete` methods automatically. Standalone services have hand-written methods because their API endpoints are heterogeneous and do not fit a uniform CRUD pattern.
|
||||
|
||||
|
||||
## Dependencies
|
||||
|
||||
This module has **zero external dependencies**. It relies solely on the Go standard library (`net/http`, `encoding/json`, `context`, `iter`, etc.) and requires Go 1.26 or later.
|
||||
|
||||
```
|
||||
module forge.lthn.ai/core/go-forge
|
||||
|
||||
go 1.26.0
|
||||
```
|
||||
Loading…
Add table
Reference in a new issue