oapixconstgen: Make Your OpenAPI Spec the Source of Truth for Go Constants

x-constants:
  int:
    defaultPageSize:   &defaultPageSize 20
    maxPageSize:       &maxPageSize     100
  uint:
    minPasswordLength: &minPasswordLength 8
  float64:
    requestTimeout:    &requestTimeout 30.5
  string:
    defaultLocale:     &defaultLocale "en-US"
  bool:
    enableCache:       &enableCache true
// Code generated by oapixconstgen. DO NOT EDIT.
package api
const (
    DefaultLocale     string  = "en-US"
    DefaultPageSize   int     = 20
    EnableCache       bool    = true
    MaxPageSize       int     = 100
    MinPasswordLength uint    = 8
    RequestTimeout    float64 = 30.5
)

That’s the whole product. Top block goes in your OpenAPI YAML. Bottom block is what oapixconstgen writes to disk when you point it at the spec. The values cross from your API contract to your Go code without a human re-typing them.

The anchor trick

The &defaultPageSize bits aren’t part of the OpenAPI standard — they’re plain YAML anchors. Anywhere else in the spec you can dereference them with *defaultPageSize and the YAML parser inlines the value. So when your /items endpoint declares:

parameters:
  - name: limit
    in: query
    schema:
      type: integer
      default: *defaultPageSize
      maximum: *maxPageSize

…it gets 20 and 100 at parse time. The spec validates as vanilla OpenAPI 3.x. The x-constants block at the top is just an x- extension — anything that doesn’t recognize it leaves it alone. The whole “single source of truth” thing is the YAML language doing the work; oapixconstgen just walks across the bridge to Go and lays down typed constants on the other side.

Install and go:generate

Go 1.24 added the tool directive to go.mod. Use it:

go get -tool github.com/psyb0t/oapixconstgen@latest

Then in the package that wants the constants:

//go:generate go tool oapixconstgen -spec=../../api/openapi.yaml -out=. -pkg=api
package api
go generate ./...

Spec changes → go generate → constants update → the compiler flags every downstream call site that was relying on the old value. That’s the loop. Drift detection by typechecker, not by hoping someone reads the diff carefully.

Flags and types

  • -spec (required) — path to the OpenAPI YAML.
  • -out — output directory. Defaults to ..
  • -file — output file name. Defaults to constants.gen.go. v1.1+, useful when one package consumes multiple specs.
  • -pkg — package name in the generated file. Defaults to api.

Type keys under x-constants: are every Go primitive that holds a single scalar value: int, int8/16/32/64, uint, uint8/16/32/64, float32, float64, string, bool. Unknown type keys get silently skipped — typo integer instead of int and that whole subsection vanishes from the output, and the resulting compile error tells you exactly which constant disappeared. Names get PascalCased from camelCase or snake_case on the way out (max_page_size and maxPageSize both land as MaxPageSize). The output goes through go/format before hitting disk, so it’s gofmt-clean even if the constants change every release.

The whole thing

Tiny. One YAML key, one type map, one format pass. gopkg.in/yaml.v3 for parsing, go/format for output. No template DSL, no code-gen framework, no plugin system. The entire program is ~180 lines of Go.

I built it because I was about to write the constants by hand for the eighth project in a row, knowing one of the values would drift between spec and code within a month, knowing nobody would notice until something rejected a request the spec said was fine. Now the YAML is the constants file. The Go side is generated from it. The two cannot disagree because one is computed from the other.

github.com/psyb0t/oapixconstgen — MIT.