Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,18 @@ malformed input, the petstore, aliased schemas, go123-specific forms, and cross-
(`types.LookupFieldOrMethod`), never against the emitted names. Placed on the embed (plain field
names) or on the declaration (dotted embed paths); embeds only. Unresolved / behind-`$ref`
targets raise Hints. See `internal/builders/schema/README.md#omit`.
- A `swagger:enum` schema takes its `type`/`format` from the **declared** Go type (`int8` →
`integer/int8`, `float32` → `number/float`), never from the parsed const values, and each member is
normalised to that type — typing from the first value let the const block's declaration order
decide the schema type. Member *values* come from the type-checker
(`TypesInfo.Defs[name].(*types.Const).Val()`), never from the literal syntax, and membership is
decided per name from the constant's own type — which is what makes `iota` blocks (where only the
first spec carries a type and a value) visible at all. So `iota`, expressions (`1 << 3`),
references to earlier members, rune literals (`'a'` → 97), `true`/`false` (identifiers, not
literals), raw/escaped strings, every integer base and above-`MaxInt64` members all resolve
(go-swagger#3412). A literal reader survives only as the degraded-load fallback. See
`internal/scanner/README.md#enum-values`, `internal/builders/schema/README.md#enum-typing` and
`internal/builders/validations/README.md#enum-const-values`.
- The scanner works at the AST / `go/types` level — it never executes or compiles scanned code.
- Parsers never import builders; they write through the interfaces in `internal/ifaces`.
When adding a new annotation, extend the relevant builder's `taggers.go` rather than reaching
Expand Down
2 changes: 1 addition & 1 deletion docs/doc-site/annotation-index/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ tutorial that shows the annotation as runnable Go next to the spec it produces;
| `swagger:allOf` | embedded field / struct | an `allOf` composition | [example]({{% relref "/tutorials/model-definitions#swaggerallof" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-allof" %}}) |
| `swagger:default` | value / field doc | a default-value anchor | [example]({{% relref "/tutorials/examples-and-defaults#swaggerdefault" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-default" %}}) |
| `swagger:description` | type / field / response doc | overrides the `description` (verbatim body with `\|`) | [how-to]({{% relref "overriding-titles-and-descriptions" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-description" %}}) |
| `swagger:enum` | named type | an `enum` array (+ `x-go-enum-desc`) | [example]({{% relref "/tutorials/model-definitions#swaggerenum" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-enum" %}}) |
| `swagger:enum` | named type | an `enum` array (+ `x-go-enum-desc`) | [example]({{% relref "/tutorials/enumerations" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-enum" %}}) |
| `swagger:file` | param / response field | `{type: file}` | [example]({{% relref "/tutorials/routes-and-operations#swaggerfile" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-file" %}}) |
| `swagger:ignore` | type / field doc | excludes the declaration | [example]({{% relref "/tutorials/model-definitions#swaggerignore" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-ignore" %}}) |
| `swagger:meta` | package doc | top-level `info`, `host`, `basePath`, `schemes`, … | [example]({{% relref "/tutorials/document-metadata#swaggermeta" %}}) | [reference]({{% relref "/maintainers/annotations/swagger-meta" %}}) |
Expand Down
20 changes: 18 additions & 2 deletions docs/doc-site/maintainers/annotations/swagger-enum.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,24 @@ description: "Marks a named type as an enum and collects its const values."

## What it does

Marks a string-typed (or integer-typed) named type as an enum and
collects the type's `const` declarations.
Marks a named type over a string, integer, number or boolean as an enum
and collects the type's `const` declarations.

Values come from the Go type-checker, so any constant expression is
collected — `iota` (including the implicit specs, which carry neither a
type nor a value), computed members (`1 << 3`), references to earlier
members, negative values, every integer base, values above `MaxInt64` in
an unsigned enum, rune literals (as code points), `true` / `false`, and
both string forms. The emitted `type` / `format` come from the **declared
Go type**, never from the members, so an `int8` enum is
`{integer, int8}` and reordering the const block cannot change the type.
A type declared over another named type keeps that type's format
(`type Kind strfmt.UUID` stays `format: uuid`).

Two shapes do not work: an alias to a basic type cannot host an enum (the
type-checker erases the alias, leaving nothing to collect), and a `rune`
or `byte` enum emits integers, which is what those types are on the wire.
See [Enumerations]({{% relref "/tutorials/enumerations" %}}).

- **Without `swagger:model`** (the default): the values are applied
**inline on each model field that references the type** — the property
Expand Down
134 changes: 134 additions & 0 deletions docs/doc-site/tutorials/enumerations.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
---
title: Enumerations
weight: 12
description: |
Publish a Go const block as a spec enum — any constant expression, the type
and format taken from the declaration, and the same members inline on
parameters and headers.
---

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`](https://github.com/go-openapi/codescan/tree/master/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`]({{% relref "/maintainers/options" %}}) 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.

{{< example go="concepts/enums/enums.go" goregion="iota"
json="concepts/enums/testdata/iota.json" jsonlabel="#/definitions/Schedule" >}}

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

{{< example go="concepts/enums/enums.go" goregion="expressions"
json="concepts/enums/testdata/expressions.json" jsonlabel="#/definitions/Threshold" >}}

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:

{{< example go="concepts/enums/enums.go" goregion="signed"
json="concepts/enums/testdata/signed.json" jsonlabel="#/definitions/Camera" >}}

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

{{< example go="concepts/enums/enums.go" goregion="width"
json="concepts/enums/testdata/width.json" jsonlabel="#/definitions/Lens" >}}

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

{{< example go="concepts/enums/enums.go" goregion="strfmt"
json="concepts/enums/testdata/strfmt.json" jsonlabel="#/definitions/Label" >}}

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

{{< example go="concepts/enums/enums.go" goregion="params"
json="concepts/enums/testdata/params.json" jsonlabel="GET /cameras/search — parameters"
full="concepts/enums/testdata/full.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`:

{{< example go="concepts/enums/enums.go" goregion="runes"
json="concepts/enums/testdata/runes.json" jsonlabel="#/definitions/Glyph" >}}

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.**

```go
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

- [Validations]({{% relref "/tutorials/validations" %}}) — the other constraints
a property can carry, and the reduced surface parameters accept.
- [Model definitions]({{% relref "/tutorials/model-definitions" %}}) — the rest
of the per-type annotations.
- [`swagger:enum` reference]({{% relref "/maintainers/annotations/swagger-enum" %}})
— the exhaustive rule.
5 changes: 5 additions & 0 deletions docs/doc-site/tutorials/model-definitions.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ Here the values inline on the referencing field. Add `swagger:model` to the enum
type to make it a first-class definition (carrying the `enum` array) that fields
`$ref` — again the `swagger:model ⇒ definition + $ref` rule.

Enums have more to them than a string const block: `iota` and computed members,
what decides the emitted `type` / `format`, and the inline form parameters and
headers take. They get their own page —
[Enumerations]({{% relref "/tutorials/enumerations" %}}).

## swagger:allOf

Embedding base types under `swagger:allOf` composes a schema. Each embedded base
Expand Down
Loading
Loading