📖 9 min read (~ 1900 words).

Enumerations

An enum in Go is a named type plus a block of constants. swagger:enum turns that pair into an enum array on every schema, parameter and header the type reaches. This page covers what the scanner accepts on the value side, what decides the emitted type / format, and the two shapes that do not work.

Every Go snippet below comes from the test-covered docs/examples/concepts/enums package, and every JSON pane is a golden file a test regenerates.

swagger:enum

swagger:enum <name> collects the const values declared with that type. A bare swagger:enum on the type declaration works too — the name is inferred from the declaration it sits on.

The enum type is emitted because something points at it: a model field, a parameter, a header. On its own it is unreachable, and unreachable types are not published. Add swagger:model to the enum type to make it a first-class definition (carrying the enum array) that fields $ref instead — the general swagger:model ⇒ definition + $ref rule.

Each member’s doc comment becomes a line of the x-go-enum-desc extension, and is appended to the property description. Set SkipEnumDescriptions to keep the mapping on the extension only.

Any constant expression, not just literals

The values come from the Go type-checker, which has already evaluated the const block. So the members do not have to be written as literals — anything the compiler can fold is collected.

iota is the case that matters most, because after the first line there is nothing left in the source to read: the following specs carry neither a type nor a value, and inherit both implicitly.

Annotated Go
// Weekday is an iota enum: only the first spec carries a type and a value, and
// every following one inherits both implicitly.
//
// swagger:enum Weekday
type Weekday int

const (
	// Sunday is the first day.
	Sunday Weekday = iota
	// Monday is the second day.
	Monday
	// Tuesday is the third day.
	Tuesday
)

// Schedule carries the enum, which is what makes it reachable and so emitted.
//
// swagger:model
type Schedule struct {
	// Day the job runs on.
	Day Weekday `json:"day"`
}

Full source: docs/examples/concepts/enums/enums.go

