go-forge/docs/development.md
Virgil bfb8cf6034
All checks were successful
Security Scan / security (push) Successful in 17s
Test / test (push) Successful in 41s
refactor(ax): harden forgegen error flow and sync dev guide
Co-Authored-By: Virgil <virgil@lethean.io>
2026-03-30 01:07:12 +00:00

6.2 KiB

title description
Development 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:

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:

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.

# 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:

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:

golangci-lint run ./...

Or with the core CLI:

core go lint

Enabled linters: govet, errcheck, staticcheck, unused, gosimple, ineffassign, typecheck, gocritic, gofmt.

Formatting and vetting

gofmt -w .
go vet ./...

Or:

core go fmt
core go vet

For a full quality-assurance pass (format, vet, lint, and test):

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:

    go generate ./types/...
    

    Or manually:

    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: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]:

    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:

    type TopicService struct {
        client *Client
    }
    
  2. Add action methods for any operations beyond standard CRUD:

    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 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)