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:
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:
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:
- If status >= 400: parseError -> return *APIError
- If status <400:json.Decodeinto&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)