go-unifi

Code Generation

The two-pass codegen pipeline — the legacy Internal API and the Official OpenAPI surface — and the two independent version axes that pin them.

A single go generate produces all of go-unifi's *.generated.go files. Under the hood it runs two passes over two independent inputs, pinned by two version markers. This page explains the moving parts; for the exact commands to run, see Regenerating.

One command, two passes

The root generator (codegen/main.go, package main) orchestrates everything. Its generate() function resolves versions, makes the inputs available, runs both passes, and writes the version artifacts.

Internal passcodegen/internal (internal.Generate). Reads the controller's field-definition JSONs and emits unifi/<resource>.generated.go (resource structs + private CRUD) and unifi/client.generated.go (the Client/InternalClient interfaces). Hand-maintained v2/ definitions render through a dedicated template.

Official passcodegen/official (runOfficialPass). Reads the committed OpenAPI snapshot and emits the whole unifi/official/ surface: the oapi-codegen models, the per-tag fluent wrappers, the Client interface, and the per-group mocks.

Version artifacts — root writes unifi/version.generated.go plus both markers (.unifi-version and .unifi-version-official) so the generated code and the markers always move together.

Why the Official pass is a separate module

codegen/official carries its own go.mod. The root generator shells out to it as a subprocess (go run .) rather than importing it. That keeps the heavy oapi-codegen / kin-openapi toolchain out of the published go.mod graph — consumers of the library never pull those in. The subprocess runs with GOFLAGS=-buildvcs=false and reads the committed spec offline.

codegen/main.go  (package main, root module)
├── codegen/internal      Internal-API pass (in-process)
│     └── unifi/*.generated.go, unifi/client.generated.go
└── codegen/official      Official-API pass (separate go.mod, shelled out via `go run .`)
      └── unifi/official/*.generated.go

Two inputs, both committed snapshots

Both passes read from committed snapshots, so generation is deterministic and fully offline by default. No controller download is needed for a normal regenerate.

PassInputCommitted location
InternalController field-definition JSONs (extracted from the UniFi Network Application package)codegen/v<version>/ (e.g. codegen/v9.5.21/)
OfficialUniFi OpenAPI (integration/v1) speccodegen/openapi/integration-<version>.json

The Internal field snapshot is frozen at codegen/v9.5.21/ (committed, with a matching .gitignore un-ignore rule). Because the inputs are committed, the daily regeneration CI is a deterministic no-op unless a snapshot or template actually changes.

Multiple Official snapshots may coexist under codegen/openapi/: bumping the spec keeps the older integration-<ver>.json alongside the new one. The Official pass generates the Go surface from the pinned version (the positional argument / .unifi-version-official); the docs website always renders the newest committed snapshot by version order. If the pinned snapshot already exists, the generator uses it offline and skips the download.

If a committed snapshot is missing, the generator falls back to downloading it. download.go is the only remote-ingest point — HTTPS with Ubiquiti-host pinning, atomic extraction, and size caps. A package that predates the Official API (no integration.json) is skipped with a warning so the Internal pass never regresses.

Two version axes

The Internal and Official surfaces version independently. Each has its own marker file at the repo root and its own resolver in codegen/version.go.

Prop

Type

The Internal axis is capped at 9.5.21

9.5.21 is the last UniFi Network Application release that ships the internal api/fields/*.json definitions. Newer Network Application packages (10.x and later) dropped those files, so there is nothing to generate Internal resources from beyond this point — the legacy field-definition format is end-of-life there. (9.5.21 and 10.x are Network Application versions, unrelated to the console's UniFi OS version.) resolveInternalVersion enforces the cap:

  • latest resolves the newest release, then clamps to min(resolved, 9.5.21).
  • An explicit version ≤ 9.5.21 is used as-is.
  • An explicit version > 9.5.21 fails loudly, pointing you at the Official OpenAPI surface instead.

The Official axis floors at 10.1.78

10.1.78 is the first controller whose package ships integration.json (the Official OpenAPI spec). resolveOfficialSpecVersion decouples this from the Internal version: when set explicitly (the positional argument) it uses exactly that version; otherwise it reuses the Internal version if it is ≥ 10.1.78, and falls back to the latest release when the Internal version predates the Official API.

The committed go generate directive pins both axes explicitly, so the canonical regenerate is fully reproducible. The positional argument is the Official spec version; -legacy-version pins the Internal controller version:

unifi/codegen.go
//go:generate go run ../codegen/ -version-base-dir=../codegen/ -legacy-version=9.5.21 10.1.85

Generator flags

The root generator (go run ./codegen [OPTIONS] [official-spec-version]) accepts:

FlagDescriptionDefault
-version-base-dirBase directory for version JSON files (and the openapi/ snapshot dir).
-output-dirOutput directory for the generated Go code.
-legacy-versionPin the Internal/legacy controller version (capped at 9.5.21)latest
-download-onlyOnly fetch/build the input JSONs; do not generatefalse
-debugEnable debug loggingfalse
-traceEnable trace logging (takes precedence over -debug)false

The positional [official-spec-version] argument pins the Official OpenAPI spec version (e.g. 10.1.85); leave it empty to auto-select (the Internal version when ≥ 10.1.78, otherwise the latest release).

What guards it

CI re-runs generation and fails if the working tree is not byte-identical:

  • test-codegen runs go generate unifi/codegen.go, then git diff --exit-code.
  • test-codegen-official runs the codegen/official module's own test suite.
  • The Official pass is additionally guarded by determinism / committed-surface tests in the codegen/official module (TestSurfaceMatchesCommitted, TestSurfaceDeterministic).
  • The generated models carry a round-trip test, models_roundtrip_test.go, which lives in the generated unifi/official package (not the codegen/official module) and runs under the normal go test ./... suite.

See also

On this page