#/definitions/Schedule
{
  "type": "object",
  "title": "Schedule carries the enum, which is what makes it reachable and so emitted.",
  "properties": {
    "day": {
      "description": "Day the job runs on.\n0 Sunday is the first day.\n1 Monday is the second day.\n2 Tuesday is the third day.",
      "type": "integer",
      "format": "int64",
      "enum": [
        0,
        1,
        2
      ],
      "x-go-enum-desc": "0 Sunday is the first day.\n1 Monday is the second day.\n2 Tuesday is the third day.",
      "x-go-name": "Day"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/iota.json

Constant expressions and references to earlier members are collected the same way.

Annotated Go
// Level is built from a constant expression and from a reference to an earlier
// member — neither of which is a literal.
//
// swagger:enum Level
type Level int

const (
	// LevelLow is the floor.
	LevelLow Level = 1 << 3
	// LevelHigh doubles it.
	LevelHigh Level = LevelLow * 2
)

// Threshold carries the computed enum.
//
// swagger:model
type Threshold struct {
	// Level to alert at.
	Level Level `json:"level"`
}

Full source: docs/examples/concepts/enums/enums.go

#/definitions/Threshold
{
  "type": "object",
  "title": "Threshold carries the computed enum.",
  "properties": {
    "level": {
      "description": "Level to alert at.\n8 LevelLow is the floor.\n16 LevelHigh doubles it.",
      "type": "integer",
      "format": "int64",
      "enum": [
        8,
        16
      ],
      "x-go-enum-desc": "8 LevelLow is the floor.\n16 LevelHigh doubles it.",
      "x-go-name": "Level"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/expressions.json

The same goes for every other constant form Go offers: negative values, the non-decimal bases (0x2a, 0b101010, 0o52) and digit separators, values above MaxInt64 in an unsigned enum, true / false, rune literals, and both the raw and the escaped string forms.

Negative members are worth a pane of their own, since a signed constant is not a literal in the Go grammar — it is an expression wrapping one:

Annotated Go
// PanDirection straddles zero. A signed constant is not a literal in the Go
// grammar — it is an expression wrapping one — so these are the members that
// used to go missing.
//
// swagger:enum PanDirection
type PanDirection int8

const (
	// PanLeft pans to the left.
	PanLeft PanDirection = -1
	// NoPan holds the current position.
	NoPan PanDirection = 0
	// PanRight pans to the right.
	PanRight PanDirection = 1
)

// Camera carries the signed enum, declared int8.
//
// swagger:model
type Camera struct {
	// Pan direction of the camera.
	Pan PanDirection `json:"pan"`
}

Full source: docs/examples/concepts/enums/enums.go

#/definitions/Camera
{
  "type": "object",
  "title": "Camera carries the signed enum, declared int8.",
  "properties": {
    "pan": {
      "description": "Pan direction of the camera.\n-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
      "type": "integer",
      "format": "int8",
      "enum": [
        -1,
        0,
        1
      ],
      "x-go-enum-desc": "-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
      "x-go-name": "Pan"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/signed.json

The type comes from the declaration

type and format are read from the Go type you declared, never from the members. PanDirection above is an int8, so the property is {integer, int8} — even though every member would fit in a smaller or larger box.

This is also what makes the const block safe to reorder. Zoom is a float32 whose first member is written 0, an integer literal; the schema is a number enum regardless of which member comes first:

Annotated Go
// Zoom is a float32 enum whose FIRST member is written as an integer literal.
// The schema type follows the declared type, so the block can be reordered
// freely.
//
// swagger:enum Zoom
type Zoom float32

const (
	// ZoomNone is the neutral step.
	ZoomNone Zoom = 0
	// ZoomOut steps back.
	ZoomOut Zoom = -1.5
	// ZoomIn steps in.
	ZoomIn Zoom = 1.5
)

// Lens carries the float enum.
//
// swagger:model
type Lens struct {
	// Zoom step of the lens.
	Zoom Zoom `json:"zoom"`
}

Full source: docs/examples/concepts/enums/enums.go

#/definitions/Lens
{
  "type": "object",
  "title": "Lens carries the float enum.",
  "properties": {
    "zoom": {
      "description": "Zoom step of the lens.\n0 ZoomNone is the neutral step.\n-1.5 ZoomOut steps back.\n1.5 ZoomIn steps in.",
      "type": "number",
      "format": "float",
      "enum": [
        0,
        -1.5,
        1.5
      ],
      "x-go-enum-desc": "0 ZoomNone is the neutral step.\n-1.5 ZoomOut steps back.\n1.5 ZoomIn steps in.",
      "x-go-name": "Zoom"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/width.json

A type declared over another named type keeps what that type contributed. An enum written over a string format is still that format:

Annotated Go
// Kind is an enum written over a string format rather than over a plain string:
// the format of the type it is declared over comes with it.
//
// swagger:enum Kind
type Kind strfmt.UUID

const (
	// KindPrimary is the primary kind.
	KindPrimary Kind = "0a8bcf1e-0000-0000-0000-000000000000"
	// KindSecondary is the secondary kind.
	KindSecondary Kind = "0a8bcf1e-1111-1111-1111-111111111111"
)

// Label carries the formatted enum.
//
// swagger:model
type Label struct {
	// Kind of the label.
	Kind Kind `json:"kind"`
}

Full source: docs/examples/concepts/enums/enums.go

#/definitions/Label
{
  "type": "object",
  "title": "Label carries the formatted enum.",
  "properties": {
    "kind": {
      "description": "Kind of the label.\n0a8bcf1e-0000-0000-0000-000000000000 KindPrimary is the primary kind.\n0a8bcf1e-1111-1111-1111-111111111111 KindSecondary is the secondary kind.",
      "type": "string",
      "format": "uuid",
      "enum": [
        "0a8bcf1e-0000-0000-0000-000000000000",
        "0a8bcf1e-1111-1111-1111-111111111111"
      ],
      "x-go-enum-desc": "0a8bcf1e-0000-0000-0000-000000000000 KindPrimary is the primary kind.\n0a8bcf1e-1111-1111-1111-111111111111 KindSecondary is the secondary kind.",
      "x-go-name": "Kind"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/strfmt.json

Parameters and headers

OpenAPI 2.0 forbids a $ref on a non-body parameter or a response header, so there the members and the format are written inline. Nothing changes on the annotation side — the same enum type reaches in: query, path, header and formData, and the items of an array-typed one:

Annotated Go
// SearchParams consumes an enum from a non-body parameter, where OpenAPI 2.0
// forbids a $ref: the members and the format ship inline.
//
// swagger:parameters search
type SearchParams struct {
	// Direction to pan while searching.
	//
	// in: query
	Pan PanDirection `json:"pan"`

	// Directions the client accepts.
	//
	// in: query
	Accepted []PanDirection `json:"accepted"`
}

Full source: docs/examples/concepts/enums/enums.go

[
  {
    "enum": [
      -1,
      0,
      1
    ],
    "type": "integer",
    "format": "int8",
    "x-go-enum-desc": "-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
    "x-go-name": "Pan",
    "description": "Direction to pan while searching.\n-1 PanLeft pans to the left.\n0 NoPan holds the current position.\n1 PanRight pans to the right.",
    "name": "pan",
    "in": "query"
  },
  {
    "type": "array",
    "items": {
      "enum": [
        -1,
        0,
        1
      ],
      "type": "integer",
      "format": "int8"
    },
    "x-go-name": "Accepted",
    "description": "Directions the client accepts.",
    "name": "accepted",
    "in": "query"
  }
]

Full source: docs/examples/concepts/enums/testdata/params.json

Two shapes that do not work

A rune or byte enum emits integers. It is collected like any other, and 'a' reaches the spec as 97:

Annotated Go
// Letter is a rune enum. A rune is an int32, on the wire as much as in Go, so
// the members are code points — 'a' is 97.
//
// swagger:enum Letter
type Letter rune

const (
	// LetterA is the first letter.
	LetterA Letter = 'a'
	// LetterB is the second letter.
	LetterB Letter = 'b'
)

// Glyph carries the rune enum.
//
// swagger:model
type Glyph struct {
	// Letter of the glyph.
	Letter Letter `json:"letter"`
}

Full source: docs/examples/concepts/enums/enums.go

#/definitions/Glyph
{
  "type": "object",
  "title": "Glyph carries the rune enum.",
  "properties": {
    "letter": {
      "description": "Letter of the glyph.\n97 LetterA is the first letter.\n98 LetterB is the second letter.",
      "type": "integer",
      "format": "int32",
      "enum": [
        97,
        98
      ],
      "x-go-enum-desc": "97 LetterA is the first letter.\n98 LetterB is the second letter.",
      "x-go-name": "Letter"
    }
  },
  "x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/enums"
}

Full source: docs/examples/concepts/enums/testdata/runes.json

That is unlikely to be what you pictured, and it is the only faithful answer: a scalar rune is an int32 on the wire as much as in Go, so json.Marshal writes 97, and encoding/json refuses to unmarshal "a" back into the field. A string-typed schema would describe a payload your own server rejects. If you meant characters, declare the type over string (type Letter string, LetterA Letter = "a") — that changes the wire, and the schema follows.

An alias to a basic type cannot host an enum.

type Unsigned = uint64          // an alias, not a new type

// swagger:enum Unsigned        // ← collects nothing
const Zero Unsigned = 0

The Go type-checker erases the alias, so Zero is indistinguishable from any other uint64 constant and there is no set of members to collect. Declare a real type instead (type Unsigned uint64). An alias to a named enum type is fine — the named type survives.

Where to go next