go-forge/docs/development.md

233 lines
6.2 KiB
Markdown
Raw Permalink Normal View History

---
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_Get_Good ./...
# 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 `Test<TypeOrArea>_<MethodOrCase>_<Good|Bad|Ugly>`:
- **`_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_Get_Good
TestClient_ServerError_Bad
TestClient_NotFound_Bad
TestClient_ContextCancellation_Good
TestResource_ListAll_Good
```
## 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 := ResolvePath("/api/v1/repos/{owner}/{repo}/topics", pathParams("owner", owner, "repo", 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)
```