Documentation set for latest master. Latest release v0.36.4. Requires Go 1.26.0 or later. Built 2026-09-04.
github.com/go-openapi/codescan is a Go source code scanner that produces
Swagger 2.0 (OpenAPI 2.0) specifications.
It reads specially formatted comments (annotations) in Go source files and
extracts API metadata β routes, parameters, responses, schemas and more β to
build a complete spec.Swagger document. It supports Go modules (since
go1.11).
The scanner works entirely at the AST / go/types level: it never compiles
or executes the code it scans. It only reads the source and its annotation
comments.
The complete, normative reference for the codescan annotation language.
Every annotation, every keyword, the embedded sub-languages, the formal
grammar the parser implements, and how the commands are put together.
Subsections of go-openapi codescan
About
codescan is a code-first OpenAPI engine: it reads specially formatted
comments (annotations) in your Go source and produces a
Swagger 2.0 specification. It works entirely at the AST /
go/types level β it never compiles or runs the code it scans.
Two ways to build an API
APIs and their documentation tend to evolve along one of two paths. The go-openapi / go-swagger toolkit supports both.
Design-first (contract-first) β you write the OpenAPI document first and treat it as the contract,
then generate servers and clients from it.
If this is your workflow, you should reach for go-swagger (swagger generate server / swagger generate client).
Code-first β you write annotated Go and scan it to produce the spec.
This keeps the document in sync with the code as it changes, and lets you produce a valid specification for a service that already exists.
codescan is the engine for the code-first path.
Relationship to go-swagger
codescan began life as a single package inside go-swagger and was
spun out into its own go-openapi repository. It is the scanner behind the go-swagger command:
swagger generate spec ./...
For now, go-swagger remains the main command-line consumer of this library.
This site documents the scanner library itself β the layer beneath swagger generate spec β
so it sits upstream of go-swagger’s “generate spec” documentation.
If you arrived here from go-swagger: the annotations are exactly the same, and you can either keep using the
swagger CLI or call codescan.Run directly from your own program
(see Getting started).
Info
codescan now releases standalone CLI tools that ship independently from go-swagger:
genspec, genspec-wasi and genspec-tui.
These are supplementary tools, not a replacement, and go-swagger will continue shipping updates from this library.
Why scan from source
One source of truth. The spec is derived from the code, so it cannot
silently drift from what the service actually exposes.
Fast iteration. Add a field, add its annotation, regenerate β no separate
document to keep in step by hand.
Document what exists. Produce a standards-compliant spec for an API server
that is already deployed, so it becomes interoperable with new clients and
tooling.
When document-level metadata (info, security, servers) is more naturally hand-authored,
you do not have to push it into the code: scan the code for the operations and models,
and overlay the result onto a hand-written base document
(see Shaping the output β Overlaying a spec).
The go-openapi toolkit
go-openapi and go-swagger are community-driven, open-source building blocks meant to be assembled and customized
β there are too many ways to approach APIs to cover them all.
Point genspec at annotated Go source and get a Swagger 2.0 document β on
standard output, in a file, checked against the schema, or as a
machine-readable envelope from a sandbox with no Go toolchain in it.
Drive codescan interactively: browse an annotated source tree, watch the spec it produces.
It re-renders on every save, and follow any node back to the code that made it.
All three drive the same scanner over the same annotations, and every knob is
spelled the same way in each, so the terminal UI shows exactly the document
your build will produce.
The most direct way to use codescan from your own Go program is to import it and call Run.
This supports various use-cases such as a generator, a go:generate step, or a test that keeps your spec in sync with the source.
Install
go get github.com/go-openapi/codescan
codescan exposes a deliberately small surface: a single Run function and an Options struct.
funcRun(opts*Options) (*spec.Swagger, error)
Annotate your source
Annotations are special comments following the go-swagger
convention (swagger:meta, swagger:route, swagger:model, swagger:parameters, swagger:response, β¦).
A package-level swagger:meta block carries the top-level metadata of the spec:
// Package petstore Petstore API//
// A tiny pet store, used to demonstrate codescan annotations: the package// comment is a `swagger:meta` block carrying the top-level metadata of the// generated specification (title, description, version, base path, β¦).//
// Schemes: https// Version: 1.0.0// BasePath: /v1//
// Consumes:// - application/json//
// Produces:// - application/json//
// swagger:meta
A swagger:model annotation turns a Go struct into a definition; field-level
comments become validations and descriptions:
// Pet is a single pet in the store.//
// swagger:model PettypePetstruct {
// The id of the pet.//
// required: true// minimum: 1IDint64`json:"id"`// The name of the pet.//
// required: true// min length: 1Namestring`json:"name"`// The tags associated with this pet.Tags []string`json:"tags,omitempty"`}
The returned *spec.Swagger is the standard
github.com/go-openapi/spec
document β marshal it to JSON or YAML, feed it to a validator, or merge it onto an existing spec via Options.InputSpec.
Overlay: merge discoveries on top of an existing spec.
BuildTags, Include/Exclude
Scope control over what gets scanned.
OnDiagnostic
Where everything the scan observed goes. codescan writes to no stream of its own, so without this the observations are lost.
Those are the ones a first call tends to need. Everything else β alias handling,
$ref siblings, naming, doc-comment cleanup, the loader, the go environment β
is in the Options reference, which gives
each field with its command-line and configuration-file spellings beside it. The
godoc is the normative source.
Dependencies
Code loading relies by default on the go toolchain and this requires go to be installed.
To alleviate this constraint, you may want to use the pure-go ToolchainFreeLoader loader in your options,
so programs that build on top of codescan don’t shell out a go list command.
Next
Scan a package β the whole of the above as
one runnable, test-covered example.
Usage as a headless CLI β the same
scan without writing a program, for a build or a pipeline.
Tutorials β the worked, by-concept version of
the above, each with the spec it produces.
Annotation index β every annotation at a
glance, linked to its example and its full reference.
Maintainers reference β the complete
annotation vocabulary, keywords, and grammar.
Usage as a headless CLI
genspec is the ordinary way to run codescan without writing a program: point it at annotated Go source,
get a Swagger 2.0 document. Everything the library can be told is a flag, the document goes to standard output or to -output,
and what the scan observed goes to standard error.
Install
go install github.com/go-openapi/codescan/cmd/genspec@latest
# scan the module in the current directory, document to standard output
genspec
# somewhere else, narrowed, to a file, and checked
genspec -workdir ../my-api -output swagger.yaml -validate ./internal/api/...
The packages are Go patterns, resolved against -workdir. Naming none scans
./....
Note
genspec does the job of go-swagger’s swagger generate spec, but is released
independently of it β so fixes and new options reach it at codescan’s pace
rather than go-swagger’s. go-swagger has a much larger scope, and the
dependencies that go with it.
Two streams, never mixed
The document goes to standard output; diagnostics go to standard error, colored
when that is a terminal. Nothing is written to standard error when there is
nothing to say. So the obvious pipeline is safe:
This is the command’s doing, not the library’s: codescan itself never writes to
either stream. Every observation it makes β a dropped construct, a rename, a
prune β is passed to the command through a callback, and genspec renders those
callbacks as lines you can read.
Choosing the output
Flag
Effect
-output
the file to write, or - for standard output (the default)
-format
json, yaml, or auto β which reads the extension of -output, and writes JSON when that says nothing
-compact
JSON with no indentation
-input
merge the scan’s discoveries into an existing document
auto is why the common cases need no -format at all:
genspec -output swagger.yaml # YAML, because of the name
genspec -output swagger.json # JSON
genspec > swagger.json # JSON
genspec -compact # JSON with no indentation
YAML is derived from the JSON rendering, which costs key order: the document
comes out alphabetical rather than in the order the spec types declare. Same
information, a different diff against a hand-written file.
-input is the place for everything a scanner cannot know β the host, the
security definitions, a hand-written path the annotations do not describe. The
scan is merged on top of it; see
Overlaying a spec.
Diagnostics
Flag
Effect
-quiet
say nothing at all
-verbose
also report hints, which are muted by default β and say which configuration file was read
-color
auto (a terminal), always, never. auto honours NO_COLOR and TERM=dumb
-validate
check the document against the Swagger 2.0 schema and report what is wrong with it
-fail-on
exit non-zero when something reaches this severity: error, warning, or never
-fail-on covers what -validate found as well as what the scan observed: they reach the reader as one stream,
so a threshold that saw only half of it would be a trap rather than a policy.
It defaults to never, because a scan that emits warnings is the ordinary case,
and a command that failed the build over one would mostly teach people to stop reading them.
Validating: whether the document is legal Swagger 2.0
A scan that reports nothing is not a promise that the document it produced is
valid. The scanner diagnoses what is wrong with your annotations; whether the
result is a legal Swagger 2.0 document is a separate question. Pass -validate
to answer it:
It checks the rendered JSON rather than the document in memory, so what is
validated is exactly what was written β including whatever the round trip
through JSON did to it. The check is go-openapi/validate, the same
one behind swagger validate.
Findings arrive through the same stream as the scan’s own, because to a reader
they are the same kind of news about the same document. Each is located by the
JSON pointer the validator recorded as it walked the spec, so it names the node
rather than a line:
ERROR | paths./orders.post.parameters.1.in in body is required at=/paths/~1orders/post/parameters/1
A finding about something the document lacks β no info block, no paths β
has an empty pointer, which is a location in RFC 6901 and not the absence of
one. It is reported as the whole document rather than printed as nothing:
ERROR | info in body is required at=(the whole document)
genspec: the specification is not valid: 1 finding(s)
Warnings and errors both come out. Only errors make the document invalid: a
warning β an unused definition, say β is worth saying and is not a verdict, and
whether it fails the command is -fail-on’s business rather than the
validator’s.
An invalid document exits 4, and that outranks -fail-on’s 3: it is the
more specific answer. The document is still written either way, so you can look
at what was rejected.
Exit status
A specification is written whenever one could be produced, so a non-zero status
says what was wrong with the document rather than meaning nothing came out.
Status
Meaning
0
the scan produced a document, and nothing asked for more
1
the scan failed
2
the command line does not make sense
3
what was reported reached the severity -fail-on names
4
-validate found the document invalid
An invalid document outranks -fail-on: it is the more specific answer.
Configuring it once
Anything that can be a flag can be preset in a .codescan.yaml, found by
searching upwards from wherever you are β so a project configures itself once
and the command is run bare:
The options naming a path are not among them β -workdir, -output, -input β because a file
found by searching upwards belongs to the tree being scanned, and that tree must not choose where the
command reads or writes. They are typed:
genspec -workdir ./api -output swagger.yaml ./...
Anything typed on the command line wins over it. The file’s full contract β
where it is looked for, what sections exist, -config and --no-config β is in
Setting an option.
The scan itself
genspec -h lists every flag. They fall into the four families a configuration
file uses as its sections:
which code (scan) β -workdir, the positional patterns, -build-tags,
-include / -exclude, -include-tags / -exclude-tags, -exclude-deps
what it is built as (go) β -goos, -goarch, -goflags, -gowork,
-goexperiment. Each decides what compiles, and so what the document says;
each is a flag rather than inherited state, so a scan is reproducible
how it is read (load) β -loader, -stub-stdlib,
-compiled-dependencies
what is emitted (emit) β -scan-models, -prune-unused-models, the
alias and allOf knobs, -skip-extensions, the naming and doc-comment knobs
They are the library’s own options under their own names: a flag is the
kebab-case of the field it writes, without exception. The
Options reference gives each one with its
field, its default and what it does.
Without a Go toolchain
genspec-wasi is the same scan with nothing behind it: it depends on nothing
beyond the library, runs no subprocess, and cross-compiles to wasip1/wasm.
Use it where genspec cannot go β a sandbox, a CI image with no Go in it, a
WebAssembly runtime β and it is the engine behind the
Playground.
go install github.com/go-openapi/codescan/cmd/genspec-wasi@latest
genspec-wasi -workdir ../my-api ./internal/api/...
It carries the same scan flags, but drops genspec’s document and diagnostics
surface: no -validate, no -fail-on, no -color. In their place it offers an
envelope.
An envelope for a program
Document on stdout and prose on stderr suits a person or a pipeline. It is not
enough for a program: prose carries a position only in the sense that one is
printed in it, and provenance β which Go construct produced this spec node β has
nowhere to go at all. -format=json writes one object instead:
Positions are relative to -workdir, so a caller holds models/pet.go and need
not know what the guest called it; a position outside the module stays
absolute, which is how a consumer tells the two apart. Provenance covers anchors
β type declarations, fields, values, route and meta blocks β sorted by pointer,
so the same scan produces the same bytes. Under -format=json standard error
stays clean, so the envelope is safe to read from a pipe.
Experimental, inheriting the status of Options.OnProvenance.
Under a WASI runtime
GOOS=wasip1 GOARCH=wasm go build -o genspec-wasi.wasm ./cmd/genspec-wasi
# wasmtime β <host>::<guest>
wasmtime run --dir "$PWD::$PWD" genspec-wasi.wasm -workdir "$PWD" ./...
# wazero β <host>:<guest>, no separator
wazero run -mount="$PWD:$PWD" genspec-wasi.wasm -workdir "$PWD" ./...
Two things a guest cannot work out for itself:
-goos / -goarch must be passed. Left alone they default to the
platform the scanner is running on, which inside a guest is wasip1 β which
silently drops every _linux.go file and produces a different document than
the same scan run natively.
GOROOT and the module cache are found by path. Nothing in a WASI
environment can ask the go command where they live, so if the scan needs them
they must be mounted and named through the environment.
How much of the host to expose
The real choice is how much of the host the guest may see. Measured on the
petstore fixture under wasmtime:
mounted
mode
time
peak RSS
result
GOROOT + module cache
default
7.3 s
681 MB
identical to a go list scan
module cache
-export-data
1.0 s
138 MB
identical
module cache
-stub-stdlib
1.0 s
147 MB
degraded
project tree only
-stub-stdlib
0.1 s
123 MB
degraded
Memory is usually the binding constraint rather than time β 681 MB for a fixture
this small is more than a browser tab can host.
-export-data reads dependencies from the export data the compiler already
produced instead of parsing and type-checking them. It costs a fraction of the
time with no loss of fidelity, the types being the compiler’s own; it is
valid only for the toolchain that produced it. -stub-stdlib fabricates
standard-library types from the names the code selects, needs no GOROOT at all,
and is the one option here that is not failsafe: recognition by identity
survives (time.Time is still a date-time) but structure does not, so
json.RawMessage stops rendering as a byte array and nothing is seen to
implement encoding.TextMarshaler. Every synthesized import raises a
scan.synthesized-import diagnostic, so the loss is never silent.
Full detail, including a build that embeds its own export data and needs nothing
mounted but the project, is in the
genspec-wasi README.
Next
Setting an option β the configuration file
in full, and which spelling wins.
Usage as a terminal UI β the same scan with
the source and the document side by side, while you write the annotations.
Usage as a terminal UI
genspec-tui is an interactive terminal front-end for codescan.
It puts the Go source on the left, the Swagger document that source produces on the right,
and the scanner’s diagnostics underneath β all regenerated every time you save.
Its reason to exist is that loop: change an annotation, save, see the spec change.
Predicting what an annotation will produce is the slow part of writing one,
and reading a golden file after a build is a poor substitute for watching the node appear.
Beyond the loop, it links the two sides together. You can ask “which Go declaration produced this node?” or
“what did this field turn into?” and get an answer by position β not by matching names by eye.
Note
The TUI is a separate Go module inside the codescan repository, so bubbletea and
its dependency tree never reach the library β installing it pulls none of that
into your own project. Why the commands are split that way is in
The commands.
Install and run
go install github.com/go-openapi/codescan/cmd/genspec-tui@latest
# scan the module in the current directory
genspec-tui
# or point it somewhere, and narrow the scope
genspec-tui -workdir ../my-api ./internal/models/... ./internal/api/...
The selected packages are a positional argument, resolved against -workdir; naming none scans ./....
-packages, taking one comma-separated list, is the older spelling and still works
The TUI registers the same flags as the other commands β one per field of
codescan.Options β and reads the same
.codescan.yaml, so a session starts where
your build leaves off. The flags decide the first scan; almost all of them are
also live toggles, below.
The ones worth knowing at the point of starting a session:
Flag
Default
Meaning
-workdir
.
module directory the scan runs in (WorkDir)
-scan-models
true
also emit definitions for swagger:model types
-build-tags
β
comma-separated build tags to apply while loading
-include / -exclude
β
patterns selecting which packages are scanned
-include-tags / -exclude-tags
β
swagger tags selecting which operations are emitted
-name-from-tags
json
ordered struct tags a field’s name derives from, e.g. form,json for gin. Pass -name-from-tags= (empty) to use the Go field name
-name-concat-budget
0.65
readability cutoff when deconflicting colliding definition names
Note that -scan-models defaults to on here, where the library’s
ScanModels defaults to off. A spec you are browsing in order to see what your
types became has little to show without them.
A second group settles what gets built, and how it is loaded β the environment
go list would read, plus codescan’s own loader:
Flag
Default
Meaning
-goos / -goarch
this machine’s
the platform the scanned code is built for, so build-tagged files are selected the way that platform selects them
-goflags
β
default go command flags, as GOFLAGS β -build-tags wins over a -tags given here
-gowork
search upwards
workspace selection, as GOWORK: off to ignore a go.work, or the path to one
-goexperiment
β
toolchain experiments, as GOEXPERIMENT
-loader
auto
auto runs go list, as every native build does, and picks own only where no subprocess can be started; go always runs go list; own always uses codescan’s own loader, which needs no toolchain (experimental)
-stub-stdlib
false
synthesize the standard library instead of reading GOROOT (needs -loader=own)
-compiled-dependencies
false
take dependency types from the compiler’s export data instead of reading every dependency from source (needs -loader=go). Worth having here more than anywhere: a session rescans on every save, so the build cache is warm after the first one
Everything the flags set is also a live toggle. Press o for the options
popup, space to flip a row, Esc to apply β the spec re-renders on close, which
makes the popup the fastest way to find out what a knob such as EmitRefSiblings
actually changes. Rows that only bite in combination say so: PruneUnusedModels
reads (needs ScanModels) until that one is on.
Being in both places is the point: you can start a session one way and change your
mind without restarting. The one option with no route in at all is InputSpec
(overlay mode).
Scanning: the edit-save-see loop
The left pane starts as the source tree; Enter opens a file into the viewer,
which is read-only and navigable. i turns it into an editor, Ctrl-S saves, a
file watcher notices the write, and the spec re-renders. Editing outside the TUI
works just as well β disk is the source of truth, and F5 re-reads the open file
(asking first, if you have unsaved edits to lose).
A rescan keeps you where you were. The spec cursor is restored to the same
node, not the same line number, so a definition that appears above what you
are reading does not slide you somewhere else. If the node is gone β you deleted
the type β the cursor falls back to its nearest surviving ancestor.
Both panes are syntax-highlighted by the same palette, and in the source viewer a
comment gets three classes rather than one, because in a spec generator a comment
is not uniformly commentary:
Looks like
Reads as
Why
// swagger:model order
a spec key
the annotation declares the thing; it is the input that produced the pane opposite
// required: true
a keyword
grammar the parser acts on, in the class Go’s own type and func get
// the id of the order
dimmed prose
freeform description
What lights up as a keyword comes from the parser’s own table, so aliases
(min β minimum) and letter case are free β and what is highlighted is what
the parser will actually act on.
Tracking: from a spec node back to the code
f turns on a persistent link between panes. The pane you pressed it in is the
driver and keeps focus; the others mirror it on every cursor move,
centring and highlighting the linked line. A SPEC βΈ SOURCE badge names the
direction and the target it resolved to.
It works in three directions β spec β source, source β spec, and a diagnostic to
both. Esc, a second f, changing focus, or starting to edit all leave it.
Two indexes, rebuilt on every render, meet at a JSON pointer: one maps each
rendered spec line to the pointer of the node on it, the other maps pointers back
to Go source positions through codescan’s OnProvenance callback. That is why
the answer is exact rather than a name match.
The gutter marks which lines actually lead somewhere, so you can see what is
navigable without probing for it:
Marker
In the spec pane
In the source viewer
β’
this node has a source position of its own
this line produced a spec node
β
a followable $ref; Enter goes to its definition
β
Only exact anchors are marked. Nearly every line resolves to something through
its nearest anchored ancestor, so marking those would dot the whole document and
tell you nothing.
F3 steps through the places the node under the cursor is referenced, wrapping;
shift+F3 goes back; Enter follows a $ref to its definition.
Diagnostics: what the scanner made of your annotations
Whether your annotations were understood is a different question from whether
the document they produced is well formed, and the pane at the bottom answers
the first one. Under a one-line severity tally it lists everything the scan
observed, in source order, each row carrying its severity’s colour so the pane can
be read for red at a glance. Enter jumps to the source line a finding names and focuses
it; f makes the selection drive both other panes at once.
Findings are also drawn at the site: the token a diagnostic names is
underlined in the severity’s colour, in the source viewer itself. The pane tells
you what and where; the underline tells you which token, without leaving
the line you are reading.
Marks are re-derived on every rescan, so they never outlive the finding that
produced them.
Validating: whether the document is legal Swagger 2.0
v runs the generated document through
go-openapi/validate and lists what it
finds in a validation tab of the diagnostics pane. V switches between that
tab and the scan’s own findings.
This is the second of the two questions above, and answering both is why they are
tabs rather than one list: a scan can be perfectly clean and still produce
something a consumer rejects.
They also track different things. A scan diagnostic carries a source position, so
it drives the source pane. A validation finding carries only a JSON pointer, so
Enter and f there drive the spec pane and nothing else.
The tab exists only once you have pressed v, and a rescan retires it: those
findings judged a document that has just been replaced, and a list of complaints
about a spec that no longer exists invites navigating to nodes that may have
moved or gone. Press v again.
Where a finding lands
A finding carries the JSON pointer the validator recorded as it walked, so
navigation is exact. Indexed paths included β
/paths/~1pets/get/parameters/0/type lands on that parameter, not on the list β
and so are faults reached through a $ref, which are reported against the shared
definition that actually holds them.
A finding about something the document lacks is reported on the value that
should hold it: a response missing its description lands on the response, and a
document missing its info block lands on the document, which Enter takes you
to the top of.
What a scan cost: m
m opens a card describing the run that just finished β how long it took, what
it allocated, what it left live, and how much the process holds from the OS.
Time and memory are split between scanning and rendering the document,
and recapped as ratios on the split line, because the question a reader
arrives with is usually which phase is this? The two can disagree sharply,
which is why both are there.
Allocated counts everything the run churned through, garbage included;
retained counts what it left behind. A scan that allocates half a gigabyte and retains a few
megabytes is not the same problem as one that keeps what it takes, and one
number could not tell you which you have.
Two things the card says about itself, worth repeating: the window is
process-wide, so the redraw loop and the file watcher are in the figures; and a
rescan holds two documents at once, so the retained figure reads high by about
one. That is arithmetic, not a leak.
A scalar cannot say who spent it, which is what -profile is for. Start the
session with it and each scan is profiled as well as timed, so the card also
reports where the CPU went and what allocated it, by function and per phase:
genspec-tui -profile -workdir ../my-api
# every allocation counted rather than sampled every 512 KiB β accurate, and slow
genspec-tui -profile -mem-profile-rate=1 -workdir ../my-api
CPU is charged to the call of ours that led there, not to the leaf frame. A
leaf answers “what was executing”, which for this program is mostly the
allocator and the collector β true, and nothing anyone can act on. Charged our
way, a row names the boundary where codescan hands the work to somebody else’s
code, and covers everything under it.
Profiling is not free, and the card says so rather than letting you compare
across runs by accident: under -profile the scalars at the top carry the
profiler’s own overhead and its collections, while the tables below exclude
them. The card scrolls when it outgrows the terminal (ββ/jk, PgUp/PgDn,
Home/End).
It names the .pprof files it wrote and the go tool pprof commands that open
them. All three flags are addressed in the profile section of a
.codescan.yaml.
Looking up an annotation
With the viewer’s navigation line on a comment carrying a swagger: directive,
K shows what that annotation does: its syntax, a one-line summary, and what may
be written in its body. Clicking the directive does the same.
K because it is vim’s “look up what is under the cursor”, and what LSP clients
bind hover to. It reads the buffer, so it works on an annotation you are
still typing β which is when you want it.
The popup covers all twenty swagger: annotations, and each entry names the
family of keywords its body accepts rather than listing them: there are far too
many individual keywords for a popup to be the right place for them. For those,
and for worked examples of every annotation, the reference on this site is the
long form:
Annotations β one page per
annotation, with grammar and live examples
Keyword reference β every keyword,
grouped by the family it belongs to
Keys worth knowing
The binding surface is context-dependent β f follows from three different
panes, Enter opens a file in the tree but follows a $ref in the spec β so the
header carries a standing h: help banner.
Key
Action
h / ?
the full keymap, grouped by pane
Tab / click
focus a pane (the wheel scrolls whichever pane is under the pointer)
ctrl+arrows
move either divider, in the arrow’s own direction
f
follow mode
K
what the swagger: annotation on this line means
v / V
validate the spec / switch diagnostics tab
o
scanner options
m
what the last scan cost
r / F5
rescan now / re-read the open file from disk
c
copy the focused pane to the clipboard
ctrl+q
quit
Everything else β the per-pane bindings, the editor, the popups β is in the h
overlay and in the
module README,
which documents the internals as well.
Worth knowing before you rely on it
These limits are deliberate; the TUI reports them rather than guessing.
The editor rewrites whitespace on save
bubbles/textarea expands tabs to four spaces when a file is loaded and treats a
lone CR as a line break, and neither has an exported knob. Everything the TUI
shows you agrees with itself because it all reads the same normalised text β but
Ctrl-S writes the buffer, so saving re-indents a tab-indented file with
spaces and rewrites CRLF endings as LF. Edit and save here only when you are
content with that; otherwise edit in your own editor and let the watcher pick the
change up.
Not every node has source. codescan anchors code-detail nodes β type
declarations, fields, values, route and meta blocks β and finer nodes resolve to
their nearest anchored ancestor. A node with no anchored ancestor at all was not
produced from code (an InputSpec overlay node, say); the follower holds
position and says so instead of jumping somewhere plausible.
Positions are as of the last scan. With unsaved edits in the buffer every
anchor below the edit has shifted, so follow shows a STALE badge. Saving
triggers a rescan and clears it.
$ref resolution is a site index, not a resolver. Local #/β¦ refs are
followable; a ref into another file or a URL is reported as external rather
than chased.
Some keys are terminal-dependent.shift+F3 and ctrl+arrow rely on
sequences most modern emulators send and a few (notably inside a default tmux)
do not. Where they are missing nothing misfires β there is simply no
previous-reference key, and no resize keys.
Split sizes last for the session only. They survive rescans and terminal
resizes, but not a restart. The TUI does read a
.codescan.yaml β but what a file can set
is flags, and where the splits sit is not one of them.
Usage as a library β drive the same
scanner from your own program, a go:generate step, or a test.
Usage as a headless CLI β the same
scan in a build, with the settings you converged on here.
Options reference β every knob the o popup
toggles, and what it does to the document.
Tutorials β annotate a package from meta to
definitions, with the TUI open beside you.
Playground
Everything below runs in this tab. There is no server: the scanner is codescan itself, compiled to WebAssembly.
The source you open never leaves your browser.
Experimental
Offered for demonstration.
It follows genspec-tui closely,
and its interface is checked by hand rather than by tests.
Room to work:
What to try
The Examples menu carries five modules, each one whole and each scanning as it stands:
shows
Models
a struct becoming a definition: validations, an enum, an example, a $ref
Routes
swagger:route with its parameters and responses
Operation
swagger:operation, where you write the OpenAPI directly in YAML
Enums
a Go constant set becoming an enum, typed from the declaration
Polymorphism
a discriminated base and its subtypes under swagger:allOf
Edit anything on the left and it rescans after a pause.
Track joins the two panes. Put the cursor on a Go line and the specification
highlights what that line produced; put it on a spec line and the source
highlights what produced it; click a diagnostic and both light up. It answers by
position rather than by matching names, which is why it survives a rename.
One direction is exact and the other is not. The scanner records where a field starts and not where it ends,
so a cursor sitting in a doc comment is attributed to the nearest anchor,
ties resolving downwards because Go documentation sits above what it documents.
Press / in the specification to search it, n and N to step through matches.
The Swagger UI tab renders the document as a reader of your API would see it.
Scanning your own code
Use Open moduleβ¦. Three things are worth knowing first, and the second is
the one nobody guesses:
It has to be a module.go mod init example.com/api if there is no
go.mod. Import paths resolve against the module, and without one there is
nothing to resolve them against.
Vendor the dependencies β go mod vendor. There is no module cache in a
browser and nothing is downloaded, so a dependency’s types can only arrive as
source. That matters more than it looks: a library that declares things in
its comments, as strfmt does with swagger:strfmt, cannot be understood
any other way. Open a module without vendoring and the playground says so
before it scans.
Pick the folder, not the files. The whole directory goes in at once. Test
files are skipped, vendor/ is kept, and the tree is re-rooted on the
outermost go.mod β so it does not matter whether you pick the module, its
parent, or a directory inside it.
The status line reports what each scan cost, in time and in memory. Hovering it
breaks the time into fetching the scanner, compiling it, preparing the
filesystem and scanning, which is how to tell a slow first load from a slow
scan. The scanner is fetched once and then cached, so later scans start
immediately.
The same thing without a browser
genspec-wasi
is the command this page is built around.
It writes a specification to standard output,
and -format=json wraps it with the diagnostics and cross-references that drive the two panes above.
go install github.com/go-openapi/codescan/cmd/genspec-wasi@latest
genspec-wasi -workdir ./my-api ./...
For an interactive scan in a terminal, with the same tracking and the same diagnostics,
use genspec-tui.
Advanced Usage
codescan is a go library that ships with a few utility commands.
These commands are thin wrappers around the library’s scanner.
The library’s behavior is tuned by fields on codescan.Options.
The commands β genspec,
genspec-tui and genspec-wasi β register one flag per field over that same struct.
So every available knob has three spellings for one meaning:
Spelling
Looks like
A Go field
opts.NameFromTags = []string{"form", "json"}
A command-line flag
genspec -name-from-tags form,json
A configuration key
emit: Β Β name-from-tags: [form, json]
The mapping is mechanical: a flag is the kebab-case of the field it writes, without exception,
and a configuration key is the flag, spelled exactly as on the command line, under the section that flag belongs to.
That is why genspec -h doubles as the reference for the file β and why the one
reference below serves all three.
Note
The commands do not each declare that surface: it is written once, and a guard
fails the build when an option lands with no flag β so a knob added to the
library is reachable from all of them at the same moment. How that is arranged is
in The commands.
Every option codescan takes - Go field, command-line flag, configuration key.
Subsections of Advanced Usage
Setting options
There are three ways to set the same knob β a Go field, a command-line flag, a key
in a .codescan.yaml β where the file is looked for, what may be in it, and
which spelling wins when they disagree.
As a Go field
Calling the library, an option is a field on
codescan.Options, passed to Run.
The zero value is a valid configuration β every boolean defaults to false,
every slice to nil, every numeric tunable to its built-in default β so you set only what you need:
opts:=&codescan.Options{
WorkDir: workDir, // module root to resolve patterns fromPackages: []string{"./petstore"}, // relative package patternScanModels: true, // also emit definitions for swagger:model types}
doc, err:=codescan.Run(opts)
iferr!=nil {
returnnil, err}
This is the only spelling that reaches everything: a few options are not
values a command line can carry β a filesystem to read, a document to merge
into, a callback to receive diagnostics. See What has no flag.
As a flag
This is available for every CLI tool shipped: genspec, genspec-tui, genspec-wasi.
The library itself is not bound to command line flags.
Every value-typed option is a flag, on every command:
A flag is the kebab-case of the field it writes, without exception.SkipJSONifyInterfaceMethods is -skip-jsonify-interface-methods; no shorter
spelling is offered, because guessing one is how a caller ends up guessing wrong.
genspec -h lists them all, and the Options reference
gives the flag beside the field for each.
Two shapes do not follow from a field name:
the packages to scan are positional arguments β genspec ./api/... β
so that the command reads like the go commands it resolves patterns for.
Naming none scans ./...;
-loader takes go, own or auto where the field
(ToolchainFreeLoader) is a boolean: the useful default is the third answer,
“whichever one can run here”. On a native build that is go list, so a stock
run loads exactly as it always did; auto picks own only where no subprocess
can be started. That is what lets the same source build for a WASI guest, which
has no process model and so can never run go list.
Commands may add some extra flags that do not affect the scanning itself.
genspec adds its own -output, -validate or -fail-on (these are not library options)
genspec-tui adds a unique -profile flag and related flags to store profiling data
genspec-wasi adds a unique -export-data flag to preload a compiled go standard library
Those are documented with the command that owns them.
As a configuration key
Anything that can be a flag can be preset in a .codescan.yaml, so a project
configures itself once and the command is run bare.
This is available for genspec and genspec-tui β not, for now, for genspec-wasi,
which reads its whole configuration from the arguments its host hands it.
The library itself is not bound to config files.
Keys are grouped into sections, and inside a section a key is the flag it
sets, spelled exactly as on the command line. There is no second vocabulary to
learn and no mapping table to keep in step: genspec -h is the reference for the
file.
What a file may not set
The options naming a path are settable on the command line only: -workdir,
genspec’s -output and -input, and genspec-tui’s -profile-dir.
A file is found by searching upwards, so running a command inside a repository
reads that repository’s file β and a tool whose job is reading somebody else’s
code must not let the code decide where it reads or writes. Everything else a
file sets shapes the document, which is what one is for. Naming a file with
-config does not lift the restriction: the rule belongs to the option, so there
is nothing to remember at the point of use.
The sections
The library’s four are the questions a scan answers, in the order it answers
them. Each command adds its own for the flags that are its business:
Section
The question
Declared by
scan
which code is looked at
the library β every command
go
what it is built as: the go environment that decides what compiles
the library β every command
load
how the packages are read
the library β every command
emit
what the specification ends up saying
the library β every command
document
how the specification is rendered: format, compact
genspec
diagnostics
how loud it is about what it saw: color, quiet, verbose, validate, fail-on
genspec
profile
whether a run is profiled: profile, mem-profile-rate
genspec-tui
Where the file is found
The search walks upwards from where the command was run, so running it from
anywhere inside a project finds the project’s file. It stops at the first hit
rather than merging what it passed: a file half-overridden by another three
directories up is not something anyone can read off the page.
.codescan.yaml, .codescan.yml and .codescan.json are looked for in that
order. JSON is a subset of YAML, so it needs no parser of its own β and a
generated file is as likely to be JSON.
Flag
Effect
-config <path>, -c <path>
read this file, which must exist β a caller who named one meant that file
--no-config
read none, whatever is lying around, for a run that has to be reproducible
Asking for both at once is an error rather than a coin toss. So is -config and
-c naming different files.
Note
The search starts from the current directory, not from -workdir. A file
found through the very directory it was meant to describe would be reasoning in a
circle β which is one of the reasons -workdir is not something a file sets.
Which spelling wins
Flags win over config. That holds for a flag typed with the value it already had:
# false, even where the file says scan-models: true
genspec -scan-models=false
It is decided by asking the flag set which flags were actually seen, not by
comparing values against defaults β which is what makes the rule statable in one
sentence instead of one per flag.
Everything the file sets lands through the same path a command-line argument
takes, so a value is parsed and validated exactly once, and a file cannot
express anything an argument could not. A malformed value is refused the same
way, naming the file.
Between the two, the ordinary defaults apply β with one deliberate divergence:
the commands default -scan-models to true where the library field is
false. A command asked to produce a specification and handed a package of
annotated models should produce their definitions.
One file, several commands
There is one file name for the whole family, not one per command: a project
configuring a scan has configured it for all of them. What tells them apart is
the sections.
A section a command does not recognize is skipped, not rejected β that is how
genspec-tui’s profile: settings sit beside genspec’s document: in the
same file.
A key inside a section it does know must name one of its flags. That is what
makes a typo an error rather than a setting that quietly never applied.
Run genspec -verbose to see which file was read and which keys were skipped.
What has no flag
Four options are not values a command line can carry.
The command owns them instead β and each of the commands makes its own choice about how,
which is why they are not in the shared table:
Option
Reached by
InputSpec
genspec -input <file> (document.input) β the command loads the document
FS
no flag: a filesystem is a Go value. The commands read the real one; the Playground hands the browser’s
ExportData
genspec-wasi -export-data <dir|zip> β the command opens the path. This option is specific to the wasi CLI for now
OnDiagnostic, OnProvenance
no flag: the commands wire the sinks. genspec prints diagnostics to standard error; genspec-wasi -format=json puts both in its envelope. Custom hooks are only available to the library.
Two more fields carry no flag because nothing should be reaching for them:
DescWithRef and Debug are deprecated.
The zero value is a valid configuration β every flag defaults to false, every slice/map to nil,
every numeric tunable to its built-in default. You set only what you need.
This page is the field-by-field catalogue, and serves the commands as much as the
library: Flag gives the text to type, and Section gives the .codescan.yaml
section addressing it β the key inside that section is the flag without its dash. See
Setting an option for the rules those two
columns follow, and for the handful of options that are nobody’s flag.
The godoc is the
normative source; each field here links to the
how-to guide that shows it on real
input where one exists.
Note
Default is the library’s. The commands agree with it everywhere but one:
-scan-models defaults to true, because a command asked for a specification
and handed a package of annotated models should produce their definitions.
Note
Config Section names the .codescan.yaml section addressing the option. A dash
means it has none: either the option is not a value a file can carry (a callback, a
positional argument), or it names a path, which is settable on the command
line only. See what a file may not set.
Note
codescan never writes to stdout or stderr. Every scan-time observation β a
dropped construct, a rename, a prune β flows through the OnDiagnostic
callback. See Diagnostics & observability below.
Inputs & scope
What gets loaded and which packages and types are in play. See
Scope & discovery.
Option
Type
Default
Flag
Config Section
Effect
Packages
[]string
nil
(positional)
β
Package patterns to scan (e.g. ./...), resolved relative to WorkDir.
WorkDir
string
"" (cwd)
-workdir
β
Working directory the package patterns and module resolution are rooted at. Command line only: see what a file may not set.
BuildTags
string
""
-build-tags
scan
Go build tags to activate while loading, so tag-guarded source is scanned. See Build tags.
Include
[]string
nil
-include
scan
Allow-list of package path patterns; when non-empty only matching packages are scanned. See Scoping the scan.
Exclude
[]string
nil
-exclude
scan
Deny-list of package path patterns, applied after Include. See Scoping the scan.
IncludeTags
[]string
nil
-include-tags
scan
Allow-list filtering routes/operations by their swagger tags.
ExcludeTags
[]string
nil
-exclude-tags
scan
Deny-list filtering routes/operations by their swagger tags.
ExcludeDeps
bool
false
-exclude-deps
scan
Skip types reached through module dependencies, keeping the scan to first-party packages.
ScanModels
bool
false
-scan-models
emit
Also emit a definition for every swagger:model type, not just route-reachable ones. See When the scanner emits a type.
PruneUnusedModels
bool
false
-prune-unused-models
emit
With ScanModels, drop discovered definitions not transitively reachable from a path, shared response/parameter, or InputSpec root. Runs before name reduction; InputSpec definitions are pinned. No-op without ScanModels. See Pruning unused models.
InputSpec
*spec.Swagger
nil
-input(genspec)
document
Base document to overlay scanned discoveries onto; its definitions are pinned and seed pruning roots. See Overlaying a spec.
Loading & the go environment
Where the package graph comes from, and which platform it is built for. These
change the emitted spec the way BuildTags does β by deciding which files each
package is made of β or change what the scan needs in order to run at all.
They are options rather than inherited process state so that a scan is
reproducible: a value picked up from whatever shell started it is easy to apply on
one code path and forget on another.
Which loader, and why
Three ways to get a package graph. The table below catalogues the fields; this is
how to choose between them.
Standard loader β the default. Loads your code with the Go toolchain, through
golang.org/x/tools/go/packages. Maintained by the Go team, and the reference for
how patterns and imports resolve: where either of the others disagrees with it, the
other one is wrong. Requires Go installed, and uses the build cache.
Pure-Go loader (ToolchainFreeLoader) β loads your code with codescan’s own
reimplementation. Cuts memory by roughly 45%, and needs no go command and no
subprocess. It still reads GOROOT/src for the standard library, so it wants a Go
installation β just not a runnable toolchain. Modules only. It uses no build
cache, so cold costs what warm costs: about level with the standard loader on a
warm cache, roughly 30% faster on a cold one, and the only choice whose cost does
not depend on cache state. Usually the right pick for CI.
Compiled dependencies (CompiledDependencies) β the standard loader taking
dependency types from the compiler’s export data instead of reading their source. It
produces the same document either way β whatever the spec needs out of a dependency
is read at the moment it is needed β and on a warm build cache it is the fastest by
a wide margin, and several times smaller.
It must compile the dependency closure rather than type-check it, so on a cold
cache it is an order of magnitude slower and writes a large build cache. Reach for
it where the cache is warm by construction β your own machine, a watch loop, a
pipeline that restores its cache β and leave it off where a clean checkout is the
norm, as a CI runner usually is. Code that does not compile is not a
reason to avoid it: such a load is retried from source automatically.
Two further options drop the GOROOT requirement altogether, for environments with
no Go installation at all β a WASI guest, a browser. StubStdlib synthesizes the
standard library, and pays for the reach in fidelity: a fabricated type has the
right name and no structure. ExportData serves dependencies from a blob you
prepare in advance, and pays in preparation instead β the types are the compiler’s
own, but the blob is only valid for the toolchain that produced it, and a package
it does not cover falls back to source and then to synthesis.
Note
The percentages are indicative, not a promise: the balance moves with the size of
the tree being scanned, and on a small one the pure-Go loader is slower warm than
the standard loader. The figures, the corpora they were taken on and the method are in
internal/benchmarks,
whose harness also takes an extra corpus of your own to measure alongside them.
Option
Type
Default
Flag
Section
Effect
GOOS / GOARCH
string
"" (this machine)
-goos / -goarch
go
The platform the scanned code is built for. //go:build lines and _linux.go / _amd64.go filename suffixes resolve against them, so they select which files a package is made of.
GOFLAGS
string
"" (process env)
-goflags
go
Default go command flags, e.g. -tags=integration. Flags given through BuildTags win, as they do for the go command.
GOWORK
string
"" (search upwards)
-gowork
go
Workspace selection: off disables it, a path names a go.work. Inside a workspace a sibling module resolves to the copy being worked on rather than to the module cache β miss that and its types are read stale, or synthesized empty.
GOEXPERIMENT
string
"" (process env)
-goexperiment
go
Toolchain experiments, e.g. jsonv2; each contributes a goexperiment.<name> build tag.
ToolchainFreeLoader
bool
false
-loader=own
load
Resolve the package graph with codescan’s own loader instead of golang.org/x/tools/go/packages. Same job and, across the fixture corpus, the same spec; it differs in needing no installed toolchain and no subprocess, since it never runs go list. Experimental.
FS
fs.FS
nil
β
β
Read source through a virtual filesystem β an in-memory tree, an uploaded archive, an embed.FS β instead of the real one. Implies ToolchainFreeLoader, since go list can only read the real filesystem. FS is the whole world the scan can read: dependencies and GOROOT come through it too, absolute paths map by dropping the leading separator, and anything unreachable is synthesized β a valid but quietly thinner spec, announced by scan.synthesized-import and scan.degraded-load. Experimental.
StubStdlib
bool
false
-stub-stdlib
load
Synthesize the standard library from the names the code selects, rather than reading GOROOT. Toolchain-free loader only. Identity recognition still works (time.Time, json.RawMessage are matched on package and name), but a synthesized type has no fields and no method set β so json.RawMessage stops rendering as a byte array and nothing is seen to implement encoding.TextMarshaler. Trades fidelity for reach, quietly; prefer a full graph where GOROOT is available. Experimental.
ExportData
fs.FS
nil
-export-data(genspec-wasi)
β
Serve dependencies from pre-computed export data (one <import path>.export file per package) under the toolchain-free loader. Unlike StubStdlib this costs no fidelity, the types being the ones the compiler computed β but it is valid only for the toolchain that produced it, and an uncovered package falls back to source, then to synthesis. The module under scan is never read this way, and neither is a dependency whose source carries annotations or one the spec later needs a declaration from. Experimental.
CompiledDependencies
bool
false
-compiled-dependencies
load
Take dependency types from the compiler’s export data instead of reading every dependency from source, under the go/packages loader. It costs no meaning: a dependency whose source carries annotations is read back after the load, and one that merely declares a type the spec carries is read at the lookup that wants it β so a swagger:strfmt written in a library still counts, and a model declared in an unannotated dependency keeps its doc comment and its fields. Set it for cost alone, and only where the build cache is warm: it is markedly faster warm and markedly slower cold, since go list -export compiles the closure before it can read it. A closure that does not compile is handled either way β the load falls back to source and raises scan.compiled-dependencies.
Note
The virtual-filesystem and export-data options exist to make a scan possible
where no Go toolchain is present: they let codescan run compiled to
WebAssembly. See the Playground.
Names & references
How definitions are named and how $refs render. See
Names & $refs.
Option
Type
Default
Flag
Section
Effect
NameFromTags
[]string
nil (β ["json"])
-name-from-tags
emit
Ordered struct-tag types a property/parameter/header name is derived from; first that supplies a name wins. Explicit empty slice β Go field name. Only the name β json encoding directives (-, ,omitempty, ,string) always come from json. See Naming from struct tags.
SkipJSONifyInterfaceMethods
bool
false
-skip-jsonify-interface-methods
emit
Emit interface-method property names verbatim (ID, CreatedAt) instead of auto-jsonifying them (id, createdAt). Only affects interface methods; struct fields and swagger:name overrides are unchanged. See Interface-method property names.
RefAliases
bool
false
-ref-aliases
emit
Render Go type aliases as a first-class $ref (via swagger:model) instead of expanding them inline. See Alias rendering.
TransparentAliases
bool
false
-transparent-aliases
emit
Make aliases fully transparent β never creating a definition. See Alias rendering.
DefaultAllOfForEmbeds
bool
false
-default-all-of-for-embeds
emit
Render a plain (untagged, unnamed) struct embed as an allOf member β a $ref for a model embed, an inline member otherwise β with the embedding struct’s own fields in a sibling member, instead of inlining promoted properties. json-named embeds, swagger:allOf embeds, and interface embeds are unaffected. See Composing embeds with allOf.
NameConcatBudget
float64
0 (β 0.65)
-name-concat-budget
emit
Readability cutoff [0,1] for the package-segment concatenation that deconflicts colliding definition names; lower scores are more readable. A group whose best concat scores above the budget is a candidate for the hierarchical fallback. See Resolving $ref name conflicts.
EmitHierarchicalNames
bool
false
-emit-hierarchical-names
emit
For the rare collision group whose best flat concat exceeds NameConcatBudget, emit nested container definitions (#/definitions/<pkg>/<Name>) instead of a long flat concat, with an explanatory diagnostic. The always-correct flat concat is the default. See Resolving $ref name conflicts.
EmitRefSiblings
bool
false
-emit-ref-siblings
emit
Emit a $ref’d field’s description and vendor extensions as direct $ref siblings ({$ref, description, x-*}) instead of an allOf wrap. Validations/externalDocs still force a compound. See Descriptions beside a $ref.
SkipAllOfCompounding
bool
false
-skip-all-of-compounding
emit
Never emit an allOf compound for a $ref’d field. Validations/externalDocs are dropped (description/extensions too, unless EmitRefSiblings keeps them as siblings); each drop raises a diagnostic. required is unaffected. See Descriptions beside a $ref.
DescWithRef
bool
false
β
β
Deprecated β prefer EmitRefSiblings. In the description-only case, wrap the $ref in a single-arm allOf to preserve the description (strict draft-4 shape). No-op when EmitRefSiblings is set. See Descriptions beside a $ref.
Route every single-line doc comment to description, never to title/summary (the first-sentence convention otherwise applies). Multi-line comments keep the title/description split. See Single-line comments as descriptions.
AfterDeclComments
bool
false
-after-decl-comments
emit
Let swagger annotations live inside a struct body or as a trailing comment, in addition to the doc comment above the declaration, so the godoc stays clean. v0.36 scope: type declarations (struct inside-body + alias trailing comment). See Keeping annotations out of the godoc.
CleanGoDoc
bool
false
-clean-go-doc
emit
Strip godoc doc-link brackets from generated title/description (humanizing unresolved ones, dropping reference-definition lines, recomposing resolved links to each schema’s exposed name). Applies only to godoc-derived prose; overrides are untouched. See Cleaning godoc doc-links.
Emit x-nullable: true on pointer-typed fields. See Nullable pointers.
SkipExtensions
bool
false
-skip-extensions
emit
Suppress all x-go-* vendor extensions in the output. See Vendor extensions.
EmitXGoType
bool
false
-emit-x-go-type
emit
Stamp an x-go-type extension (fully-qualified originating Go type) on every emitted definition, for round-tripping a spec back to its Go types. Suppressed under SkipExtensions. See Vendor extensions.
SkipEnumDescriptions
bool
false
-skip-enum-descriptions
emit
Keep the per-enum-value const-name mapping (from swagger:enum) out of the description, exposing it only via the x-go-enum-desc extension. Suppressed entirely under SkipExtensions.
Diagnostics & observability
Channels for what the scan observed; these do not change the output spec.
Option
Type
Default
Flag
Section
Effect
OnDiagnostic
func(Diagnostic)
nil
β
β
Invoked once per diagnostic in source order (parser warnings, validation failures, prunes, renames). Diagnostics never block the build β invalid constructs are dropped from the spec while their explanation flows here. The only output channel. Experimental while LSP integration matures.
OnProvenance
func(Provenance)
nil
β
β
Invoked once per anchor node in the produced spec, carrying its JSON pointer and the source position of the Go construct that produced it. Never blocks the build. Experimental while LSP/TUI integration matures.
Debug
bool
false
β
β
Deprecated, ignored. The legacy stderr debug logger was retired; wire OnDiagnostic instead. Retained for API compatibility.
The two callbacks are how the commands report: genspec renders diagnostics on
standard error, and genspec-wasi -format=json carries both in its envelope for
a program to read.
See also
Setting an option β the flag and key rules
the two middle columns follow, and which spelling wins.
Annotations β the swagger:* vocabulary the
scanner reads from comments.
Keyword reference β the keyword: value forms
inside annotation bodies.
Shaping the output β task-oriented
how-tos that put these options to work on real input.
Project
Everything else on this site is about using codescan. This section is about the repository it
comes from β who publishes it, under what licence, and where to send a patch.
codescan follows the conventions of every go-openapi repository rather than inventing its own, so
how to contribute to it is documented once, for all of them, in the shared doc-site:
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Tutorials
These tutorials teach codescan by spec concept, not annotation by annotation.
Each page takes one thing you want in your OpenAPI document β a model definition, a route, a validated field
β and shows the Go annotation that produces it next to the resulting JSON, side by side.
Every Go snippet on these pages comes from the test-covered
docs/examples
module, and every JSON pane is a golden file a test regenerates β so what you see published here has been tested.
Reading the panes
The example panes put the annotation in on the left and the spec concept
out on the right:
Annotated Go
// Pet is a single pet in the store.//
// swagger:model PettypePetstruct {
// The id of the pet.//
// required: true// minimum: 1IDint64`json:"id"`// The name of the pet.//
// required: true// min length: 1Namestring`json:"name"`// The tags associated with this pet.Tags []string`json:"tags,omitempty"`}
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.
How Go maps render as objects, which key types survive, and how to control a
schema’s open/closed/typed extra keys with additionalProperties and
patternProperties.
Declare a parameter or response once and reuse it across operations through
the spec-level shared namespace, with the wildcard swagger:parameters and
swagger:response forms.
Drive JSON-Schema validations from field doc comments β numeric ranges,
length and array bounds, patterns, formats, and enums β and understand the
reduced surface on parameters and headers.
The smallest end-to-end use of codescan: annotate a package, scan it, and
get back a Swagger 2.0 document.
When you want the exhaustive rule rather than an example, every page links into
the Maintainers reference; the
Annotation index maps every annotation to
both.
Subsections of Tutorials
Scan a package
This tutorial scans a tiny annotated “petstore” package and produces a Swagger
2.0 spec. It is the worked version of
Usage as a library β start here, then take
one concept at a time from the pages that follow.
The annotated API
A package-level swagger:meta block sets the top-level metadata:
// Package petstore Petstore API//
// A tiny pet store, used to demonstrate codescan annotations: the package// comment is a `swagger:meta` block carrying the top-level metadata of the// generated specification (title, description, version, base path, β¦).//
// Schemes: https// Version: 1.0.0// BasePath: /v1//
// Consumes:// - application/json//
// Produces:// - application/json//
// swagger:meta
A swagger:model struct becomes a definition, with field comments driving
validations:
// Pet is a single pet in the store.//
// swagger:model PettypePetstruct {
// The id of the pet.//
// required: true// minimum: 1IDint64`json:"id"`// The name of the pet.//
// required: true// min length: 1Namestring`json:"name"`// The tags associated with this pet.Tags []string`json:"tags,omitempty"`}
Marshalling the returned *spec.Swagger to JSON yields the document below β
the meta block became the top-level info / basePath, the swagger:route
became the /pets path, and the swagger:model became the Pet definition:
{
"consumes": [
"application/json" ],
"produces": [
"application/json" ],
"schemes": [
"https" ],
"swagger": "2.0",
"info": {
"description": "A tiny pet store, used to demonstrate codescan annotations: the package\ncomment is a `swagger:meta` block carrying the top-level metadata of the\ngenerated specification (title, description, version, base path, β¦).",
"title": "Petstore API",
"version": "1.0.0" },
"basePath": "/v1",
"paths": {
"/pets": {
"get": {
"tags": [
"pets" ],
"summary": "Lists all the pets in the store.",
"operationId": "listPets",
"responses": {
"200": {
"$ref": "#/responses/petsResponse" }
}
}
}
},
"definitions": {
"Pet": {
"type": "object",
"title": "Pet is a single pet in the store.",
"required": [
"id",
"name" ],
"properties": {
"id": {
"description": "The id of the pet.",
"type": "integer",
"format": "int64",
"minimum": 1,
"x-go-name": "ID" },
"name": {
"description": "The name of the pet.",
"type": "string",
"minLength": 1,
"x-go-name": "Name" },
"tags": {
"description": "The tags associated with this pet.",
"type": "array",
"items": {
"type": "string" },
"x-go-name": "Tags" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/petstore" }
},
"responses": {
"petsResponse": {
"description": "petsResponse is the list of pets returned by listPets.",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet" }
}
}
}
}
This JSON is not hand-written: it is a golden file the example’s test
regenerates and compares on every run (UPDATE_GOLDEN=1 go test ./...). Because
the example is ordinary, test-covered Go, go test ./docs/examples/... keeps
the page honest β if the scanner’s output changes, CI fails before the
documentation can go stale.
Model definitions
A definitions entry is the most common thing you ask codescan to produce. This
page walks the annotations that create and shape one, from the plain
swagger:model struct to the per-type overrides. Each pane pairs the annotated
Go (left) with the exact fragment the scanner emits (right) β both come from the
test-covered docs/examples/concepts/models
package.
For the exhaustive rule on any annotation below, follow its link to the
Maintainers reference.
swagger:model
swagger:model publishes a Go struct as a definition. Field doc comments become
property descriptions; json tags drive the property names; the Go type drives
the JSON-Schema type / format. Well-known standard-library types resolve
automatically β a time.Time field, for instance, is published as
{type: string, format: date-time}.
UUIDs are recognised two different ways, and both publish
{type: string, format: uuid}:
by type identity β the standard-library uuid.UUID introduced in Go 1.27,
matched on its import path and name, so nothing else can be mistaken for it;
by type name β any other type named UUID (case-insensitively) that
marshals as text, which covers github.com/google/uuid, gofrs/uuid,
strfmt.UUID and the like.
The name-based rule is a heuristic and stays available whichever Go version you
build with. Either way an explicit swagger:strfmt on the type
wins, so you can always overrule the recognition.
Annotated Go
// Pet is a single pet in the store.//
// swagger:modeltypePetstruct {
// ID is the unique identifier.IDint64`json:"id"`// Name is the pet's display name.Namestring`json:"name"`// Tags categorise the pet.Tags []string`json:"tags,omitempty"`}
A swagger:model that nothing else references appears in definitions only
when you scan with Options.ScanModels (the -m flag). See
When the scanner emits a type.
swagger:strfmt
swagger:strfmt <name> marks a named string type as a custom format. The type
does not become its own definition β instead, every field typed by it renders as
{type: string, format: <name>}.
Annotated Go
// MAC is a hardware address rendered as a colon-separated hex string.//
// swagger:strfmt mactypeMACstringfunc (mMAC) MarshalText() ([]byte, error) { return []byte(m), nil }
func (m*MAC) UnmarshalText(b []byte) error { *m = MAC(b); returnnil }
// Device exposes a strfmt-typed field: wherever MAC appears it renders inline// as {type: string, format: mac}.//
// swagger:modeltypeDevicestruct {
// Addr is the hardware address.AddrMAC`json:"addr"`}
Add swagger:model to the strfmt type to opt it into a first-class definition
({type: string, format: <name>}) that fields $ref instead of inlining β the
general swagger:model β definition + $ref rule.
swagger:enum
swagger:enum <name> collects the type’s const values. When a model field
references the type, the property carries the enum array and an
x-go-enum-desc extension built from the per-value doc comments. (The enum type
is reachable, and so emitted, only because a model field points at it.) A bare
swagger:enum on the type declaration works too β the name is inferred.
Annotated Go
// Priority is the urgency level on a task.//
// swagger:enum PrioritytypePrioritystringconst (
// PriorityLow is for tasks that can wait.PriorityLowPriority = "low"// PriorityMedium is the default.PriorityMediumPriority = "medium"// PriorityHigh is for tasks that must run soon.PriorityHighPriority = "high")
// Task is a unit of work carrying an enum-typed field. Referencing Priority// from a model is what makes the enum reachable, and so emitted.//
// swagger:modeltypeTaskstruct {
// Priority is the task's urgency.PriorityPriority`json:"priority"`}
{
"description": "Task is a unit of work carrying an enum-typed field. Referencing Priority\nfrom a model is what makes the enum reachable, and so emitted.",
"type": "object",
"properties": {
"priority": {
"description": "Priority is the task's urgency.\nlow PriorityLow is for tasks that can wait.\nmedium PriorityMedium is the default.\nhigh PriorityHigh is for tasks that must run soon.",
"type": "string",
"enum": [
"low",
"medium",
"high" ],
"x-go-enum-desc": "low PriorityLow is for tasks that can wait.\nmedium PriorityMedium is the default.\nhigh PriorityHigh is for tasks that must run soon.",
"x-go-name": "Priority" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
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.
swagger:allOf
Embedding base types under swagger:allOf composes a schema. Each embedded base
becomes a $ref arm of the allOf; the struct’s own (non-embedded) fields form
a final inline arm. That last arm is inline rather than a $ref because those
fields are new to this type β they belong to no existing definition to point at.
Here Dog embeds two bases (Animal, Tagged) and adds breed, producing
three arms: two $refs and one inline object.
Annotated Go
// Animal is one abstract base.//
// swagger:modeltypeAnimalstruct {
// Kind discriminates the animal.Kindstring`json:"kind"`}
// Tagged is a second reusable base.//
// swagger:modeltypeTaggedstruct {
// Tags label the resource.Tags []string`json:"tags"`}
// Dog composes two base models plus its own fields: each embedded base becomes// a $ref arm of the allOf, and the struct's own (non-embedded) fields β which// are new and cannot be a $ref β form the final inline arm.//
// swagger:modeltypeDogstruct {
// swagger:allOfAnimal// swagger:allOfTagged// Breed is the dog's breed.Breedstring`json:"breed"`}
{
"description": "Dog composes two base models plus its own fields: each embedded base becomes\na $ref arm of the allOf, and the struct's own (non-embedded) fields β which\nare new and cannot be a $ref β form the final inline arm.",
"allOf": [
{
"$ref": "#/definitions/Animal" },
{
"$ref": "#/definitions/Tagged" },
{
"type": "object",
"properties": {
"breed": {
"description": "Breed is the dog's breed.",
"type": "string",
"x-go-name": "Breed" }
}
}
],
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
When the base also declares a discriminator, this composition becomes a
Swagger 2.0 type hierarchy β see
Polymorphic models.
swagger:type
swagger:type <type> overrides the type codescan would infer. Here a [16]byte
field is published as a string.
Annotated Go
// ULID is a 128-bit identifier stored as bytes but rendered as a string.//
// swagger:type stringtypeULID [16]byte// Token carries a field whose inferred type is overridden, inline.//
// swagger:modeltypeTokenstruct {
// ID renders as a string despite its [16]byte Go type.IDULID`json:"id"`}
// RawID is a custom 16-byte identifier β an array under the hood, so left to// itself a field of this type would render as an array of integers.typeRawID [16]byte// Coupon overrides the type of a single field directly on the field doc β no// wrapper-type annotation. Code publishes as a bare string while RawID is left// untouched everywhere else it appears.//
// swagger:modeltypeCouponstruct {
// Code is an opaque identifier published as a string.//
// swagger:type stringCodeRawID`json:"code"`// Amount is the discount in cents.Amountint64`json:"amount"`}
{
"type": "object",
"title": "Token carries a field whose inferred type is overridden, inline.",
"properties": {
"id": {
"description": "ID renders as a string despite its [16]byte Go type.",
"type": "string",
"x-go-name": "ID" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
swagger:type is an inline directive: it renders the chosen type in place
and never emits a $ref. The value is a scalar type (string, integer,
number, boolean, object, or a Go builtin like int64), []T for an array
of an inlined type, inline to expand the field’s own Go type, or a known type
name to inline that type. array is deprecated in favour of inline / []T,
and file is rejected (use swagger:file). When combined with swagger:strfmt,
the type wins and the format is kept only if compatible β see the
reference.
The override also works on an individual field doc β no wrapper-type
annotation. Here Code is published as a string while its RawID type is left
untouched everywhere else:
Annotated Go
// RawID is a custom 16-byte identifier β an array under the hood, so left to// itself a field of this type would render as an array of integers.typeRawID [16]byte// Coupon overrides the type of a single field directly on the field doc β no// wrapper-type annotation. Code publishes as a bare string while RawID is left// untouched everywhere else it appears.//
// swagger:modeltypeCouponstruct {
// Code is an opaque identifier published as a string.//
// swagger:type stringCodeRawID`json:"code"`// Amount is the discount in cents.Amountint64`json:"amount"`}
{
"description": "Coupon overrides the type of a single field directly on the field doc β no\nwrapper-type annotation. Code publishes as a bare string while RawID is left\nuntouched everywhere else it appears.",
"type": "object",
"properties": {
"amount": {
"description": "Amount is the discount in cents.",
"type": "integer",
"format": "int64",
"x-go-name": "Amount" },
"code": {
"description": "Code is an opaque identifier published as a string.",
"type": "string",
"x-go-name": "Code" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
swagger:name <name> overrides the JSON property name a field or method
renders as. It works on a struct field (overriding the json: tag / Go
field name) and on an interface method. It is most useful on interface
methods, which publish one property per nullary method and cannot carry a
json tag: below, StructType() would default to structType, and the
annotation publishes it as jsonClass instead.
Note
swagger:name is the legacy annotation form. The
name: keyword is the
canonical, universal equivalent β it renames a property here exactly the
same way, and is the only form that also works on parameters and
response headers. Precedence: name: > swagger:name > json: tag > Go
field name.
Annotated Go
// Car is exposed as a schema via its method set. Interface methods cannot carry// a json tag, so by default each property takes the camelCased method name;// swagger:name overrides that where the default is not what you want.//
// swagger:modeltypeCarinterface {
// Maker is the manufacturer. With no override the property is the// camelCased method name, "maker".Maker() string// StructType is the polymorphic class. Without the override the property// would be "structType"; swagger:name publishes it as "jsonClass".//
// swagger:name jsonClassStructType() string}
// Account shows the universal name: keyword renaming model struct fields. The// same keyword used on parameters and response headers also sets a property key// here, winning over a json tag, the legacy swagger:name annotation, and the Go// field name.//
// swagger:modeltypeAccountstruct {
// Bal has no json tag; the keyword sets the property key directly.//
// name: balanceBalfloat64// Currency carries both naming forms; the keyword wins over the// legacy annotation and the json tag.//
// name: currencyCode// swagger:name legacyCurrencyCurrencystring`json:"currency"`}
{
"description": "Car is exposed as a schema via its method set. Interface methods cannot carry\na json tag, so by default each property takes the camelCased method name;",
"type": "object",
"properties": {
"jsonClass": {
"description": "StructType is the polymorphic class. Without the override the property\nwould be \"structType\"; swagger:name publishes it as \"jsonClass\".",
"type": "string",
"x-go-name": "StructType" },
"maker": {
"description": "Maker is the manufacturer. With no override the property is the\ncamelCased method name, \"maker\".",
"type": "string",
"x-go-name": "Maker" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
The name: keyword does the same on a model property β and, being the universal
form, with the same syntax you would use on a parameter or header. Here it names
a tag-less field directly, and on a field that also carries a json: tag and a
legacy swagger:name, the keyword wins (name: > swagger:name > json: tag >
Go field name):
Annotated Go
// Account shows the universal name: keyword renaming model struct fields. The// same keyword used on parameters and response headers also sets a property key// here, winning over a json tag, the legacy swagger:name annotation, and the Go// field name.//
// swagger:modeltypeAccountstruct {
// Bal has no json tag; the keyword sets the property key directly.//
// name: balanceBalfloat64// Currency carries both naming forms; the keyword wins over the// legacy annotation and the json tag.//
// name: currencyCode// swagger:name legacyCurrencyCurrencystring`json:"currency"`}
{
"description": "Account shows the universal name: keyword renaming model struct fields. The\nsame keyword used on parameters and response headers also sets a property key\nhere, winning over a json tag, the legacy swagger:name annotation, and the Go\nfield name.",
"type": "object",
"properties": {
"balance": {
"description": "Bal has no json tag; the keyword sets the property key directly.",
"type": "number",
"format": "double",
"x-go-name": "Bal" },
"currencyCode": {
"description": "Currency carries both naming forms; the keyword wins over the\nlegacy annotation and the json tag.",
"type": "string",
"x-go-name": "Currency" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
swagger:ignore drops a declaration from the output. The scanner sees Secret,
classifies it, then excludes it β so it never reaches the definitions (a fact
the example’s TestIgnoreOmitsType asserts). It also works on a single struct
field β placed on the field’s doc comment, it drops just that property from
the model.
// Secret never reaches the spec.//
// swagger:ignoretypeSecretstruct {
// Token is internal.Tokenstring`json:"token"`}
When a field’s type resolves to a named model, the property is a $ref β and a
bare $ref cannot carry sibling keywords (a JSON Schema draft-4 rule). So a
field that is both a reference and decorated (a description, a vendor
extension, a default/example, a validation override) would lose its
decorations.
codescan avoids that by wrapping the $ref as a member of an allOf. The
property is then an ordinary schema object, free to carry the decorations
alongside the reference:
the description and any x-*extensions sit on the property itself;
a value override (default, example) rides a secondallOf member;
required rides the parent model’s required list.
Annotated Go
// Address is a referenced model.//
// swagger:modeltypeAddressstruct {
// Street is the street line.Streetstring`json:"street"`}
// Person references Address through a field that also carries a description and// a vendor extension. A bare $ref cannot hold sibling keywords, so codescan// wraps the reference as a member of an allOf β the property is then a normal// schema that keeps the description and the x-* extension, and required goes to// the parent. Nothing is dropped.//
// swagger:modeltypePersonstruct {
// Home is where the person lives.//
// required: true// extensions:// x-ui-order: 3HomeAddress`json:"home"`}
{
"description": "Person references Address through a field that also carries a description and\na vendor extension. A bare $ref cannot hold sibling keywords, so codescan\nwraps the reference as a member of an allOf β the property is then a normal\nschema that keeps the description and the x-* extension, and required goes to\nthe parent. Nothing is dropped.",
"type": "object",
"required": [
"home" ],
"properties": {
"home": {
"description": "Home is where the person lives.",
"allOf": [
{
"$ref": "#/definitions/Address" }
],
"x-go-name": "Home",
"x-ui-order": 3 }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/refoverride"}
Nothing is dropped. The description-only case is the exception β it is governed
by the DescWithRef option (see
Descriptions beside a $ref) β
and a default/example on a $ref’d field is shown in
Examples & defaults.
Info
Same-name collisions. Two structs that share the same short name in
different packages stay distinct: codescan keys each by a compiler-unique
identity and qualifies the colliding ones with a package segment
(billing.Account / identity.Account β BillingAccount / IdentityAccount),
deterministically. Pin the names in your public contract with an explicit
swagger:model <Name>, and let auto-resolution handle the rest β see
Resolving $ref name conflicts.
What’s next
Routes & operations β wire
these models into paths, parameters and responses.
Validations β constrain field values
with keyword annotations.
Shaping the output β alias handling,
$ref vs inline, nullable pointers and more.
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.
swagger:enum and enum: are two different things
They produce the same spec keyword from opposite directions, and the names are
close enough to trip over:
swagger:enum β an annotation
enum: β a keyword
Where
on the type declaration
inside any annotation block, on a field, parameter, header or declaration
Members come from
the Go const block of that type, read from the type-checker
the literal list you write after the colon
Type / format
the declared Go type
the schema the keyword sits on
Use it when
the values already exist as Go constants
there is no const block, or the members are not Go values at all
// swagger:enum Kind β annotation: members are collected from the conststypeKindstringconst (
KindAKind = "a"KindBKind = "b")
typeFilterstruct {
// enum: asc, desc β keyword: members are taken verbatimOrderstring`json:"order"`}
The annotation is the better tool whenever the constants exist: it stays in
sync with the code, carries each member’s doc comment into x-go-enum-desc,
and cannot drift from the Go values. The keyword is the escape hatch for
everything else.
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 WeekdaytypeWeekdayintconst (
// Sunday is the first day.SundayWeekday = 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:modeltypeSchedulestruct {
// Day the job runs on.DayWeekday`json:"day"`}
{
"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"}
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 LeveltypeLevelintconst (
// LevelLow is the floor.LevelLowLevel = 1<<3// LevelHigh doubles it.LevelHighLevel = LevelLow*2)
// Threshold carries the computed enum.//
// swagger:modeltypeThresholdstruct {
// Level to alert at.LevelLevel`json:"level"`}
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 PanDirectiontypePanDirectionint8const (
// PanLeft pans to the left.PanLeftPanDirection = -1// NoPan holds the current position.NoPanPanDirection = 0// PanRight pans to the right.PanRightPanDirection = 1)
// Camera carries the signed enum, declared int8.//
// swagger:modeltypeCamerastruct {
// Pan direction of the camera.PanPanDirection`json:"pan"`}
{
"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"}
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 ZoomtypeZoomfloat32const (
// ZoomNone is the neutral step.ZoomNoneZoom = 0// ZoomOut steps back.ZoomOutZoom = -1.5// ZoomIn steps in.ZoomInZoom = 1.5)
// Lens carries the float enum.//
// swagger:modeltypeLensstruct {
// Zoom step of the lens.ZoomZoom`json:"zoom"`}
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 KindtypeKindstrfmt.UUIDconst (
// KindPrimary is the primary kind.KindPrimaryKind = "0a8bcf1e-0000-0000-0000-000000000000"// KindSecondary is the secondary kind.KindSecondaryKind = "0a8bcf1e-1111-1111-1111-111111111111")
// Label carries the formatted enum.//
// swagger:modeltypeLabelstruct {
// Kind of the label.KindKind`json:"kind"`}
{
"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"}
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 searchtypeSearchParamsstruct {
// Direction to pan while searching.//
// in: queryPanPanDirection`json:"pan"`// Directions the client accepts.//
// in: queryAccepted []PanDirection`json:"accepted"`}
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 LettertypeLetterruneconst (
// LetterA is the first letter.LetterALetter = 'a'// LetterB is the second letter.LetterBLetter = 'b')
// Glyph carries the rune enum.//
// swagger:modeltypeGlyphstruct {
// Letter of the glyph.LetterLetter`json:"letter"`}
{
"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"}
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/jsonrefuses 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.
typeUnsigned = uint64// an alias, not a new type// swagger:enum Unsigned // β collects nothingconstZeroUnsigned = 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 β the other constraints
a property can carry, and the reduced surface parameters accept.
Not every object has a fixed set of named fields. A Go map models an object
with dynamic keys; a struct can be marked open (extra keys allowed),
closed (extra keys forbidden), or given a typed value schema for its
extras. codescan expresses all of this with additionalProperties and
patternProperties. The panes below are rendered from the test-covered
docs/examples/concepts/maps
package.
Maps become objects
A map field renders as {type: object} whose values all share one schema,
carried as additionalProperties β the value schema is derived from the Go map’s
element type:
Annotated Go
// Inventory shows a plain Go map. A map renders as an object whose values all// share one schema, carried as additionalProperties.//
// swagger:modeltypeInventorystruct {
// Counts maps each SKU to its on-hand quantity.Countsmap[string]int`json:"counts"`}
A map key has to become a JSON object key β a string. codescan accepts every key
type encoding/json can stringify: string kinds, every integer / unsigned
kind (so map[int]V, map[uint8]V, β¦), and any type implementing
encoding.TextMarshaler. An integer-keyed map is therefore still a valid object:
Annotated Go
// Lookups shows which Go map keys survive. A key is usable when encoding/json// can stringify it: string kinds, every integer/unsigned kind, and types// implementing encoding.TextMarshaler. Other keys (float, bool, struct without// TextMarshaler) are dropped with a diagnostic.//
// swagger:modeltypeLookupsstruct {
// ByName is keyed by a plain string.ByNamemap[string]int`json:"byName"`// ByCode is keyed by an integer β JSON stringifies it, so the map is still// an object with additionalProperties.ByCodemap[int]string`json:"byCode"`}
{
"description": "Lookups shows which Go map keys survive. A key is usable when encoding/json\ncan stringify it: string kinds, every integer/unsigned kind, and types\nimplementing encoding.TextMarshaler. Other keys (float, bool, struct without\nTextMarshaler) are dropped with a diagnostic.",
"type": "object",
"properties": {
"byCode": {
"description": "ByCode is keyed by an integer β JSON stringifies it, so the map is still\nan object with additionalProperties.",
"type": "object",
"additionalProperties": {
"type": "string" },
"x-go-name": "ByCode" },
"byName": {
"description": "ByName is keyed by a plain string.",
"type": "object",
"additionalProperties": {
"type": "integer",
"format": "int64" },
"x-go-name": "ByName" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
A key JSON cannot stringify β a float, bool, a struct without
TextMarshaler, or an interface β is not silently dropped: the map’s
additionalProperties is omitted and a CodeUnsupportedType diagnostic
(“additionalProperties dropped”) is raised. A json:"-" map field is muted
before this check, so it never warns.
Open & closed objects
A struct normally renders with just its named properties and says nothing about
extra keys. The decl-level swagger:additionalProperties <spec> marker decides
the policy, where <spec> is true, false, or a value type:
// ClosedObject forbids any key beyond its named properties: the marker false// closes the object.//
// swagger:model// swagger:additionalProperties falsetypeClosedObjectstruct {
Astring`json:"a"`Bint`json:"b"`}
// OpenObject keeps its named property and also allows arbitrary extra keys.//
// swagger:model// swagger:additionalProperties truetypeOpenObjectstruct {
Astring`json:"a"`}
// TypedObject complements its named property with typed (integer) extra values.//
// swagger:model// swagger:additionalProperties integertypeTypedObjectstruct {
Astring`json:"a"`}
// RefObject references a model as the schema of its extra values.//
// swagger:model// swagger:additionalProperties ThingtypeRefObjectstruct {
Astring`json:"a"`}
{
"type": "object",
"title": "OpenObject keeps its named property and also allows arbitrary extra keys.",
"properties": {
"a": {
"type": "string",
"x-go-name": "A" }
},
"additionalProperties": true,
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
A type spec instead of a bool gives the extra values a schema β a primitive
(or []T), or a model name that becomes a $ref (the same value-type grammar as
swagger:type, except a
type name resolves to a $ref):
integer β typed values
{
"type": "object",
"title": "TypedObject complements its named property with typed (integer) extra values.",
"properties": {
"a": {
"type": "string",
"x-go-name": "A" }
},
"additionalProperties": {
"type": "integer" },
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
{
"type": "object",
"title": "RefObject references a model as the schema of its extra values.",
"properties": {
"a": {
"type": "string",
"x-go-name": "A" }
},
"additionalProperties": {
"$ref": "#/definitions/Thing" },
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
On a map type the marker overrides the element-derived value schema; on a
struct it complements the named properties (as above). It composes with the
object validations maxProperties / minProperties / patternProperties.
Info
The model name is a bare leaf: codescan resolves it in the annotating type’s
own package first, then uniquely across the scanned model set, so a value type
declared in another package resolves to a $ref by name. A leaf that matches a
model in several packages is ambiguous β it is dropped with a
validate.ambiguous-type-name diagnostic. See
Resolving $ref name conflicts.
Per-field control
The same <spec> is available as a field keyword,
additionalProperties: <spec>, decorating one struct field. On a map field it
overrides the value schema; on a $ref’d field the value rides an allOf
sibling so the reference is preserved:
Annotated Go
// Holder decorates individual fields with the additionalProperties: keyword.//
// swagger:modeltypeHolderstruct {
// OverriddenMap keeps its map shape but overrides the value schema from the// Go element type (string) to integer.//
// additionalProperties: integerOverriddenMapmap[string]string`json:"overriddenMap"`// RefMap points the map values at a model.//
// additionalProperties: ThingRefMapmap[string]string`json:"refMap"`// ClosedRef references a model and forbids extra keys. Because the field is// a $ref, the value rides an allOf sibling so the reference is preserved.//
// additionalProperties: falseClosedRefThing`json:"closedRef"`}
{
"type": "object",
"title": "Holder decorates individual fields with the additionalProperties: keyword.",
"properties": {
"closedRef": {
"description": "ClosedRef references a model and forbids extra keys. Because the field is\na $ref, the value rides an allOf sibling so the reference is preserved.",
"allOf": [
{
"$ref": "#/definitions/Thing" },
{
"additionalProperties": false }
],
"x-go-name": "ClosedRef" },
"overriddenMap": {
"description": "OverriddenMap keeps its map shape but overrides the value schema from the\nGo element type (string) to integer.",
"type": "object",
"additionalProperties": {
"type": "integer" },
"x-go-name": "OverriddenMap" },
"refMap": {
"description": "RefMap points the map values at a model.",
"type": "object",
"additionalProperties": {
"$ref": "#/definitions/Thing" },
"x-go-name": "RefMap" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
patternProperties constrains extra keys by a name regex rather than allowing
all of them. The regex-only
patternProperties: keyword
maps a pattern to an empty (any-value) schema. The decl-level
swagger:patternProperties "<re>": <spec>, β¦ marker is the typed counterpart
β each quoted regex pairs with a value spec (a primitive, or a model name that
becomes a $ref):
Annotated Go
// TypedPatterns maps property-name regexes to typed value schemas. Each quoted// regex pairs with a value spec β a primitive or a model name (which becomes a// $ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0// subset; codescan emits it ungated.//
// swagger:model// swagger:patternProperties "^x-": string, "^\d+$": integer, "^item-": ThingtypeTypedPatternsstruct {
Knownstring`json:"known"`}
{
"description": "TypedPatterns maps property-name regexes to typed value schemas. Each quoted\nregex pairs with a value spec β a primitive or a model name (which becomes a\n$ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0\nsubset; codescan emits it ungated.",
"type": "object",
"properties": {
"known": {
"type": "string",
"x-go-name": "Known" }
},
"patternProperties": {
"^\\d+$": {
"type": "integer" },
"^item-": {
"$ref": "#/definitions/Thing" },
"^x-": {
"type": "string" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
Beyond Swagger 2.0.patternProperties is a JSON-Schema (draft-4) keyword,
not part of the Swagger 2.0 Schema Object subset. codescan emits it ungated,
consistent with go-openapi’s JSON-Schema-first stance β your downstream tooling
must understand it. Each regex is RE2-hygiene-checked: one that does not compile
raises a CodeInvalidAnnotation warning but is preserved on the schema.
Info
additionalProperties and patternProperties are object-schema keywords β
they only ride on an object. They are the lowest-priority annotations: if a
prior rule already fixed a non-object type (a swagger:type scalar, a
swagger:strfmt, a special type), the marker is dropped with a
CodeShapeMismatch diagnostic. Object schemas also have no OAS-2 SimpleSchema
form, so neither keyword applies on a non-body parameter or a response header
(it is dropped there with a diagnostic).
What’s next
Validations β maxProperties /
minProperties and the regex-only patternProperties: keyword.
Model definitions β the
$ref mechanics the typed value schemas rely on.
Polymorphic models
Swagger 2.0 expresses polymorphism with three ingredients:
a base type that declares a discriminator β the property whose value
says which concrete subtype a payload is;
subtypes that include the base via allOf and add their own fields;
a discriminator value per subtype (here, the subtype’s definition name).
Mark one property discriminator: true. codescan writes that property’s name
onto the schema’s discriminator. A discriminator property must also be
required β a consumer cannot pick a subtype from a value that may be absent.
Annotated Go
// Pet is the polymorphic base type. The field marked `discriminator: true` names// the property whose value tells a consumer which concrete subtype a payload is:// codescan writes that property's name onto the schema's `discriminator`, and a// discriminator property must be `required`.//
// swagger:modeltypePetstruct {
// PetType selects the concrete subtype β its value is the subtype's// definition name (e.g. "Cat" or "Dog").//
// discriminator: true// required: truePetTypestring`json:"petType"`// Name is common to every pet.//
// required: trueNamestring`json:"name"`}
{
"description": "Pet is the polymorphic base type. The field marked `discriminator: true` names\nthe property whose value tells a consumer which concrete subtype a payload is:\ncodescan writes that property's name onto the schema's `discriminator`, and a\ndiscriminator property must be `required`.",
"type": "object",
"required": [
"petType",
"name" ],
"properties": {
"name": {
"description": "Name is common to every pet.",
"type": "string",
"x-go-name": "Name" },
"petType": {
"description": "PetType selects the concrete subtype β its value is the subtype's\ndefinition name (e.g. \"Cat\" or \"Dog\").",
"type": "string",
"x-go-name": "PetType" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism",
"discriminator": "petType"}
Each subtype embeds the base as an anonymous field annotated swagger:allOf. The
result is allOf: [ {$ref to the base}, {the subtype's own fields} ] β the same
composition covered in Model definitions,
now given meaning by the base’s discriminator.
Annotated Go
// Cat is a Pet subtype. `swagger:allOf` composes it as// `allOf: [ $ref Pet, {its own fields} ]` β the composition Swagger 2.0// polymorphism builds on.//
// swagger:modeltypeCatstruct {
// swagger:allOfPet// HuntingSkill is how the cat hunts.HuntingSkillstring`json:"huntingSkill"`}
// Dog is a second Pet subtype.//
// swagger:modeltypeDogstruct {
// swagger:allOfPet// PackSize is the size of the dog's pack.PackSizeint32`json:"packSize"`}
Dog follows the identical shape. A payload is then recognised as a Cat or a
Dog by its petType value.
How subtypes are discovered
A family has an awkward property: the references all point upwards. A subtype
$refs its base, and nothing ever $refs a subtype. So an API that returns the
base β the whole point of polymorphism β names only the base, and the ordinary
reachability rule would stop right there,
leaving a discriminator with nothing to discriminate between.
codescan therefore looks the relation up backwards. When a definition that
declares a discriminator enters the spec, every swagger:model that composes it
under swagger:allOf is pulled in with it β wherever those subtypes are declared,
including other packages. No ScanModels needed. The route below references
only Pet:
Annotated Go
// PetResponse returns the polymorphic BASE β nothing in the API surface names// Cat or Dog.//
// swagger:response petResponsetypePetResponsestruct {
// in: bodyBodyPet`json:"body"`}
// swagger:route GET /pets pets listPets//
// Lists pets.//
// responses://
// 200: petResponse
Cat and Dog are in there, and each pull is announced on the
OnDiagnostic sink,
so a definition you did not ask for by name is never a mystery:
scan.discovered-subtype: definition "Cat" discovered as a subtype of discriminated base "Pet"
scan.discovered-subtype: definition "Dog" discovered as a subtype of discriminated base "Pet"
Reachability, not existence. The trigger is the base entering the spec,
not merely existing in the scanned source. A discriminated base that nothing
references still emits nothing β bases are not roots, or every hierarchy in a
shared library would land in every spec.
The family travels as a unit. With
PruneUnusedModels a reachable
discriminated base keeps its subtypes, even though no $ref reaches them; and
an unreachable base is dropped together with its subtypes. You never get a
base whose subtypes have vanished.
Only swagger:allOf counts. A plain embed inlines the base’s properties
instead of composing them, so it is not a subtype relation. The
DefaultAllOfForEmbeds option
changes how embeds render, deliberately not which definitions exist.
Multi-level hierarchies
A subtype can be a base in its own right. Write the intermediate level as an
interface β only an interface can be embedded by the concrete structs beneath
it β composing its parent with swagger:allOf and declaring a discriminator of
its own:
Annotated Go
// Shape is the root of the hierarchy: a base written as an interface, whose// `discriminator: true` member names the property a consumer switches on.//
// swagger:modeltypeShapeinterface {
// ShapeType selects the concrete subtype.//
// discriminator: true// required: true// swagger:name shapeTypeShapeType() string// swagger:name areaArea() float64}
// Polygon is a subtype of Shape AND a base of its own: it composes Shape as an// allOf member and declares a second discriminator. An intermediate level is// written as an interface, because only an interface can be embedded by the// concrete structs below it.//
// swagger:modeltypePolygoninterface {
// swagger:allOfShape// PolygonType selects the concrete polygon.//
// discriminator: true// required: true// swagger:name polygonTypePolygonType() string}
// Square is a leaf, two levels down. It composes the INTERMEDIATE type, so it// inherits Shape transitively.//
// swagger:modeltypeSquarestruct {
// swagger:allOfPolygon// Side is the length of a side.Sidefloat64`json:"side"`}
{
"description": "Polygon is a subtype of Shape AND a base of its own: it composes Shape as an\nallOf member and declares a second discriminator. An intermediate level is\nwritten as an interface, because only an interface can be embedded by the\nconcrete structs below it.",
"allOf": [
{
"$ref": "#/definitions/Shape" },
{
"type": "object",
"required": [
"polygonType" ],
"properties": {
"polygonType": {
"description": "PolygonType selects the concrete polygon.",
"type": "string",
"x-go-name": "PolygonType" }
},
"discriminator": "polygonType" }
],
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/polymorphism-nested"}
Discovery cascades: the route references Shape, Shape pulls Polygon, and
Polygon β itself only just discovered β pulls Square. Note where each
level’s discriminator lands, because the two differ:
level
shape
discriminator
root (Shape)
a plain object
at the top level
intermediate (Polygon)
allOf: [ $ref Shape, {own} ]
inside its own allOf member
leaf (Square)
allOf: [ $ref Polygon, {own} ]
none of its own
A leaf never inherits its base’s discriminator as its own: it points at a
discriminated base, which is what makes it a subtype, not a base.
Info
The discriminator value for each subtype is its definition name (Cat,
Dog) β so petType must carry exactly "Cat" or "Dog". codescan does not
implement a custom-value annotation (swagger:discriminatorValue), so the
subtype name is the value. Keep the discriminator a plain string and required
on the base; it is inherited by every subtype through the $ref.
What’s next
Model definitions β the
swagger:allOf composition this builds on, and the rest of the model surface.
Routes & operations β
return a base type and let the discriminator carry the subtype.
Routes and operations turn an annotation into an entry in the spec’s paths
map, wired to the parameters it accepts and the responses it returns. This page
covers the two operation annotations and the companion structs they reference.
Each pane pairs the annotated Go (left) with the exact fragment the scanner
emits (right), from the test-covered
docs/examples/concepts/routes
package.
For the exhaustive rule on any annotation below, follow its link to the
Maintainers reference; the
Parameters: / Responses: body grammars are covered in
Sub-languages.
swagger:route
swagger:route <METHOD> <path> [tags] <operationID> declares a path and its
operation in one annotation. The body’s responses: block ties status codes to
named responses ($ref into the spec’s responses). It lives in a plain
comment block β no Go declaration required.
Annotated Go
// swagger:route GET /pets pets listPets//
// Lists pets in the store, optionally filtered by tag.//
// responses://
// 200: petsResponse// default: errorResponse
The body can also carry an indented Parameters: block to declare simple
parameters (path / query / header) inline β no swagger:parameters struct
needed. For body parameters or parameter sets shared across operations, use
swagger:parameters instead. The block syntax is covered
in sub-languages.
swagger:operation
swagger:operation carries the same header but spells the operation out as a
YAML document after a --- fence β useful when you want to author the operation
object directly (here a path parameter and an inline $ref response schema).
Annotated Go
// swagger:operation GET /pets/{id} pets getPet//
// ---// summary: Get a pet by ID.// parameters:// - name: id// in: path// required: true// type: integer// format: int64// responses:// '200':// description: the requested pet// schema:// $ref: '#/definitions/Pet'// default:// $ref: '#/responses/errorResponse'
swagger:parameters <operationID>β¦ declares a struct whose fields become the
parameters of the named operation(s). Field doc comments carry in:, the
validations, and the description; the parameters attach to every operation ID
listed.
Annotated Go
// ListPetsParams is the parameter set for the listPets operation. Each field// becomes one parameter; the operation IDs after swagger:parameters name the// operations the set applies to.//
// swagger:parameters listPetstypeListPetsParamsstruct {
// Tag filters pets by tag.//
// in: queryTagstring`json:"tag"`// Limit caps the number of results.//
// in: query// minimum: 1// maximum: 100Limitint32`json:"limit"`}
A field marked in: body makes its Go type the request body schema β the
usual shape for a POST or PUT payload:
Annotated Go
// CreatePetParams is the request parameter set for createPet. A field marked// `in: body` makes its Go type the request body schema β the usual shape for a// POST or PUT payload.//
// swagger:parameters createPettypeCreatePetParamsstruct {
// Body is the pet to create.//
// in: body// required: trueBodyPet`json:"body"`}
// swagger:route POST /pets/import pets createPet//
// responses://
// 200: petsResponse
When a parameter field’s Go type is a struct (or any type that has no simple
Swagger representation), it cannot be a query/path/header parameter on its own. A
swagger:type override
collapses it to a simple parameter β a scalar, or a []-wrapped scalar for an
array parameter:
Annotated Go
// Cursor is an opaque pagination token. As a struct it cannot be a query// parameter on its own; swagger:type publishes it as a simple parameter.typeCursorstruct {
PageintTokenstring}
// FilterPetsParams shows swagger:type on parameter fields: a struct-typed field// is collapsed to a simple parameter. The override accepts a scalar or a// []-wrapped scalar β the inline / type-name forms are rejected here (a// non-body parameter has no schema to inline into).//
// swagger:parameters filterPetstypeFilterPetsParamsstruct {
// After is an opaque cursor carried as a plain string query parameter.//
// in: query// swagger:type stringAfterCursor`json:"after"`// Sort is a list of sort keys carried as an array-of-string query parameter.//
// in: query// swagger:type []stringSort []Cursor`json:"sort"`}
// swagger:route GET /pets/filter pets filterPets//
// Filter pets with cursor pagination.//
// responses://
// 200: description: matched pets
swagger:response <name> declares a struct as a named entry in the spec’s
top-level responses. A Body field (or in: body) becomes the response
schema; routes reference it by name. Here the body is a []Pet, so the schema
is an array of $refs.
Annotated Go
// PetsResponse is the list returned by listPets.//
// swagger:response petsResponsetypePetsResponsestruct {
// in: bodyBody []Pet}
// ErrorResponse is the default error payload.//
// swagger:response errorResponsetypeErrorResponsestruct {
// in: bodyBodystruct {
// Message is a human-readable error message.Messagestring`json:"message"` }
}
swagger:file on a parameter field marks it as a binary upload β the parameter
emits as {type: file}. It belongs on a formData field of a
swagger:parameters struct.
Annotated Go
// swagger:route POST /pets/{id}/photo pets uploadPetPhoto//
// responses://
// 200: petsResponse// UploadParams is the multipart upload for the uploadPetPhoto operation.//
// swagger:parameters uploadPetPhototypeUploadParamsstruct {
// Photo is the image to upload.//
// in: formData// swagger:filePhotoio.ReadCloser`json:"photo"`}
An externalDocs: block (description + url) links an object out to external
documentation. It rides an operation (in a swagger:route or
swagger:operation body) and a full schema (a swagger:model). It is a
full-Schema-only keyword: on a simple-schema parameter (anything but in: body)
it is dropped with a diagnostic. The same ExternalDocs: block on a
swagger:meta package populates the spec’s top-level externalDocs (see
Document metadata).
Annotated Go
// swagger:route GET /pets/search pets searchPets//
// Searches pets. The operation links out to external documentation.//
// externalDocs:// description: Search guide// url: https://example.com/docs/search//
// responses://
// 200: petsResponse// CatalogEntry carries externalDocs at the schema level (the link rides the// definition) and on its fields. (On a simple-schema parameter externalDocs is// dropped with a diagnostic: it is a full-Schema-only keyword.)//
// externalDocs: {description: "Catalog schema reference", url: "https://example.com/docs/catalog"}//
// swagger:modeltypeCatalogEntrystruct {
// SKU is the catalog identifier.SKUstring`json:"sku"`// Vendor is a plain field: externalDocs attaches directly to the property.//
// externalDocs: {description: "Vendor field docs", url: "https://example.com/docs/vendor"}Vendorstring`json:"vendor"`// Supplier is a $ref'd field: its sibling externalDocs lifts onto the// field's allOf compound (a bare $ref cannot carry sibling keywords).//
// externalDocs: {description: "Supplier docs", url: "https://example.com/docs/supplier"}SupplierSupplier`json:"supplier"`}
// Supplier is referenced by CatalogEntry.Supplier.//
// swagger:modeltypeSupplierstruct {
// Name is the supplier name.Namestring`json:"name"`}
On a model the link rides the definition, and it also rides individual
struct fields: on a plain field it attaches to the property directly; on a
$ref’d field it is lifted onto the field’s allOf compound (a bare $ref
cannot carry sibling keywords). The value can be written as the indented block
above or as an equivalent inline { description: β¦, url: β¦ } mapping β which
reads better on a single-line doc comment:
Annotated Go
// swagger:route GET /pets/search pets searchPets//
// Searches pets. The operation links out to external documentation.//
// externalDocs:// description: Search guide// url: https://example.com/docs/search//
// responses://
// 200: petsResponse// CatalogEntry carries externalDocs at the schema level (the link rides the// definition) and on its fields. (On a simple-schema parameter externalDocs is// dropped with a diagnostic: it is a full-Schema-only keyword.)//
// externalDocs: {description: "Catalog schema reference", url: "https://example.com/docs/catalog"}//
// swagger:modeltypeCatalogEntrystruct {
// SKU is the catalog identifier.SKUstring`json:"sku"`// Vendor is a plain field: externalDocs attaches directly to the property.//
// externalDocs: {description: "Vendor field docs", url: "https://example.com/docs/vendor"}Vendorstring`json:"vendor"`// Supplier is a $ref'd field: its sibling externalDocs lifts onto the// field's allOf compound (a bare $ref cannot carry sibling keywords).//
// externalDocs: {description: "Supplier docs", url: "https://example.com/docs/supplier"}SupplierSupplier`json:"supplier"`}
// Supplier is referenced by CatalogEntry.Supplier.//
// swagger:modeltypeSupplierstruct {
// Name is the supplier name.Namestring`json:"name"`}
{
"description": "CatalogEntry carries externalDocs at the schema level (the link rides the\ndefinition) and on its fields. (On a simple-schema parameter externalDocs is\ndropped with a diagnostic: it is a full-Schema-only keyword.)",
"type": "object",
"properties": {
"sku": {
"description": "SKU is the catalog identifier.",
"type": "string",
"x-go-name": "SKU" },
"supplier": {
"description": "Supplier is a $ref'd field: its sibling externalDocs lifts onto the\nfield's allOf compound (a bare $ref cannot carry sibling keywords).",
"allOf": [
{
"$ref": "#/definitions/Supplier" }
],
"x-go-name": "Supplier",
"externalDocs": {
"description": "Supplier docs",
"url": "https://example.com/docs/supplier" }
},
"vendor": {
"description": "Vendor is a plain field: externalDocs attaches directly to the property.",
"type": "string",
"x-go-name": "Vendor",
"externalDocs": {
"description": "Vendor field docs",
"url": "https://example.com/docs/vendor" }
}
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/routes",
"externalDocs": {
"description": "Catalog schema reference",
"url": "https://example.com/docs/catalog" }
}
Shaping the output β $ref vs inline,
aliases, and the other rendering knobs.
Sharing parameters & responses
When the same header, query parameter or error response appears on many
operations, you don’t have to repeat it. codescan can publish a parameter or
response once into the spec’s top-level parameters / responses maps, then
reference it from each operation as a $ref. This is the OpenAPI 2.0 shared
namespace (#/parameters/{name}, #/responses/{name}).
Each pane below pairs the annotated Go (left) with the exact fragment the scanner
emits (right), from the test-covered
docs/examples/concepts/sharedparams
package. For the per-operation basics this builds on β the plain
swagger:parameters <operationID> and swagger:response <name> forms β see
Routes & operations.
Declaring a shared parameter
swagger:parameters * declares a struct whose fields are registered at the spec
top level, #/parameters/{name}, keyed by each parameter’s resolved name. The
bare * is register-only: it publishes the parameter but does not, by
itself, attach it to any operation.
The convenience form swagger:parameters * <operationID>β¦ does both at once β it
registers the parameter and$refs it into the listed operations, which is
handy for a small spec.
// CommonHeaders registers a reusable header parameter at the spec top// level, #/parameters/X-Request-ID. The bare `*` target is register-only:// it publishes the parameter but does not, by itself, attach it to any// operation.//
// swagger:parameters *typeCommonHeadersstruct {
// RequestID correlates a request across services.//
// in: headerRequestIDstring`json:"X-Request-ID"`}
// AuthHeader registers #/parameters/X-API-Key and, in the same breath,// $ref's it into the createPet operation β the convenient `* <opid>`// form for a small spec.//
// swagger:parameters * createPettypeAuthHeaderstruct {
// APIKey authorises access.//
// in: header// required: trueAPIKeystring`json:"X-API-Key"`}
// ErrorResponse is the common error envelope returned by every operation.//
// swagger:response *typeErrorResponsestruct {
// in: bodyBodystruct {
// Code is a machine-readable error code.Codeint`json:"code"`// Message is a human-readable error message.Messagestring`json:"message"` } `json:"body"`}
Once a parameter is registered, an operation can pull it in by name. There are
two reference channels:
swagger:parameters * <operationID> on the declaring struct β the
convenience form above. AuthHeader uses it to inject X-API-Key into
createPet.
swagger:parameters <operationID> <name>β¦ as a standalone marker on the
operation’s function β the scaling channel: the shared struct need not
enumerate every operation that wants the parameter; instead each operation
opts in next to its own swagger:route.
// ListPets lists pets.//
// The standalone reference marker on the next line pulls the shared// X-Request-ID parameter into this operation as a $ref. This is the// scaling channel: the shared struct need not enumerate every operation// that wants the parameter.//
// swagger:route GET /pets pets listPets// swagger:parameters listPets X-Request-ID// Responses://
// default: ErrorResponsefuncListPets() {}
// CreatePet creates a pet. Its X-API-Key comes from AuthHeader// (#/parameters/X-API-Key, $ref'd via `* createPet`); its body comes from// the inlined CreatePetParams.//
// swagger:route POST /pets pets createPet// Responses://
// default: ErrorResponsefuncCreatePet() {}
A parameter can also attach to a whole path rather than a single operation.
swagger:parameters /path inlines a struct’s fields into the path-item’s
parameters array, so every operation under that path inherits them.
// TenantHeader inlines a required header into the /pets/{id} path-item// itself, so every operation under that exact path inherits it. The// target is a literal path, and matching is exact β OAS2 has no path// hierarchy, so this does NOT apply to /pets.//
// swagger:parameters /pets/{id}typeTenantHeaderstruct {
// Tenant scopes the request to a customer.//
// in: header// required: trueTenantstring`json:"X-Tenant"`}
// GetPet fetches one pet. It declares no header of its own; X-Tenant// reaches it through the /pets/{id} path-item parameter above.//
// swagger:route GET /pets/{id} pets getPet// Responses://
// default: ErrorResponsefuncGetPet() {}
Exact path, no hierarchy. OpenAPI 2.0 has no path nesting, so the target is
matched literally: swagger:parameters /pets/{id} applies to /pets/{id} only β
not to /pets. Path-item parameters also co-exist with operation-level
ones rather than replacing them; if an operation declares a parameter with the
same (name, in), the operation’s wins at resolution time per the OAS2 rule.
To $ref an already-registered shared parameter into a path-item (instead of
inlining a new one), use the reference form with a path target:
swagger:parameters /path <name>β¦ β the path-item analogue of the per-operation
marker above.
Shared responses
Responses share the same way. swagger:response * registers a struct at
#/responses/{name} (keyed by the Go type name). The * is a synonym for the
bare/named swagger:response form β its job is to mark the response as a
shared one. Operations then name it in their Responses: block and it is
emitted as a $ref.
// ErrorResponse is the common error envelope returned by every operation.//
// swagger:response *typeErrorResponsestruct {
// in: bodyBodystruct {
// Code is a machine-readable error code.Codeint`json:"code"`// Message is a human-readable error message.Messagestring`json:"message"` } `json:"body"`}
Both routes write default: ErrorResponse, which resolves to a single shared
$ref (visible as responses.default.$ref in the operation panes above) β
one error envelope, defined once, referenced everywhere.
Conflicts, duplicates & dangling references
The shared namespace is referenced only by short name, so codescan cannot
silently rename a collision the way it
deconflicts model definitions.
Instead it applies a deterministic, observable policy and reports every
adjustment through Options.OnDiagnostic (the scan never fails on these β it
keeps a valid spec and warns):
Situation
Policy
Diagnostic
Two swagger:parameters * register the same name
keep-first (sorted by package path then position; never renamed) β later one dropped
scan.shared-parameter-conflict
Two swagger:response * register the same name
keep-first; later one dropped
scan.shared-response-conflict
A reference names a parameter no * registered
reference dropped (no dangling $ref emitted)
scan.dangling-parameter-ref
An operation names an unregistered shared response
reference dropped
scan.dangling-response-ref
A * <opid>β¦ marker repeats an operation id
duplicate dropped
scan.duplicate-target
A reference repeats a parameter name
collapses to a single $ref
scan.duplicate-ref
Note
The shared parameters, responses and definitions namespaces are
independent: #/parameters/Status, #/responses/Status and
#/definitions/Status can all coexist. The resolved (name:-overridden) name is
the key, so references must use that name β not the Go field name. An InputSpec
overlay entry seeds the namespace and wins any keep-first conflict.
What’s next
Routes & operations β the
per-operation swagger:parameters / swagger:response basics.
Pruning unused models
β shared parameters and responses count as reachability roots when pruning.
Keyword reference β the exhaustive
parameters / responses body grammars.
Validations
Validations are keyword-driven: you write keyword: value lines in a field’s
doc comment and they become minimum, maxLength, pattern, enum, and the
rest of the validation surface on that property. Each pane below pairs the
annotated Go (left) with the fragment the scanner emits (right), from the
test-covered docs/examples/concepts/validations
package.
For the per-keyword reference card β value shapes, aliases, and legal contexts β
see Keywords.
On a model field β the full surface
A swagger:model field accepts the full JSON-schema validation vocabulary:
Numeric β minimum, maximum, multipleOf (on Price).
Length β min length / max length (on Name).
Arrays β min items / max items / unique (on Tags).
Pattern β a regular expression (on SKU).
Enum β a fixed value set (on Grade).
Required β required: true lifts the property into the schema’s
object-level required array (sku).
Annotated Go
// Product is a model whose fields carry the full JSON-schema validation surface.//
// swagger:modeltypeProductstruct {
// SKU is the stock code.//
// required: true// pattern: ^[A-Z]{3}-[0-9]{4}$SKUstring`json:"sku"`// Price is the price in cents.//
// minimum: 1// maximum: 1000000// multipleOf: 1Priceint64`json:"price"`// Name is the display name.//
// min length: 1// max length: 120Namestring`json:"name"`// Grade is a quality band.//
// enum: A,B,CGradestring`json:"grade"`// Tags label the product.//
// min items: 1// max items: 10// unique: trueTags []string`json:"tags"`}
Simple schemas have a reduced surface. Parameters other than in: body, and
response headers, are simple schemas in OpenAPI 2.0 β not full JSON schemas.
They accept the validation subset (maximum/minimum/multipleOf,
maxLength/minLength/pattern, maxItems/minItems/uniqueItems, enum,
plus the simple-schema-only collectionFormat) but not schema-only
constructs. A schema-only keyword such as readOnly placed on a query parameter
is simply not emitted β spec.Parameter has nowhere to carry it.
A Go map field has no simple-schema representation either: on a non-body
parameter or a response header it is skipped with a
validate.unsupported-in-simple-schema warning. Maps are only representable on
a body schema (as object + additionalProperties).
An array element is itself a simple schema, so it may not be a $ref. A
named-primitive element ([]Label, underlying string) expands inline to its
type; an object element ([]SomeStruct) has no simple-schema form and dissolves
to an empty items: {} with the same warning. Use a body schema for an array of
objects.
The same numeric and length keywords work on a query parameter; arrays add
collectionFormat:
Annotated Go
// SearchParams is the simple-schema parameter set for searchProducts. Query// parameters accept the reduced OAS 2.0 validation surface.//
// swagger:parameters searchProductstypeSearchParamsstruct {
// Q is the search text.//
// in: query// min length: 3// max length: 50Qstring`json:"q"`// Limit caps the number of results.//
// in: query// minimum: 1// maximum: 100Limitint32`json:"limit"`// Sort lists the sort fields.//
// in: query// collection format: csv// unique: trueSort []string`json:"sort"`}
A response header is also a simple schema, so it takes the same reduced
validation set (here minimum on an integer header). Note headers carry no
required flag.
Annotated Go
// RateLimited is a response carrying a validated header (a simple schema).//
// swagger:response rateLimitedtypeRateLimitedstruct {
// XRateRemaining is the remaining request budget.//
// minimum: 0XRateRemainingint32`json:"X-Rate-Remaining"`}
The object-validation keywords constrain a free-form object as a whole rather
than named fields: minProperties / maxProperties bound the property count,
and patternProperties permits properties whose name matches a regex. They are
schema-only β kept on an object-typed model, stripped (with a diagnostic) on a
scalar model or a simple-schema parameter. For dynamic-key objects, the typed
value forms, and additionalProperties, see
Maps & free-form objects.
Annotated Go
// Attributes is a free-form object constrained by the object-validation// keywords: it must carry between 1 and 10 properties, and any property whose// name matches the regex is permitted. Object validations constrain the map of// (additional) properties rather than named struct fields.//
// minProperties: 1// maxProperties: 10// patternProperties: ^x-//
// swagger:model AttributestypeAttributesmap[string]any
{
"description": "Attributes is a free-form object constrained by the object-validation\nkeywords: it must carry between 1 and 10 properties, and any property whose\nname matches the regex is permitted. Object validations constrain the map of\n(additional) properties rather than named struct fields.",
"type": "object",
"maxProperties": 10,
"minProperties": 1,
"additionalProperties": {},
"patternProperties": {
"^x-": {}
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/validations"}
Keyword reference β every keyword,
its value shape, and where it is legal.
Examples & defaults
Example values and defaults are documentation that travels with the schema: an
example: shows a caller what a value looks like, a default: declares what the
field is when the caller omits it. Both are typed to the field β a numeric
default on an integer field is a JSON number, not a string. The panes below pair
the annotated Go with the fragment the scanner emits, from the test-covered
docs/examples/concepts/examples
package.
For the exact value shapes these keywords accept, see
Keywords.
example
example: <value> attaches an example to the property, coerced to the field’s
type β Hello, world! stays a string, 3 becomes a number.
Annotated Go
// Greeting carries an example value for documentation.//
// swagger:modeltypeGreetingstruct {
// Message is the greeting text.//
// example: Hello, world!Messagestring`json:"message"`// Count is how many times to repeat it.//
// example: 3Countint32`json:"count"`}
{
"type": "object",
"title": "Greeting carries an example value for documentation.",
"properties": {
"count": {
"description": "Count is how many times to repeat it.",
"type": "integer",
"format": "int32",
"x-go-name": "Count",
"example": 3 },
"message": {
"description": "Message is the greeting text.",
"type": "string",
"x-go-name": "Message",
"example": "Hello, world!" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"}
The value is not limited to scalars. A JSON literal is parsed into a
structured example β a { β¦ } object on a map field, a [ β¦ ] array on a slice
field. A bare comma-separated list (example: a,b) is not split; it is kept
verbatim as a string, so write example: ["a","b"] when you need an array.
On a plain string field a surrounding pair of double quotes is treated as
delimiters and stripped β so example: "Foo" yields Foo, and
example: "" sets an empty string (the same applies to default:). Bare
values keep their text as-is.
Annotated Go
// Profile carries structured (non-scalar) example values. A JSON object literal// on a map field and a JSON array literal on a slice field are parsed into// structured examples β a bare comma-separated list would instead be kept// verbatim as a string.//
// swagger:modeltypeProfilestruct {
// Labels is a set of key/value labels.//
// example: {"env":"prod","tier":"gold"}Labelsmap[string]string`json:"labels"`// Roles is the list of assigned roles.//
// example: ["admin","auditor"]Roles []string`json:"roles"`}
{
"description": "Profile carries structured (non-scalar) example values. A JSON object literal\non a map field and a JSON array literal on a slice field are parsed into\nstructured examples β a bare comma-separated list would instead be kept\nverbatim as a string.",
"type": "object",
"properties": {
"labels": {
"description": "Labels is a set of key/value labels.",
"type": "object",
"additionalProperties": {
"type": "string" },
"x-go-name": "Labels",
"example": {
"env": "prod",
"tier": "gold" }
},
"roles": {
"description": "Roles is the list of assigned roles.",
"type": "array",
"items": {
"type": "string" },
"x-go-name": "Roles",
"example": [
"admin",
"auditor" ]
}
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"}
default: <value> sets the property’s default, again typed to the field β 8080
is a number, false a boolean, auto a string.
Annotated Go
// Settings carries default values applied when a field is omitted.//
// swagger:modeltypeSettingsstruct {
// Port is the listen port.//
// default: 8080Portint32`json:"port"`// Mode is the run mode.//
// default: autoModestring`json:"mode"`// Verbose toggles verbose logging.//
// default: falseVerbosebool`json:"verbose"`}
swagger:default never emitted a default into the spec. It is now an inert
sink that raises a validate.deprecated diagnostic. Use the default: keyword
above.
The keyword covers every place OpenAPI 2.0 admits a default value: a model field,
a non-body parameter, a header, and array items. The one remaining sense of
“default” β an operation’s default response β is not a value at all; it is
written as a response code in a route’s responses: body:
// swagger:route GET /pets pets listPets//
// Lists pets in the store, optionally filtered by tag.//
// responses://
// 200: petsResponse// default: errorResponse
When a field’s type is a named (defined) type, it renders as a $ref to that
type’s definition β and a $ref cannot carry sibling keywords. An example: or
default: on such a field is therefore preserved on the override arm of an
allOf compound, so the value still reaches the spec.
Annotated Go
// Currency is a named (defined) string type, so it earns its own definition and// a field typed Currency renders as a $ref. A $ref cannot carry sibling// keywords, so an example or default on such a field rides the override arm of// an allOf compound β the value still reaches the spec.//
// swagger:modeltypeCurrencystring// Price shows example + default on a defined-type field.//
// swagger:modeltypePricestruct {
// Unit is the ISO currency code.//
// default: USD// example: EURUnitCurrency`json:"unit"`}
A JSON object or array literal on a $ref’d field is coerced into a
structured value on that override arm β exactly as it is on a plain field β so
the example reads as real JSON, not an escaped string. A bare scalar is the
exception: on the override arm the referenced type is unknown, so a scalar stays
a string rather than being silently retyped.
Annotated Go
// Coordinates is a defined struct, so a field typed Coordinates renders as a// $ref.//
// swagger:modeltypeCoordinatesstruct {
// Lat is the latitude.Latfloat64`json:"lat"`// Lng is the longitude.Lngfloat64`json:"lng"`}
// Place shows a JSON-object example on a $ref'd field. Because the field is a// $ref, the example rides the override arm of the allOf β and a JSON object (or// array) literal is coerced into a structured value there, exactly as it is on a// plain field. A bare scalar would instead stay a string, since the referenced// type is not known on the override arm.//
// swagger:modeltypePlacestruct {
// At is the location.//
// example: {"lat":48.85,"lng":2.35}AtCoordinates`json:"at"`}
{
"description": "Place shows a JSON-object example on a $ref'd field. Because the field is a\n$ref, the example rides the override arm of the allOf β and a JSON object (or\narray) literal is coerced into a structured value there, exactly as it is on a\nplain field. A bare scalar would instead stay a string, since the referenced\ntype is not known on the override arm.",
"type": "object",
"properties": {
"at": {
"description": "At is the location.",
"allOf": [
{
"$ref": "#/definitions/Coordinates" },
{
"example": {
"lat": 48.85,
"lng": 2.35 }
}
],
"x-go-name": "At" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/examples"}
example: is not limited to model fields. On a swagger:response whose body is
a top-level array (or other non-struct) type, the example lands on the response
body schema:
Annotated Go
// NTPServers is a top-level array response carrying an example. The example// lands on the response body schema rather than being dropped.//
// swagger:response ntpServers// example: ["10.0.0.1","10.0.0.2"]typeNTPServers []string// swagger:route GET /ntp ntp listNTP//
// responses://
// 200: ntpServers// Pet is the response payload.//
// swagger:model PettypePetstruct {
Namestring`json:"name"`}
// PetResponse returns a pet, with one example payload per media type.//
// The plural `examples:` keyword on a struct swagger:response is a YAML map// keyed by media type, populating the OpenAPI response `examples` object.//
// swagger:response petResponse//
// examples://
// application/json:// name: Fluffy// application/xml: "<pet><name>Fluffy</name></pet>"typePetResponsestruct {
// in: bodyBodyPet`json:"body"`}
// swagger:route GET /pets pets listPets//
// responses://
// 200: petResponse
{
"description": "NTPServers is a top-level array response carrying an example. The example\nlands on the response body schema rather than being dropped.",
"schema": {
"type": "array",
"items": {
"type": "string" },
"example": [
"10.0.0.1",
"10.0.0.2" ]
}
}
A response can carry an examples: map keyed by media type β these populate the
OpenAPI response examples object, one example payload per content type. Both
annotation styles support it.
In a swagger:operation YAML body, examples: sits under the response code:
// swagger:operation GET /status status getStatus//
// ---// responses:// '200':// description: Success// examples:// application/json:// hello: world
On a struct-based swagger:response, the same examples: block lives in the
declaration comment (the plural examples: is the response keyword; the
singular example: above is the schema decorator) and produces the same
response examples object:
Annotated Go
// Pet is the response payload.//
// swagger:model PettypePetstruct {
Namestring`json:"name"`}
// PetResponse returns a pet, with one example payload per media type.//
// The plural `examples:` keyword on a struct swagger:response is a YAML map// keyed by media type, populating the OpenAPI response `examples` object.//
// swagger:response petResponse//
// examples://
// application/json:// name: Fluffy// application/xml: "<pet><name>Fluffy</name></pet>"typePetResponsestruct {
// in: bodyBodyPet`json:"body"`}
// swagger:route GET /pets pets listPets//
// responses://
// 200: petResponse
{
"description": "PetResponse returns a pet, with one example payload per media type.\n\nThe plural `examples:` keyword on a struct swagger:response is a YAML map\nkeyed by media type, populating the OpenAPI response `examples` object.",
"schema": {
"$ref": "#/definitions/Pet" },
"examples": {
"application/json": {
"name": "Fluffy" },
"application/xml": "\u003cpet\u003e\u003cname\u003eFluffy\u003c/name\u003e\u003c/pet\u003e" }
}
Because the example lives on the response, one shared model can carry a
different example per response code and per operation β a 200 and a 404
that both return the same error model each show their own illustrative payload,
with no need for a distinct struct per case.
Validations β constrain the values
these examples illustrate.
Other type decorators
Beyond validations, a couple of keyword decorators annotate a property’s or
operation’s role. The panes below pair the annotated Go with the fragment the
scanner emits, from the test-covered
docs/examples/concepts/decorators
package.
For the value shapes and legal contexts of each, see the
Keyword reference.
readOnly
read only: true on a model field marks the property readOnly β the server
sets it, clients must not.
Annotated Go
// Token is issued by the server.//
// swagger:modeltypeTokenstruct {
// ID is assigned by the server and cannot be set by clients.//
// read only: trueIDstring`json:"id"`// Value is the token value.Valuestring`json:"value"`}
{
"type": "object",
"title": "Token is issued by the server.",
"properties": {
"id": {
"description": "ID is assigned by the server and cannot be set by clients.",
"type": "string",
"x-go-name": "ID",
"readOnly": true },
"value": {
"description": "Value is the token value.",
"type": "string",
"x-go-name": "Value" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/decorators"}
This is the idiomatic way to model server-set fields (an id, a createdAt)
that appear in responses but should not be supplied on create β one model,
marked readOnly, rather than separate request/response structs. (codescan does
not hide fields per operation; if you truly need different shapes, declare
distinct request and response models.)
deprecated
deprecated: true in a swagger:route / swagger:operation body marks the
operation deprecated.
Annotated Go
// swagger:route GET /legacy/ping legacy ping//
// Ping is the legacy health check.//
// deprecated: true//
// responses://
// 200: pingResponse// Gadget is a deprecated model. OpenAPI 2.0 has no native `deprecated` on a// schema, so codescan emits `x-deprecated: true` β here triggered by the// godoc-style "Deprecated:" paragraph, which is recognised on its own without a// separate annotation. (The explicit `deprecated: true` annotation, shown on the// operation above, has the same effect on a model or field.)//
// Deprecated: superseded by the v2 widget API.//
// swagger:modeltypeGadgetstruct {
// SerialNo is the legacy identifier.//
// Deprecated: use the v2 identifier instead.SerialNostring`json:"serialNo"`// Name is the current display name.Namestring`json:"name"`}
On an operation, deprecated: true sets the native OpenAPI 2.0
deprecated field. OpenAPI 2.0 has no native deprecated on the Schema object,
so on a model or model field codescan emits the x-deprecated: true vendor
extension instead.
A godoc-style Deprecated: paragraph (the pkgsite convention) is an exact
synonym for deprecated: true, recognised in any context. On a Go doc
comment it is the natural form β a bare // deprecated: true line there reads
as a malformed deprecation notice to Go linters, whereas the capitalised
Deprecated: paragraph is idiomatic. Use deprecated: true in the indented
route / operation bodies, and the Deprecated: paragraph on model and field doc
comments; either yields the same result. x-deprecated carries semantic intent
rather than reflection metadata, so it is emitted even when SkipExtensions is
set.
A godoc Deprecated: paragraph marks a model and its fields β codescan emits
x-deprecated: true on each (the explicit deprecated: true annotation has the
same effect):
Annotated Go
// Gadget is a deprecated model. OpenAPI 2.0 has no native `deprecated` on a// schema, so codescan emits `x-deprecated: true` β here triggered by the// godoc-style "Deprecated:" paragraph, which is recognised on its own without a// separate annotation. (The explicit `deprecated: true` annotation, shown on the// operation above, has the same effect on a model or field.)//
// Deprecated: superseded by the v2 widget API.//
// swagger:modeltypeGadgetstruct {
// SerialNo is the legacy identifier.//
// Deprecated: use the v2 identifier instead.SerialNostring`json:"serialNo"`// Name is the current display name.Namestring`json:"name"`}
{
"description": "Deprecated: superseded by the v2 widget API.",
"type": "object",
"title": "Gadget is a deprecated model. OpenAPI 2.0 has no native `deprecated` on a\nschema, so codescan emits `x-deprecated: true` β here triggered by the\ngodoc-style \"Deprecated:\" paragraph, which is recognised on its own without a\nseparate annotation. (The explicit `deprecated: true` annotation, shown on the\noperation above, has the same effect on a model or field.)",
"properties": {
"name": {
"description": "Name is the current display name.",
"type": "string",
"x-go-name": "Name" },
"serialNo": {
"description": "SerialNo is the legacy identifier.\n\nDeprecated: use the v2 identifier instead.",
"type": "string",
"x-deprecated": true,
"x-go-name": "SerialNo" }
},
"x-deprecated": true,
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/decorators"}
A single swagger:meta block on a package doc comment carries the document’s
top-level metadata: its info (title, description, version, license, contact),
the host and basePath, the default schemes, and consumes/produces. The
pane pairs the annotated package with the document it produces, from the
test-covered docs/examples/concepts/meta
package.
swagger:meta
The block lives in the package doc comment. The title comes from the first
line with the Package <name> prefix stripped; the following paragraph becomes
the description. The indented Key: value lines and list blocks populate the
rest β License: and Contact: parse into structured objects, and an
ExternalDocs: block (description + url) populates the spec’s top-level
externalDocs. An InfoExtensions: block adds x-* vendor extensions to the
info object β this is where an x-logo (rendered by ReDoc / Swagger UI) goes.
Package doc comment
// Package meta Pet Store.//
// A small API that demonstrates the document-level swagger:meta block: the// package doc comment carries the spec's top-level metadata.//
// Schemes: https// Host: api.example.com// BasePath: /v1// Version: 1.2.0// License: Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0.html// Contact: API Team <api@example.com> https://example.com/support//
// Consumes:// - application/json//
// Produces:// - application/json//
// ExternalDocs:// description: Full API guide// url: https://example.com/docs//
// Tags:// - name: pets// description: Everything about your Pets// externalDocs:// description: Find out more// url: https://example.com/docs/pets// - name: store// description: Access to Petstore orders// x-display-name: Store//
// SecurityDefinitions:// basic_auth:// type: basic// api_key:// type: apiKey// in: header// name: X-API-Key//
// Security:// basic_auth://
// InfoExtensions:// x-logo:// url: https://example.com/logo.png// altText: Example//
// swagger:metapackagemeta
A Tags: block declares the spec’s top-level tags β a YAML sequence of tag
objects, each with a name, an optional description, a nested externalDocs,
and any x-* vendor extensions. This is how you attach per-tag descriptions to
the tags your routes reference (above, pets and store).
For the full meta keyword surface (security definitions, external docs,
extensions, terms of service), see the
swagger:meta reference
and the meta keywords.
Security
The meta block above also declares SecurityDefinitions: (the auth schemes) and
a Security: default β authentication is declared, not hand-rolled. Declaring
schemes, requiring them per route, and overlaying security from outside the code
have their own walkthrough: Security.
A build-time version
Version: is a static literal in source β there is no Options field for it. To
stamp a version computed at build time, drive codescan as a library and set it on
the returned document after Run:
doc, _:=codescan.Run(opts)
doc.Info.Version = buildVersion// e.g. injected via -ldflags "-X main.buildVersion=..."
Alternatively, overlay a base document that already carries the version with
Options.InputSpec (see
Overlaying a spec).
OpenAPI 2.0 splits authentication into two parts: security definitions name
the schemes (an API key, OAuth2, HTTP Basic), and security requirements
reference those schemes β document-wide and/or per operation. The panes below
are backed by the test-covered
docs/examples/concepts/security
package.
Declare the schemes
A SecurityDefinitions: block in swagger:meta declares every scheme once; a
Security: block sets the document-wide default requirement that applies to
operations that do not state their own.
The scheme type drives the rest: apiKey needs in + name, oauth2 needs a
flow (and the URLs/scopes it implies), basic needs nothing more. The full
scheme surface is in the
securityDefinitions reference.
Require a scheme on a route
A route with no Security: keyword inherits the document-wide default
(api_key, above). A route that needs something different states its own
Security: requirement β here createReport requires oauth2 with the read
and write scopes, overriding the default:
swagger:route
// listReports inherits the document-wide default requirement (api_key) β no// Security: keyword is needed.//
// swagger:route GET /reports reports listReports//
// responses:// 200: description: the reports// createReport overrides the default with its own Security: requirement β// oauth2 with the read and write scopes. The Security: block is YAML: a sequence// of requirement objects, scopes as a flow (or block) list.//
// swagger:route POST /reports reports createReport//
// Security:// - oauth2: [read, write]//
// responses:// 201: description: created// archiveReport requires BOTH schemes at once β two keys in a single sequence// item are ANDed into one requirement object (separate items would mean OR).//
// swagger:route POST /reports/archive reports archiveReport//
// Security:// - api_key: []// oauth2: [write]//
// responses:// 200: description: archived// publicReport opts out of the document default entirely β an empty// `Security: []` emits an explicit empty requirement, marking the operation// public regardless of the document-wide default.//
// swagger:route GET /reports/public reports publicReport//
// Security: []//
// responses:// 200: description: the public reports
A Security: block is plain YAML β a sequence of requirement objects.
Scopes are a flow list ([read, write]) or a block list; an empty list
(api_key: []) is the scheme with no scopes. The combining rule follows
OpenAPI 2.0:
multiple schemes in one item are ANDed β all are required;
separate items are ORed β satisfying any one grants access.
So requiring both an API key and an OAuth2 scope is two keys under a single
sequence item:
swagger:route
// listReports inherits the document-wide default requirement (api_key) β no// Security: keyword is needed.//
// swagger:route GET /reports reports listReports//
// responses:// 200: description: the reports// createReport overrides the default with its own Security: requirement β// oauth2 with the read and write scopes. The Security: block is YAML: a sequence// of requirement objects, scopes as a flow (or block) list.//
// swagger:route POST /reports reports createReport//
// Security:// - oauth2: [read, write]//
// responses:// 201: description: created// archiveReport requires BOTH schemes at once β two keys in a single sequence// item are ANDed into one requirement object (separate items would mean OR).//
// swagger:route POST /reports/archive reports archiveReport//
// Security:// - api_key: []// oauth2: [write]//
// responses:// 200: description: archived// publicReport opts out of the document default entirely β an empty// `Security: []` emits an explicit empty requirement, marking the operation// public regardless of the document-wide default.//
// swagger:route GET /reports/public reports publicReport//
// Security: []//
// responses:// 200: description: the public reports
A route’s requirements replace the document default for that operation. To make
one operation public β opting out of the document-wide default β give it an
empty Security: []. That emits an explicit empty requirement (distinct from
omitting the keyword, which inherits the default):
swagger:route
// listReports inherits the document-wide default requirement (api_key) β no// Security: keyword is needed.//
// swagger:route GET /reports reports listReports//
// responses:// 200: description: the reports// createReport overrides the default with its own Security: requirement β// oauth2 with the read and write scopes. The Security: block is YAML: a sequence// of requirement objects, scopes as a flow (or block) list.//
// swagger:route POST /reports reports createReport//
// Security:// - oauth2: [read, write]//
// responses:// 201: description: created// archiveReport requires BOTH schemes at once β two keys in a single sequence// item are ANDed into one requirement object (separate items would mean OR).//
// swagger:route POST /reports/archive reports archiveReport//
// Security:// - api_key: []// oauth2: [write]//
// responses:// 200: description: archived// publicReport opts out of the document default entirely β an empty// `Security: []` emits an explicit empty requirement, marking the operation// public regardless of the document-wide default.//
// swagger:route GET /reports/public reports publicReport//
// Security: []//
// responses:// 200: description: the public reports
The same works from a swagger:operation YAML body β a security: key there
sets that operation’s requirement. (The schemes themselves are always global
swagger:meta β OpenAPI 2.0 has no per-operation securityDefinitions.)
Keep security out of your code
Authentication is often handled by a layer in front of the app β a gateway or
service mesh β and you may not want security details in the annotations at all.
In that case, leave the code free of security annotations and overlay the
schemes and requirements with Options.InputSpec:
// base carries only the security scheme + default requirement.varbasespec.Swagger_ = json.Unmarshal(baseSpecJSON, &base)
doc, _:=codescan.Run(&codescan.Options{
Packages: []string{"./..."},
ScanModels: true,
InputSpec: &base, // securityDefinitions + security come from here})
The app package above (concepts/routes) carries no security annotations,
yet the merged document is secured β the schemes and the default requirement
come entirely from the base:
This capstone scans a tiny annotated “petstore” package and produces a Swagger
2.0 spec β the concepts from the pages above, assembled into one runnable
example. It is the worked version of
usage as a library.
The annotated API
A package-level swagger:meta block sets the top-level metadata:
// Package petstore Petstore API//
// A tiny pet store, used to demonstrate codescan annotations: the package// comment is a `swagger:meta` block carrying the top-level metadata of the// generated specification (title, description, version, base path, β¦).//
// Schemes: https// Version: 1.0.0// BasePath: /v1//
// Consumes:// - application/json//
// Produces:// - application/json//
// swagger:meta
A swagger:model struct becomes a definition, with field comments driving
validations:
// Pet is a single pet in the store.//
// swagger:model PettypePetstruct {
// The id of the pet.//
// required: true// minimum: 1IDint64`json:"id"`// The name of the pet.//
// required: true// min length: 1Namestring`json:"name"`// The tags associated with this pet.Tags []string`json:"tags,omitempty"`}
Marshalling the returned *spec.Swagger to JSON yields the document below β
the meta block became the top-level info / basePath, the swagger:route
became the /pets path, and the swagger:model became the Pet definition:
{
"consumes": [
"application/json" ],
"produces": [
"application/json" ],
"schemes": [
"https" ],
"swagger": "2.0",
"info": {
"description": "A tiny pet store, used to demonstrate codescan annotations: the package\ncomment is a `swagger:meta` block carrying the top-level metadata of the\ngenerated specification (title, description, version, base path, β¦).",
"title": "Petstore API",
"version": "1.0.0" },
"basePath": "/v1",
"paths": {
"/pets": {
"get": {
"tags": [
"pets" ],
"summary": "Lists all the pets in the store.",
"operationId": "listPets",
"responses": {
"200": {
"$ref": "#/responses/petsResponse" }
}
}
}
},
"definitions": {
"Pet": {
"type": "object",
"title": "Pet is a single pet in the store.",
"required": [
"id",
"name" ],
"properties": {
"id": {
"description": "The id of the pet.",
"type": "integer",
"format": "int64",
"minimum": 1,
"x-go-name": "ID" },
"name": {
"description": "The name of the pet.",
"type": "string",
"minLength": 1,
"x-go-name": "Name" },
"tags": {
"description": "The tags associated with this pet.",
"type": "array",
"items": {
"type": "string" },
"x-go-name": "Tags" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/petstore" }
},
"responses": {
"petsResponse": {
"description": "petsResponse is the list of pets returned by listPets.",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet" }
}
}
}
}
This JSON is not hand-written: it is a golden file the example’s test
regenerates and compares on every run (UPDATE_GOLDEN=1 go test ./...). Because
the example is ordinary, test-covered Go, go test ./docs/examples/... keeps
the page honest β if the scanner’s output changes, CI fails before the
documentation can go stale.
Seeing it rendered
The same golden spec, rendered as live API documentation by Swagger UI β what a
consumer of the generated document sees. This closes the loop the capstone is
about: annotated Go β the Swagger 2.0 JSON above β the API docs those
annotations produce. The widget reads the very same golden file, so the rendered
view can’t drift from the JSON either.
Shaping the output
The same annotated Go can render into the spec in more than one shape, and a
handful of codescan.Options
(plus a few field-level annotations) let you choose.
The guides are grouped by what they shape:
Scope & discovery β which packages are read and which types become
definitions.
Names & $refs β the names definitions are published under and how
references render.
Titles & descriptions β the human-readable text the spec carries.
Field types & formats β how an individual property renders.
Response bodies β describing a payload without a dedicated
swagger:response struct.
Choose what gets scanned and which definitions land in the spec β package
patterns and filters, when a type is emitted, pruning unreferenced models,
overlaying an existing document, and build constraints.
Control the names definitions are published under and how references render β
deconflicting collisions, deriving member names from struct tags, alias
rendering, and a description sitting beside a $ref.
Shape the human-readable text β override godoc with API-facing title and
description, route single-line comments to the description, keep annotations
out of the godoc, and clean godoc doc-links out of generated prose.
Tune how an individual field renders β force a conformant format, mark
pointer fields nullable, and control the x-go-* vendor extensions codescan
emits.
Describe a concrete response payload without a dedicated swagger:response
struct β declare the body inline on the route, or shadow a generic envelope’s
payload with a doc-only struct.
Each guide is task-oriented β “I want the output to look like this” β and shows the same input rendered both ways,
as before/after golden output the example tests verify. For the field-by-field meaning of every option,
see the Options reference
or the Options godoc.
Subsections of Shaping the output
Scope & discovery
These knobs decide the inputs and the surface of the scan: which packages
codescan reads, which types become definitions, and how that set is trimmed or
merged before anything is rendered.
codescan never invents definitions β a type appears only when it is reachable
or registered. Understand reachability and swagger:model so nothing goes
missing or appears unexpectedly.
Scan a shared library with swagger:model discovery, then keep only the
definitions actually reachable from your API β the middle ground between
“only what routes use” and “every model, used or not”.
Scan source guarded by Go build constraints by passing build tags to the
scanner.
Subsections of Scope & discovery
Scoping the scan
Several options narrow what codescan looks at, independent of how individual
types render. They decide which packages are loaded and which discovered
operations survive into the spec.
Package patterns and WorkDir
Options.Packages takes relative go list-style patterns β ./petstore,
./... for a whole tree β resolved against Options.WorkDir (the module root).
This is the worked form in the Getting started
guide:
To produce several specs from one module β e.g. one per API version β run a
scan per package tree (./v1/..., then ./v2/...) and write each result
separately. There is no single-run “split by version”; the unit of a scan is the
set of packages you pass.
Include / Exclude
Options.Include and Options.Exclude are lists of regular expressions matched
against package import paths. Include acts as an allow-list (when non-empty,
only matching packages are scanned); Exclude removes matches. Use them to keep
internal or generated packages out of the spec:
Options.IncludeTags / Options.ExcludeTags filter operations by their
Swagger tags after discovery β handy for publishing a public subset of an API
while keeping the admin routes in the source:
By default codescan may follow types into dependency packages to resolve
referenced models. Options.ExcludeDeps keeps the scan within your own module,
leaving out types pulled in from dependencies.
Build constraints get their own guide β see
Build tags.
When the scanner emits a type
codescan does not emit a definition for every type it can see. A named type
reaches the spec when either of these holds:
it is reachable β referenced (directly or transitively) from an operation,
parameter, response, or another emitted model; or
it is registered β annotated swagger:model, which (with
Options.ScanModels) publishes it even when nothing references it; or
it is a subtype of an emitted discriminated base β a swagger:model that
composes that base with swagger:allOf. This one runs against the reference
direction (a subtype $refs its base, never the reverse), so it is the one case
where a definition arrives without anything referencing it. See
Polymorphic models.
A type that is neither reachable nor registered is simply absent β the scanner
never invents it. The package below has one of each case:
// Order is reached only through Cart below. A referenced named type is emitted// as a $ref target even without swagger:model.typeOrderstruct {
// ID is the order identifier.IDstring`json:"id"`}
// Cart references Order, so Order gets a definition and the field a $ref.//
// swagger:modeltypeCartstruct {
// Order is the referenced (and therefore emitted) nested model.OrderOrder`json:"order"`}
// Standalone is never referenced, but swagger:model together with ScanModels// publishes it anyway.//
// swagger:modeltypeStandalonestruct {
// Label is a free-text label.Labelstring`json:"label"`}
// Orphan is never referenced and carries no swagger:model β the scanner does// not invent it, so it never reaches the spec.typeOrphanstruct {
// Secret is internal.Secretstring`json:"secret"`}
Scanned with ScanModels: true, the definitions are:
{
"Cart": {
"type": "object",
"title": "Cart references Order, so Order gets a definition and the field a $ref.",
"properties": {
"order": {
"$ref": "#/definitions/Order" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/discovery" },
"Order": {
"description": "Order is reached only through Cart below. A referenced named type is emitted\nas a $ref target even without swagger:model.",
"type": "object",
"properties": {
"id": {
"description": "ID is the order identifier.",
"type": "string",
"x-go-name": "ID" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/discovery" },
"Standalone": {
"description": "Standalone is never referenced, but swagger:model together with ScanModels\npublishes it anyway.",
"type": "object",
"properties": {
"label": {
"description": "Label is a free-text label.",
"type": "string",
"x-go-name": "Label" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/discovery" }
}
Order β has noswagger:model, yet it is emitted (as a $ref
target) because Cart references it. You do not need to annotate every nested
type.
Standalone β a swagger:model that nothing references; ScanModels
publishes it anyway.
Orphan β neither referenced nor annotated, so it never appears.
Info
If a model is missing from your spec, it is almost always unreachable: no
operation/parameter/response/model leads to it. Either reference it, or annotate
it swagger:model and scan with ScanModels. For the opposite problem β a
ScanModels scan that pulls in models you do not want, like Standalone β
see Pruning unused models.
Generic and embedded types
codescan resolves types through go/packages type information, so two forms
that look tricky still work:
Generics. An instantiated generic β WrappedRequest[Order], whether
annotated swagger:parameters or swagger:model β emits the concrete type:
the type argument is substituted, so a T-typed field becomes a $ref to the
argument’s definition. The generic’s declaration may live in a different file
from its instantiation. A free (un-instantiated) type parameter is skipped
with a warning.
Embedded fields, including those from an external package or a type
declared in another source file, are promoted into the embedding type (only
exported members, recursively), and a custom field type resolves to its
underlying type. An in: / required: annotation written on the embedded
field itself applies to all the members it promotes. An embed that carries a
json: tag (Base \json:“base”`) is **not** promoted β it nests as a single property of that name (a $ref` to the embedded type), matching Go’s
own JSON encoding.
A type the scanner cannot model β e.g. a bare function type β is skipped with
a warning rather than failing the whole scan; the annotated models around it
are still emitted.
Pruning unused models
Options.ScanModels (the -m flag) publishes everyswagger:model type it
finds, whether or not anything references it β see
When the scanner emits a type.
That is exactly what you want when the annotated package is the contract. It is
the wrong default when you point codescan at a large shared model library and
only care about the slice your API actually exposes: the spec fills up with
definitions no operation, parameter or response ever references.
Options.PruneUnusedModels is the middle ground. It runs swagger:model
discovery as usual, then drops every discovered definition that is not
reachable from your API surface.
Three emission modes
The same source renders three ways, depending on two options:
Mode
Options
What is emitted
Reachable only
(default)
Only models reachable from an operation, parameter or response β discovery-driven. A swagger:model that nothing references is not emitted.
Every model
ScanModels
Every swagger:model type, reachable or not. The library’s whole annotated surface lands in definitions.
Models, then pruned
ScanModels + PruneUnusedModels
Discovery runs as in Every model, then the unreachable definitions are pruned away β you keep the reachable subset, including models discovered only because swagger:model published them.
PruneUnusedModels is a modifier on ScanModels. Without ScanModels the
emitted set is already reachable-only, so the flag has nothing to do: it is a
no-op and says so with a single informational diagnostic.
What counts as reachable
A definition survives the prune when it is reachable β directly or transitively
through any $ref β from one of these roots:
an operation’s body parameters and response schemas;
The walk follows references through every schema shape β properties, allOf /
anyOf / oneOf, array items, additionalProperties, and so on β and
terminates cleanly on recursive or cyclic models. A model referenced only by
another unreferenced model is itself unreachable, so the whole dead subtree is
removed, not just its entry point.
One rule does not follow a $ref: a reachable definition that declares a
discriminator also keeps its subtypes. They compose the base rather than
being referenced by it, so the walk cannot see them and a polymorphic family would
otherwise be pruned down to its base alone. The family travels as a unit β an
unreachable base is still dropped, together with its subtypes. See
Polymorphic models.
Those shared response / parameter roots are pruned too. A
shared parameter or response
that no operation and no path-item references is itself dropped (with a
scan.pruned-unused Hint) β and because that happens before the definition
walk reads its roots from the same #/parameters / #/responses maps, a model
kept alive only by a now-pruned shared object becomes prunable in turn.
Shared objects supplied through InputSpec are pinned, exactly like
definitions.
Info
Definitions you supply through InputSpec are pinned: they are never pruned,
and they seed the reachability roots, so anything they $ref survives too. The
prune only ever removes definitions codescan discovered, never ones you handed
it.
Pruning happens before name resolution
This is the part that makes pruning more than a convenience. codescan keys every
definition by a compiler-unique identity while it builds, then a final stage
projects each one back to the shortest unique name β deconflicting cross-package
collisions along the way (billing.Account / identity.Account β
BillingAccount / IdentityAccount; see
Resolving $ref name conflicts).
PruneUnusedModels runs before that name-resolution stage. So when one half
of a colliding pair is unused, it is pruned first β and the collision never
happens. The surviving model keeps its clean, unqualified name instead of being
pushed to a package-qualified one to avoid a twin that is not even in your spec.
Pruning a shared library this way removes a whole class of surprising
#/definitions/<Pkg><Name> renames that only existed because of models you were
not using.
Diagnostics
The prune is never silent. Through the
OnDiagnostic sink
codescan reports:
scan.pruned-unused β one informational diagnostic per pruned definition,
located at the originating Go type, so you can see exactly what was dropped and
why; and the single no-op notice when the flag is set without ScanModels.
scan.renamed-definition β one per collision the name stage did resolve,
located at the Go type, recording the final name it landed under. With pruning
on, collisions that vanish produce no such diagnostic at all.
When to use it
Reach for PruneUnusedModels when you scan a shared or third-party model
package with -m and want only the definitions your API actually exposes β
the reachable subset, with the noise dropped and the collision churn gone.
Stay on plain ScanModels when the annotated package is itself the
published contract and every swagger:model is meant to appear.
Stay on the default (neither flag) when you only ever want what your routes
reference; there is nothing extra to discover or prune.
Overlaying a spec β
InputSpec, whose definitions are pinned against the prune.
Overlaying a spec
Options.InputSpec seeds the scan with an existing *spec.Swagger: codescan
merges what it discovers on top of it rather than starting from a blank
document. Use it to keep hand-authored top-level metadata or a hand-written
definition, or to compose a spec across several scans.
The scanned package contributes one model:
// Widget is discovered by the scan and merged onto the input spec.//
// swagger:modeltypeWidgetstruct {
// ID identifies the widget.IDstring`json:"id"`}
The document’s info, host, basePath and the hand-authored Health
definition survive untouched; only the discovered definitions are added.
Build tags
Go files can be guarded by //go:build constraints. By default codescan loads a
package under the default build configuration, so tag-gated files are skipped.
Options.BuildTags passes the tags through to the package loader, so the
annotations in those files are scanned too.
The package has an always-present model plus this one, in a file that opens with
the constraint //go:build experimental:
// Experimental is only scanned when the "experimental" build tag is set.//
// swagger:modeltypeExperimentalstruct {
// Beta flags a beta-only feature.Betabool`json:"beta"`}
BuildTags accepts the same comma-separated form as go build -tags.
Names & $refs
Once codescan knows which definitions to emit, these knobs govern how they are
named and referenced: the definition names that form your published $ref
contract, where member names come from, and the shape a reference takes in the
output.
When two Go types want the same definition name, codescan keeps them distinct
with deterministic, package-qualified names β and you stay in control of the
$ref names that form your published contract.
Choose how Go type aliases render β dissolved to their target, or exposed as a
first-class $ref via swagger:model, with RefAliases / TransparentAliases.
Render a plain struct embed as an allOf composition β a $ref to the embedded
model plus a sibling member for the embedding struct’s own fields β instead of
inlining the promoted properties, with DefaultAllOfForEmbeds.
Control how a field’s description and extensions are rendered when its type
resolves to a $ref β wrapped in an allOf, emitted as direct siblings
(EmitRefSiblings), or dropped (SkipAllOfCompounding).
Subsections of Names & $refs
Resolving $ref name conflicts
A Swagger definition is keyed by a single short name (#/definitions/Account),
but a Go program routinely has several types that would map to that name β
the same leaf declared in different packages, or a swagger:model Account
override applied twice. codescan keys every definition by a compiler-unique
identity (<package-path>/<name>) while it builds, then a final reduce stage
projects each identity back to the shortest name that is still unique. The
result is deterministic regardless of discovery or map-iteration order: no
silent overwrite, no lost definition.
Two packages each declare an Account, with entirely different fields:
// Account is the billing view of a customer account.//
// swagger:model AccounttypeAccountstruct {
// the current balance, in minor unitsBalanceint64`json:"balance"`// the ISO-4217 currency codeCurrencystring`json:"currency"`}
// Account is the identity view of a customer account.//
// swagger:model AccounttypeAccountstruct {
// the login emailEmailstring`json:"email"`// whether the email has been verifiedVerifiedbool`json:"verified"`}
A Dashboard model references both, so they are discovered together:
// Dashboard references the same-named Account from two packages plus both// ledger entries, forcing all of them to be discovered together. The two// Accounts collide on the short name and are deconflicted by package segment;// the refs below point at the resolved names, never a bare "Account".//
// swagger:model DashboardtypeDashboardstruct {
Billingbilling.Account`json:"billing"`Identityidentity.Account`json:"identity"`// the ledger entry that kept the name "Entry"Primaryledger.Entry`json:"primary"`// the duplicate that reverted to its Go name "Reversal"Secondaryledger.Reversal`json:"secondary"`}
Before name-identity, the two would have merged onto a single
#/definitions/Account β a union of fields, last package wins,
non-deterministically. Now each keeps its own definition and the references
resolve to the deconflicted names:
{
"description": "Dashboard references the same-named Account from two packages plus both\nledger entries, forcing all of them to be discovered together. The two\nAccounts collide on the short name and are deconflicted by package segment;\nthe refs below point at the resolved names, never a bare \"Account\".",
"type": "object",
"properties": {
"billing": {
"$ref": "#/definitions/BillingAccount" },
"identity": {
"$ref": "#/definitions/IdentityAccount" },
"primary": {
"$ref": "#/definitions/Entry" },
"secondary": {
"$ref": "#/definitions/Reversal" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nameconflicts"}
The reduce stage gives every reachable identity the shortest acceptable name:
A globally unique leaf is lifted to its bare name β byte-identical to the
pre-feature output, so the common case sees zero churn.
A colliding leaf is qualified with the minimal-depth PascalCase concat of
its nearest package segments (billing.Account / identity.Account β
BillingAccount / IdentityAccount), deepening one segment at a time until
the whole group is unique. A validate.colliding-model-name diagnostic
records each rename.
Every emitted definition also carries an x-go-package extension recording the
source package, so even identically-shaped collisions stay traceable:
{
"type": "object",
"title": "Account is the billing view of a customer account.",
"properties": {
"balance": {
"description": "the current balance, in minor units",
"type": "integer",
"format": "int64",
"x-go-name": "Balance" },
"currency": {
"description": "the ISO-4217 currency code",
"type": "string",
"x-go-name": "Currency" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nameconflicts/billing"}
The whole pass is a pure function of the reachable identity set, so the names
are stable across runs β but they are derived from your package paths.
Renaming or moving a package changes the segment used in a qualified name. Pin
the names that matter (see below).
Same-package duplicates
A single package cannot own a definition name twice. If two Go types in the
same package both claim swagger:model Entry, codescan keeps one
(deterministically) and reverts the other to its Go type name, with a
validate.duplicate-model-name diagnostic:
// Entry keeps the contested name: the definition is "Entry".//
// swagger:model EntrytypeEntrystruct {
Debitint64`json:"debit"`}
// Reversal also asks for "Entry". The name is already taken in this package, so// it reverts to its Go name, "Reversal", and a diagnostic is raised.//
// swagger:model EntrytypeReversalstruct {
Creditint64`json:"credit"`}
Here Entry keeps the contested name and Reversal falls back to its Go name β
the Dashboard refs above point at #/definitions/Entry and
#/definitions/Reversal, never a merged Entry. This is a genuine authoring
error (one package, one name); the fallback keeps the spec valid rather than
silently dropping a model.
Referencing a model by leaf across packages
The type-name keywords β swagger:type, swagger:additionalProperties, and
swagger:patternProperties β accept a bare leaf as their argument. codescan
resolves it the same way the reduce stage does: the annotating type’s own
package first, then uniquely across the scanned model set. A leaf unique in
another package resolves to a $ref:
swagger:additionalProperties Widget
// Bag is an open object whose additional properties are catalog.Widget,// named by the bare leaf "Widget" β resolved cross-package because that leaf is// unique across the scanned model set.//
// swagger:model Bag// swagger:additionalProperties WidgettypeBagstruct {
IDstring`json:"id"`}
{
"description": "named by the bare leaf \"Widget\" β resolved cross-package because that leaf is\nunique across the scanned model set.",
"type": "object",
"title": "Bag is an open object whose additional properties are catalog.Widget,",
"properties": {
"id": {
"type": "string",
"x-go-name": "ID" }
},
"additionalProperties": {
"$ref": "#/definitions/Widget" },
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/nameconflicts"}
If the leaf matches a model in several packages it is ambiguous: the
reference is dropped (never guessed) and a validate.ambiguous-type-name
diagnostic is raised. Disambiguate with a same-package type or pin the target
with a swagger:model <Name> override. The same leaf rule applies to the
additionalProperties: / swagger:patternProperties value forms covered in
Maps & free-form objects.
Keeping the exposed names under your control
The generated $ref names are part of your published contract, so the author β
not the resolver β should decide the ones that matter:
Pin a public name with an explicit swagger:model <Name>. A pinned name
is the identity’s leaf, so two pinned names that still collide are deconflicted
by package segment exactly like inferred ones β pin distinct names for the
types in your public surface.
Let auto-resolution handle the rest. Incidental or internal collisions get
a valid, stable, package-qualified name with no action from you.
Tuning the qualified names
Two scanner options steer the rare, deep collisions:
Option
Default
Effect
NameConcatBudget
0.65
Readability cutoff in [0,1] (lower is more readable). A collision group whose best flat concat scores above the budget becomes a candidate for the hierarchical fallback. Raise toward 1.0 to accept longer concats; lower to fall back sooner.
EmitHierarchicalNames
false
Opt into the fallback: over-budget groups are emitted as nested container definitions (#/definitions/<pkg>/<Name>, each tagged with x-go-package) instead of a long flat concat.
Warning
EmitHierarchicalNames is off by default on purpose. A nested definition is a
deep JSON pointer that only ExpandSpec resolves, and a definitions-enumerating
consumer (e.g. go-swagger codegen, one model per entry) sees the container nodes
rather than the models. The always-correct flat concat stays the default; enable
the nested shape only when you prefer it for the over-budget tail.
When to tune vs. let it auto-resolve
Pin the names that appear in your public API contract β clients,
generated SDKs, and hand-written $refs depend on them.
Let auto-resolution handle incidental collisions between internal types;
the qualified names are valid and stable.
Reach for EmitHierarchicalNames only when a few collision groups have
package names long enough to make the flat concat unwieldy, and your
consumers resolve $ref pointers (rather than enumerating definitions).
What’s next
Type discovery β which
types become definitions in the first place.
Pruning unused models β
drops unreferenced models before this name stage, so collisions caused only
by models you do not use never arise.
By default codescan derives a field’s spec name from its json: tag (then the
Go field name). Options.NameFromTags lets you choose which struct-tag types
supply the name, in precedence order β handy when your structs are tagged for
another binding library (for example gin’s form:). It applies everywhere a
name is derived from a field: schema properties, parameters, and response
headers. The model below tags every field with both json: and form::
// Filter is a query model whose fields carry both json: and form: tags.//
// swagger:modeltypeFilterstruct {
// SortKey selects the sort column.SortKeystring`form:"sort_key" json:"sortKey"`// PageSize bounds the page length.PageSizeint`form:"page_size" json:"pageSize"`}
The first listed tag that supplies a usable name wins; a tag that is absent or
carries only options (e.g. ,omitempty) is skipped and the next is tried. An
explicit empty list (NameFromTags: []string{}) consults no tag and falls back
to the Go field name.
Info
Name only.NameFromTags changes only the name. The encoding/json
directives β json:"-" (exclude), ,omitempty, ,string β are always read
from the json tag, whatever names the field. Targeted renames (the name:
keyword, swagger:name, and swagger:model {name}) still take precedence over
any tag-derived name.
Interface-method property names
When a model’s shape is described by an interface, its methods have no
natural JSON serialization β Go’s encoding/json can’t marshal interface
methods, so there’s no struct tag to read a name from. codescan invents one by
running its jsonify transform on the Go method name: ID β id, CreatedAt
β createdAt. That “one size fits all” convention isn’t always what you want β
an interface already named for its JSON shape, or a codebase with its own
canonical-name discipline, wants the Go name kept as-is.
SkipJSONifyInterfaceMethods opts out of the mangler. With it set, an
interface-method property is emitted under the Go method name verbatim. It is an
opt-out and defaults to off; with it off, output is unchanged.
What changes
This model is an interface with two default-path methods and one carrying a
swagger:name override:
// Account is a read model whose shape is described by interface methods.//
// swagger:model AccounttypeAccountinterface {
// ID is emitted as "id" by default; verbatim "ID" with the opt-out set.ID() string// CreatedAt is emitted as "createdAt" by default; verbatim "CreatedAt" with// the opt-out set.CreatedAt() string// swagger:name explicit_name//
// A swagger:name override is taken verbatim either way β re-mangling would// camelCase it to "explicitName".OverriddenField() string}
Scanned with the flag off the method names auto-jsonify; on, they ride through
verbatim:
Default β jsonified
{
"type": "object",
"title": "Account is a read model whose shape is described by interface methods.",
"properties": {
"createdAt": {
"description": "CreatedAt is emitted as \"createdAt\" by default; verbatim \"CreatedAt\" with\nthe opt-out set.",
"type": "string",
"x-go-name": "CreatedAt" },
"explicit_name": {
"description": "\nA swagger:name override is taken verbatim either way β re-mangling would\ncamelCase it to \"explicitName\".",
"type": "string",
"x-go-name": "OverriddenField" },
"id": {
"description": "ID is emitted as \"id\" by default; verbatim \"ID\" with the opt-out set.",
"type": "string",
"x-go-name": "ID" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/interfacenames"}
{
"type": "object",
"title": "Account is a read model whose shape is described by interface methods.",
"properties": {
"CreatedAt": {
"description": "CreatedAt is emitted as \"createdAt\" by default; verbatim \"CreatedAt\" with\nthe opt-out set.",
"type": "string" },
"ID": {
"description": "ID is emitted as \"id\" by default; verbatim \"ID\" with the opt-out set.",
"type": "string" },
"explicit_name": {
"description": "\nA swagger:name override is taken verbatim either way β re-mangling would\ncamelCase it to \"explicitName\".",
"type": "string",
"x-go-name": "OverriddenField" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/interfacenames"}
Default-path methods are jsonified.ID() β id, CreatedAt() β
createdAt; the original Go name is preserved as the x-go-name extension.
With the opt-out, the Go name is the property name.ID and CreatedAt
appear verbatim β and x-go-name drops, since it would now just repeat the
property name.
A swagger:name override is verbatim either way.OverriddenField is
published as explicit_name in both panes β the override already bypasses the
mangler, so the flag never touches it (and never re-mangles it to
explicitName).
Note
This flag only affects interface methods, which have no JSON serialization to
mirror. Struct-field property names are untouched β they always reflect what
encoding/json actually produces (see
Naming from struct tags to source those from
a different tag).
What’s next
Naming from struct tags β choose which
struct tag a field name comes from (the struct-field analogue of this knob).
A Go type alias (type Price = Money) is, to the Go type system, literally the
same type as its target. codescan’s default is to treat it that way: at a use
site the alias dissolves to its target, producing no definition of its own.
Annotated Go
// Money is the underlying model.//
// swagger:modeltypeMoneystruct {
// Cents is the amount in cents.Centsint64`json:"cents"`// Currency is the ISO currency code.Currencystring`json:"currency"`}
// Price is a Go alias of Money. By default an alias is a Go implementation// detail: at use sites it dissolves to its target, producing no definition of// its own.typePrice = Money// Invoice references Price; the field resolves to Money.//
// swagger:modeltypeInvoicestruct {
// Total is the invoice total.TotalPrice`json:"total"`}
Invoice.total is typed Price, but the field resolves straight to
#/definitions/Money β Price itself never appears.
Exposing an alias as a first-class entity
This is an advanced, rarely-needed case. To keep the alias name in the spec β
its own definition that other schemas $ref β annotate the alias with
swagger:model:
// Amount is the underlying model.//
// swagger:modeltypeAmountstruct {
// Cents is the amount in cents.Centsint64`json:"cents"`// Currency is the ISO currency code.Currencystring`json:"currency"`}
// Fee is a FIRST-CLASS alias: the swagger:model annotation keeps the alias name// in the spec instead of dissolving it to Amount.//
// swagger:modeltypeFee = Amount// Receipt references the alias, not the target.//
// swagger:modeltypeReceiptstruct {
// Charge is the fee charged.ChargeFee`json:"charge"`}
{
"Amount": {
"type": "object",
"title": "Amount is the underlying model.",
"properties": {
"cents": {
"description": "Cents is the amount in cents.",
"type": "integer",
"format": "int64",
"x-go-name": "Cents" },
"currency": {
"description": "Currency is the ISO currency code.",
"type": "string",
"x-go-name": "Currency" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass" },
"Fee": {
"description": "Fee is a FIRST-CLASS alias: the swagger:model annotation keeps the alias name\nin the spec instead of dissolving it to Amount.",
"$ref": "#/definitions/Amount" },
"Receipt": {
"type": "object",
"title": "Receipt references the alias, not the target.",
"properties": {
"charge": {
"$ref": "#/definitions/Fee" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass" }
}
RefAliases: true β the alias definition is a $ref chain
The right pane above: Fee becomes {"$ref": "#/definitions/Amount"}. One
shape, two names β the alias survives at use sites without duplicating the
target’s properties. Prefer this over the default whenever the alias is
genuinely a synonym: a copy drifts the moment the target changes.
TransparentAliases: true β use sites dissolve
{
"Amount": {
"type": "object",
"title": "Amount is the underlying model.",
"properties": {
"cents": {
"description": "Cents is the amount in cents.",
"type": "integer",
"format": "int64",
"x-go-name": "Cents" },
"currency": {
"description": "Currency is the ISO currency code.",
"type": "string",
"x-go-name": "Currency" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass" },
"Fee": {
"description": "Fee is a FIRST-CLASS alias: the swagger:model annotation keeps the alias name\nin the spec instead of dissolving it to Amount.",
"type": "object",
"properties": {
"cents": {
"description": "Cents is the amount in cents.",
"type": "integer",
"format": "int64",
"x-go-name": "Cents" },
"currency": {
"description": "Currency is the ISO currency code.",
"type": "string",
"x-go-name": "Currency" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass" },
"Receipt": {
"type": "object",
"title": "Receipt references the alias, not the target.",
"properties": {
"charge": {
"$ref": "#/definitions/Amount" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/aliases-firstclass" }
}
Receipt.charge now points straight at #/definitions/Amount: the alias is
gone from the reference graph.
Warning
Note what did not happen: Fee is still emitted. TransparentAliases
governs how an alias renders at its use sites, not whether an annotated
declaration produces a definition β so with ScanModels you get a Fee
definition that nothing references. Add
PruneUnusedModels to drop it, or
simply do not annotate an alias you intend to dissolve.
The three modes at a glance:
Fee definition
Receipt.charge
default (expand)
copy of Amount
$ref: Fee
RefAliases: true
$ref: Amount
$ref: Fee
TransparentAliases: true
copy of Amount, unreferenced
$ref: Amount
Wider calibration lives in the testdata/enhancements/alias-calibration-embed
golden trio.
Note
Most APIs never need first-class aliases β prefer naming a real swagger:model
type over aliasing one. Reach for RefAliases / TransparentAliases only when
you specifically need to control whether an alias name survives in the output.
The swagger:aliasannotation is
deprecated
and has no effect β alias rendering is governed by the plain Go alias plus these
options, or by swagger:model for a first-class definition.
Composing embeds with allOf
When a struct embeds another struct, Go promotes the embedded fields, and by
default codescan mirrors that: the embedded type’s properties are inlined flat
into the embedding schema. That is faithful to the Go value, but it loses the
“this composes Base” relationship β every embedding model emits its own flat
copy of the embedded fields, and a client generator can’t recover the shared
base type.
DefaultAllOfForEmbeds changes that. With the option on, a plain embed (one
with no explicit name and no swagger:allOf tag) is rendered as an allOf
member β exactly as if it carried
swagger:allOf β so the
composition relationship survives in the spec. It is opt-in and defaults to off;
with it off, output is byte-identical to before.
What composes
This model embeds a swagger:model type (Base), a non-model type (Mixin),
and adds an own field:
// Base is a reusable base model.//
// swagger:model BasetypeBasestruct {
IDint64`json:"id"`Namestring`json:"name"`}
// Mixin is a non-model embedded type (no swagger:model), reachable only through// embedding. Under the flag it composes as an inline allOf member, since it has// no definition of its own to $ref.typeMixinstruct {
Notestring`json:"note"`}
// PlainEmbed embeds a model and a non-model plainly, plus an own field.//
// swagger:model PlainEmbedtypePlainEmbedstruct {
BaseMixinColorstring`json:"color"`}
Reading the composed pane, each embed takes the path its kind dictates:
A model embed becomes a $ref member.Base is a swagger:model, so it
has its own definition and composes as {$ref: "#/definitions/Base"} β no
copy of id / name.
A non-model embed becomes an inline member.Mixin carries no
swagger:model, so it has no definition to point at; its note property
rides an inline allOf member instead.
The embedding struct’s own fields move to a sibling member.color is no
longer a top-level property β it lands in its own allOf arm alongside the
composed embeds.
What’s left alone
The flag only changes the untagged, unnamed embed β every other embed shape is
unaffected:
// PointerEmbed embeds a model through a pointer; the pointer is peeled and// takes the same $ref path as a value embed.//
// swagger:model PointerEmbedtypePointerEmbedstruct {
*BaseTagstring`json:"tag"`}
// NamedEmbed embeds Base under an explicit json name, so Go does not promote// it: it stays a single nested property, identical on or off (go-swagger#2038).//
// swagger:model NamedEmbedtypeNamedEmbedstruct {
Base`json:"base"`Extrastring`json:"extra"`}
// TaggedEmbed already composes Base via an explicit swagger:allOf tag, so the// flag does not change its shape.//
// swagger:model TaggedEmbedtypeTaggedEmbedstruct {
// swagger:allOfBaseFieldstring`json:"field"`}
Pointer embeds are peeled first, so *Base composes to the same
$ref member as a value embed.
A json-named embed is not a promotion. Giving the embed a json tag
(Base \json:“base”`) makes it a single nested property named base`, on or
off β Go doesn’t promote a named embed (go-swagger#2038).
An explicit swagger:allOf embed already composes, so the flag is a no-op
for it; it only makes allOf the default for untagged embeds.
Interface embeds compose via allOf regardless of this flag.
Note
DefaultAllOfForEmbeds is the global default-on switch for the same shape
swagger:allOf produces per-embed. Reach for the annotation when only some
embeds should compose; reach for the option when composition is your house style
for every plain embed.
Composition needs a marshaller you write
An allOf says the JSON document satisfies every member at once β one flat object carrying all
their properties. Go’s default marshaller only produces that shape by coincidence, and the
coincidence holds for exactly one case: a plain struct embed with no marshaller of its own, whose
fields Go promotes.
Step outside that case and the default rendering stops matching the spec:
a member that is not a struct β a map, a slice, a named basic β promotes nothing, so Go emits
it as one key named after the type instead of merging it;
a member with its own MarshalJSON/MarshalText is promoted into your type’s method set, and
json.Marshal then consults it before reading any field β rendering the whole struct as whatever
that method returns.
This is why go-swagger’s generated models never rely on the default. A model with allOf embeds its
members and carries a hand-written pair that flattens them, reading every member from the same
raw document:
// swagger:model WithAllOftypeWithAllOfstruct {
Notable// an allOf memberAO1map[string]int32`json:"-"`// a map member β json:"-" keeps the default out of the wayWithAllOfAO2P2// another memberBodystring`json:"body,omitempty"`// the model's own fieldsTitlestring`json:"title,omitempty"`}
// UnmarshalJSON reads every member from the SAME document β that is what allOf means.func (m*WithAllOf) UnmarshalJSON(raw []byte) error {
varaO0Notableiferr:=jsonutils.ReadJSON(raw, &aO0); err!=nil {
returnerr }
m.Notable = aO0varaO1map[string]int32iferr:=jsonutils.ReadJSON(raw, &aO1); err!=nil {
returnerr }
m.AO1 = aO1// β¦ one block per member, then the model's own fields}
Warning
If you hand-write the Go types that codescan scans, swagger:allOf describes your intent; it
does not make encoding/json produce that document. Write the marshaller, or generate the model
from the spec and let go-swagger write it for you. codescan reads declarations β it cannot tell
whether the marshalling you need exists, so it will not warn you.
Because of this, codescan reads an embed as composition and never as an instruction about the
default marshaller. In particular, a promoted MarshalText/MarshalJSON on an embedded type is
not treated as a claim that the whole model is a scalar β see
Forcing a conformant format if you want a type rendered as
one.
Annotate the embedded type, not the embed
A classifier annotation in an embedded field’s doc comment does nothing.
swagger:strfmt and swagger:type written there are ignored β codescan reports
them under scan.ineffective-annotation rather than dropping them quietly:
typeWrongstruct {
// swagger:strfmt uuid β ignored, and warned aboutToken}
An embed contributes the shape of the type it embeds, and what that shape is
comes from that type’s own declaration. Put the annotation there and every embed
of it composes the same way:
// swagger:strfmt uuidtypeToken [16]byte
The catch is that both annotations are honoured on an ordinary field, so the
same line means something one field down and nothing on an embed. Only
swagger:allOf, swagger:omit,
swagger:name, swagger:ignore and a required: inheritance hint act on an
embed itself β everything else describes the embedded type and belongs with it.
When an override cannot be composed
Composition has one limit worth knowing. Inlining an embed resolves an
override β a field the enclosing struct re-declares wins, exactly as Go’s depth
rule decides it. allOf instead accumulates: members conjoin, and a
conjunction can only narrow, never replace. So a re-declaration that replaces
is not expressible as composition:
re-declaring a promoted field to decorate it (add readOnly, a description, a
validation) leaves the property in both members β valid, but a generator
walking the members sees it twice;
re-declaring it with a different type yields {type: integer}and{type: string} for one property β a schema nothing can satisfy.
codescan does not guess which declaration you meant: many Go types can be
written whose composition has no faithful schema, and inventing one would be
deciding your intent. Resolve it yourself with
swagger:omit on the
embed, which drops the promoted twin so only your re-declaration survives:
typeDecoratedstruct {
// swagger:omit IDBase// ID is assigned by the server.//
// read only: trueIDint64}
What’s next
swagger:omit β drop
what an embed promotes but the API should not carry.
Polymorphic models β the
swagger:allOf annotation and discriminator hints this option generalises.
When a struct field’s Go type resolves to a named model, the field becomes a
$ref. Strict JSON Schema draft 4 (the dialect OpenAPI 2.0 is built on) says a
$refreplaces its siblings β so a description, a validation, or an x-*
extension written on that field cannot simply sit next to the $ref.
codescan’s default is to preserve those decorations by wrapping the reference in
an allOf compound, which is the draft-4-correct shape. Three options tune
this behaviour. The decorations split into two classes:
description & extensions β siblings-eligible: modern tooling (OpenAPI
3.1 / JSON Schema 2020-12, most Swagger-UI renderers) reads them directly
beside a $ref.
validations & externalDocs β compound-only: they have no valid
bare-$ref form, so they can only ride an allOf compound.
// Address is a referenced model.//
// swagger:modeltypeAddressstruct {
// Street is the street line.Streetstring`json:"street"`}
// Person references Address through a field decorated with a description and a// vendor extension β both can, in principle, sit beside the $ref. How they are// rendered depends on the options.//
// swagger:modeltypePersonstruct {
// Home is where the person lives.//
// extensions:// x-ui-order: 3HomeAddress`json:"home"`}
With no options set, the field’s description and extension are preserved by
wrapping the $ref as the single member of an allOf; the decorations ride the
outer schema:
{
"description": "Home is where the person lives.",
"allOf": [
{
"$ref": "#/definitions/Address" }
],
"x-go-name": "Home",
"x-ui-order": 3}
This is the always-correct shape and needs no configuration β see also
Decorating a $ref in the
Model definitions tutorial.
Emit siblings directly β EmitRefSiblings
Set Options.EmitRefSiblings to render the description and extensions as
direct siblings of the $ref, with no allOf wrapper β the leaner shape
modern tools expect:
EmitRefSiblings only changes the cases where nothing else forces a compound.
When the field also carries a validation or externalDocs (which cannot
live beside a bare $ref), the allOf wrapper is still emitted and the
description / extensions ride its outer schema.
Drop the compound entirely β SkipAllOfCompounding
Some downstream consumers β notably go-swagger’s code generator β expect a field
that points at a model to be a bare $ref and do not handle the
allOf-compounded shape. Set Options.SkipAllOfCompounding to never emit an
allOf compound:
Every dropped decoration is reported through Options.OnDiagnostic (code
validate.dropped-ref-sibling), so the loss is never silent. Combine it with
EmitRefSiblings to keep the description and extensions as siblings while still
dropping the compound-only validations:
codescan.Run(&codescan.Options{
Packages: []string{"./..."},
ScanModels: true,
EmitRefSiblings: true, // keep description / x-* as $ref siblingsSkipAllOfCompounding: true, // drop validations / externalDocs, no allOf})
Note
required: is never affected by any of these options. It is a property of the
parent object (it lands in the parent’s required list), not a sibling of the
$ref, so it is always preserved.
DescWithRef (deprecated)
Options.DescWithRef predates EmitRefSiblings and covers only the narrow
description-only case: a $ref’d field whose sole decoration is a
description. By default that description is dropped; DescWithRef preserves it
by wrapping the $ref in a single-arm allOf.
Default β description dropped
{
"description": "Person references Address through a field whose only decoration is a\ndescription.",
"type": "object",
"properties": {
"home": {
"$ref": "#/definitions/Address" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/descref"}
{
"description": "Person references Address through a field whose only decoration is a\ndescription.",
"type": "object",
"properties": {
"home": {
"description": "Home is where the person lives.",
"allOf": [
{
"$ref": "#/definitions/Address" }
],
"x-go-name": "Home" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/descref"}
DescWithRef is deprecated β prefer EmitRefSiblings, which preserves both
descriptions and extensions (as direct siblings). DescWithRef keeps its
original behaviour for compatibility and is a no-op when EmitRefSiblings is
set.
Titles & descriptions
The same Go doc comments feed both pkg.go.dev and your API documentation, and
the two audiences rarely want the exact same words. These knobs let you keep a
concise godoc while curating the title / description text the spec carries.
Replace the godoc-derived title and description with API-facing text using
swagger:title and swagger:description β on models, fields, $ref’d fields and
responses.
Carry a verbatim markdown body β tables, blank lines, indentation and all β
into a description with the swagger:description | literal block-scalar marker,
instead of letting Option B fold it.
Let swagger annotations live inside a struct body or as trailing comments so
the godoc above each declaration stays clean β the AfterDeclComments opt-in.
Strip godoc doc-link brackets from generated descriptions and recompose
resolvable links to each schema’s exposed name β the CleanGoDoc opt-in.
Subsections of Titles & descriptions
Overriding titles & descriptions
A Go doc comment is written for Go readers. The same prose is not always what
you want in the published API β a comment may explain internal usage, reference
Go types, or simply read awkwardly to an API consumer. swagger:title and
swagger:description let the spec text diverge from the godoc: the
annotation replaces the prose-derived value, leaving the Go comment free to say
whatever Go developers need.
This is the explicit counterpart to
Single-line comments,
which controls how a plain comment is implicitly routed to title vs
description. Each pane below pairs the annotated Go (left) with the exact
fragment the scanner emits (right), from the test-covered
docs/examples/shaping/overrides
package.
Overriding a model and its fields
swagger:title <text> sets the title; swagger:description <text> sets the
description. Both sit in the comment block beside swagger:model (on a type)
or beside a field’s other keywords. The model’s Go-facing godoc here is replaced
wholesale by the two overrides:
// Widget is the Go-facing widget doc, written for Go readers.//
// It explains internal Go usage that should not leak into the API spec.//
// swagger:model// swagger:title A Public Widget// swagger:description A widget exposed via the public API.typeWidgetstruct {
// ID explains the Go field for Go readers.//
// swagger:description The unique widget identifier.IDstring`json:"id"`// Label is the Go-facing field doc. Fields carry no title by default;// the override is the only way a property gets one.//
// swagger:title Display Label// swagger:description Human-readable label shown to API consumers.Labelstring`json:"label"`// Plain keeps its godoc description because it carries no override.Plainstring`json:"plain"`// Capacity combines a description override with an inline validation// keyword on the same field: the override applies AND maximum is kept,// because the override annotations dispatch through the schema family.//
// swagger:description The maximum capacity, in liters.// maximum: 1000Capacityint64`json:"capacity"`// Suppressed has a godoc that a bare swagger:description suppresses: the// empty value is applied (description omitted) and scan.empty-override is// raised, in case the bare marker was left behind by mistake.//
// swagger:descriptionSuppressedstring`json:"suppressed"`// Notes carries a multi-line description override: the lines following the// annotation fold into the description until the blank line, joined with// newlines.//
// swagger:description Free-form notes about the widget.// They may span several lines, all folded into one description.//
// The blank line above terminates the override body; this paragraph is// ordinary godoc and is discarded (the override won).Notesstring`json:"notes"`// Gadget is a $ref field carrying title + description overrides. They are// symmetric $ref siblings: kept under EmitRefSiblings, dropped to a bare// $ref under the default flags β the same rule a prose description follows.//
// swagger:title Gadget Ref// swagger:description The attached gadget, described for API consumers.GadgetGadget`json:"gadget"`}
// Gadget is a plain referenced model.//
// swagger:modeltypeGadgetstruct {
Serialstring`json:"serial"`}
title on a property comes only from an override. A field’s godoc
becomes its description; codescan never derives a property title from
prose, so swagger:title (as on label) is the only way to set one.
plain keeps its godoc β no override means no change. Overrides are
strictly opt-in; un-annotated declarations behave exactly as before.
Multi-line descriptions
swagger:description may span several lines. The lines immediately following
the annotation fold into one description (joined with newlines) and the body
terminates at the first blank line, keyword, annotation, or end of comment.
The notes field above shows this: its two prose lines fold together, and the
ordinary godoc paragraph after the blank line is discarded.
To carry a body past a blank line β a markdown table, a multi-paragraph
description β end the annotation line with a | literal block marker; see
Markdown descriptions.
Keeping a co-located validation keyword
Because the override annotations dispatch through the schema family, a
validation keyword on the same field still applies β they co-exist rather
than one shadowing the other. The capacity field carries both
swagger:description and maximum: 1000, and the output keeps both.
Suppressing a godoc comment
A bareswagger:description (no text, empty body) applies the empty value
β a deliberate way to drop a godoc comment from the spec without deleting it
from the source. Because a stray bare marker could also be an accident,
codescan raises a scan.empty-override warning through OnDiagnostic. The
suppressed field above emits no description at all.
Overrides beside a $ref
title and description are symmetric $ref siblings: on a field whose Go
type is a referenced model, they follow the same preservation rule a prose
description does. Under the default flags they drop to a bare $ref; with
EmitRefSiblings
they ride alongside the $ref as direct siblings.
swagger:description also overrides the description of a swagger:response and
of its response headers. OpenAPI 2.0 Response and Header objects have no
title field, so a swagger:title on a response or header is rejected with a
parse.context-invalid diagnostic β the description override still applies.
// ErrorResponse is the Go-facing response doc, written for Go readers β it// should not leak into the API spec.//
// swagger:response errorResponse// swagger:description The error payload returned to API consumers.// swagger:title Ignored β responses have no titletypeErrorResponsestruct {
// XErrorCode is the Go-facing header doc.//
// swagger:description The machine-readable error code.XErrorCodestring`json:"X-Error-Code"`// ErrorBody carries the structured error.//
// in: bodyBodyErrorBody`json:"body"`}
// ErrorBody is the error payload returned in the response body.//
// swagger:modeltypeErrorBodystruct {
Messagestring`json:"message"`}
Precedence. An override always wins over the godoc-derived value. Absent β
the godoc is used unchanged. Empty (bare marker) β the empty value is applied
andscan.empty-override is raised. swagger:title is schema-only; on a
response/header it is dropped with parse.context-invalid.
What’s next
Single-line comments
β the implicit title vs description routing this overrides.
A multi-line swagger:description normally folds its body with the Option B
rule: contiguous prose lines up to the first blank line, each trimmed. That’s
right for a paragraph of prose, but it destroys markdown β a blank line ends the
description, and leading indentation and table pipes are stripped. So a table or
a multi-paragraph body never survives the trip into the spec.
Ending the annotation line with a lone | β the YAML literal block-scalar
marker β opts the body into verbatim capture instead. Everything below is
taken exactly as written β blank lines, indentation, table pipes and --- all
preserved β until the next annotation or the end of the comment. It is opt-in
per annotation; a plain swagger:description (no |) keeps the Option B
behaviour unchanged.
Plain prose vs a verbatim body
These two models carry the same markdown body. The first uses an ordinary
annotation; the second adds the | marker:
// Plain uses an ordinary description annotation.//
// swagger:description// Option B folds prose up to the first blank line, so only this sentence// survives β the markdown table below never reaches the spec.//
// | name | purpose |// |------|---------|// | foo | bars |//
// swagger:model PlaintypePlainstruct {
Namestring`json:"name"`}
// Markdown opts into a verbatim body with the literal block marker.//
// swagger:description |// The body is captured **verbatim** β pipes, blank lines and all://
// | name | purpose |// |------|---------|// | foo | bars |//
// - point one// - point two//
// swagger:model MarkdowntypeMarkdownstruct {
// Name of the widget.//
// swagger:description |// The name must be://
// 1. unique// 2. lowercaseNamestring`json:"name"`}
{
"description": "Option B folds prose up to the first blank line, so only this sentence\nsurvives β the markdown table below never reaches the spec.",
"type": "object",
"title": "Plain uses an ordinary description annotation.",
"properties": {
"name": {
"type": "string",
"x-go-name": "Name" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/markdowndesc"}
{
"description": "The body is captured **verbatim** β pipes, blank lines and all:\n\n| name | purpose |\n|------|---------|\n| foo | bars |\n\n- point one\n- point two",
"type": "object",
"title": "Markdown opts into a verbatim body with the literal block marker.",
"properties": {
"name": {
"description": "The name must be:\n\n 1. unique\n 2. lowercase",
"type": "string",
"x-go-name": "Name" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/markdowndesc"}
Option B stops at the first blank line. The plain model’s description is
just the opening sentence β the table that follows the blank line is dropped
entirely (the original go-swagger#3211 grievance).
The | body is captured whole. Table leading pipes, the significant blank
line, and the bullet list after it all ride through verbatim.
The marker never leaks. The trailing |, the swagger:description line
itself, and the single godoc // convention space per line are all stripped;
interior indentation and trailing whitespace (markdown hard breaks) are kept.
The title is unaffected. It still comes from the godoc preamble above the
annotation β only the description body becomes verbatim.
It works on a field description just the same β the name property above
keeps the indentation of its ordered list ( 1. unique).
Where the block ends
The literal block runs until the next annotation at the start of a line, or
the end of the doc comment. In the examples above, the trailing
swagger:model Widget line closes the block.
A swagger: token mid-line is ordinary prose and stays in the body β only a
line that begins with an annotation terminates. Indentation doesn’t shield
such a line, though: the comment prefix and leading whitespace are stripped
before the check, so a line-leading swagger: inside an indented markdown code
block still ends the block. Keep annotation-looking lines out of the verbatim
body, or place them before the | annotation.
Note
This reframes go-swagger#3211:
markdown is authored explicitly via swagger:description |, never recovered
from ambient godoc prose. A plain doc comment stays plain β godoc and the spec
keep their separate conventions.
By the first-sentence convention, a single-line doc comment that ends in
punctuation becomes the object’s title (on a model or the info block) or
summary (on an operation); without trailing punctuation it is a description.
That is the right default for most codebases, but some use single-line comments
purely as prose β and then a stray period silently promotes the line to a title.
Options.SingleLineCommentAsDescription opts out of the promotion: a single-line
comment is always a description, never a title / summary.
The same source, scanned both ways β the comment moves from title / summary
to description uniformly:
Default β title / summary
{
"modelDescription": "",
"modelTitle": "Gadget is a small device.",
"operationDescription": "",
"operationSummary": "Lists every gadget in the catalog."}
{
"modelDescription": "Gadget is a small device.",
"modelTitle": "",
"operationDescription": "Lists every gadget in the catalog.",
"operationSummary": ""}
Only the single-line case changes. A multi-line doc comment keeps the
existing split β the first line (or the paragraph before the first blank line)
stays the title, and the rest becomes the description. Reach for this option
when your house style writes one-line prose descriptions and you don’t want them
landing in title / summary; otherwise leave it off (the default) and write a
two-line comment, or drop the trailing period, when you want a description.
What’s next
Document metadata β the
info title/description split this option also governs.
Model definitions β the
title/description convention on a swagger:model.
Vendor extensions β
other Options knobs that reshape the emitted spec.
Keeping annotations out of the godoc
A godoc comment and an API description pursue different goals. The godoc is for
the Go developers reading the package; the API text is for the consumers of the
generated spec. By default codescan reads its annotations from the doc comment
above a declaration, which mixes the two concerns β a swagger:model,
maxProperties: or swagger:strfmt line sits right in the middle of the prose a
Go reader sees.
AfterDeclComments separates them. With the option on, codescan also reads
annotations placed inside a struct body (its leading comment) or inlined as
a trailing comment on the same line as the declaration. The godoc above stays
concise and human-facing while the swagger machinery lives out of it β same
annotation grammar, no new syntax. It is the placement counterpart to
overriding titles & descriptions,
which separates the same two concerns at the text level.
Each pane below pairs the annotated Go (left) with the exact fragment the scanner
emits (right), from the test-covered
docs/examples/shaping/afterdecl
package.
Inside a struct body, or trailing on a field
The swagger:model annotation (and any decl-level keyword such as
maxProperties:) can live as the leading comment inside the struct body,
above the first field. A field-level annotation like swagger:strfmt can ride a
trailing comment on the field line. The godoc above Widget says nothing
about swagger:
// Widget is a widget. This godoc stays clean β no swagger machinery here.typeWidgetstruct {
// Widget is exposed to API consumers.//
// swagger:model widgetModel// maxProperties: 5Namestring`json:"name"`// Created is documented with a clean godoc; the format annotation is// inlined as a trailing comment.Createdstring`json:"created"`// swagger:strfmt date}
AfterDeclComments is opt-in and defaults to off β so existing code, where a
clean comment that happens to look like an annotation is just prose, is never
reinterpreted:
With the option off, the inside-body and trailing annotations above are inert
β the clean godoc carries no annotation, so nothing is discovered. With it
on, the same source yields the three definitions, each with its keywords
applied (and the clean godoc still supplies the human-facing title):
Scope (v0.36). The opt-in covers type declarations β a struct’s
inside-body leading comment, a struct field’s trailing comment, and the trailing
comment of a defined type or alias. Routes and operations are already
position-agnostic: a swagger:route / swagger:operation block inside a
function body is discovered with or without this option. Const-based enums are a
planned follow-up.
A Go doc comment can use godoc’s doc-link
syntax β [Gadget], [Order.CustName], reference-style [text]: url lines.
Those render as live links in pkg.go.dev, but carried verbatim into a spec
title / description they read as bracket noise, and the bracketed Go
identifier is rarely the name the schema is actually exposed under.
CleanGoDoc tidies that up. With the option on, godoc doc-link brackets are
removed and β when a link resolves to a scanned schema β the span is recomposed
to the name that schema is exposed under, so the prose stays true to the
generated definitions. It applies only to godoc-derived prose; an
author-written
swagger:title / swagger:description
override is deliberate text and is never touched.
Each pane below pairs the annotated Go (left) with the exact fragment the scanner
emits (right), from the test-covered
docs/examples/shaping/godoclinks
package.
What gets cleaned
This model β its doc comment and its fields β is dense with doc-link syntax: a
self-reference, links to other models, a pointer, a cross-package link, an
unknown identifier, ordinary brackets, and a reference-definition line:
// Widget is the primary [Gadget] holder and references [Order.CustName].//
// More detail mentions a [*Gadget] pointer, a [inventory.Ledger], and an// unknown [Sprocket].//
// swagger:model gizmotypeWidgetstruct {
// Holder points at the [Gadget] that owns this widget.Holderstring`json:"holder"`// Ledger is the cross-package [inventory.Ledger] reference.Ledger*inventory.Ledger`json:"ledger"`// Index is element [0] in the [see notes] list; the [id] stays bare.Indexint`json:"index"`// Spec points at [Gadget]; the reference-definition line below is godoc// link plumbing that carries no prose.//
// [the spec]: https://example.com/specSpecstring`json:"spec"`}
Scanned with CleanGoDoc off the godoc is emitted verbatim; on, every doc-link
is resolved or humanized and the reference-definition line is dropped:
Default β verbatim
{
"description": "More detail mentions a [*Gadget] pointer, a [inventory.Ledger], and an\nunknown [Sprocket].",
"type": "object",
"title": "Widget is the primary [Gadget] holder and references [Order.CustName].",
"properties": {
"holder": {
"description": "Holder points at the [Gadget] that owns this widget.",
"type": "string",
"x-go-name": "Holder" },
"index": {
"description": "Index is element [0] in the [see notes] list; the [id] stays bare.",
"type": "integer",
"format": "int64",
"x-go-name": "Index" },
"ledger": {
"$ref": "#/definitions/Ledger" },
"spec": {
"description": "Spec points at [Gadget]; the reference-definition line below is godoc\nlink plumbing that carries no prose.\n\n[the spec]: https://example.com/spec",
"type": "string",
"x-go-name": "Spec" }
},
"x-go-name": "Widget",
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/godoclinks"}
{
"description": "More detail mentions a Gadget pointer, a Ledger, and an\nunknown sprocket.",
"type": "object",
"title": "Gizmo is the primary Gadget holder and references Order.customer_name.",
"properties": {
"holder": {
"description": "Holder points at the Gadget that owns this widget.",
"type": "string",
"x-go-name": "Holder" },
"index": {
"description": "Index is element [0] in the [see notes] list; the [id] stays bare.",
"type": "integer",
"format": "int64",
"x-go-name": "Index" },
"ledger": {
"$ref": "#/definitions/Ledger" },
"spec": {
"description": "Spec points at Gadget; the reference-definition line below is godoc\nlink plumbing that carries no prose.",
"type": "string",
"x-go-name": "Spec" }
},
"x-go-name": "Widget",
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/godoclinks"}
Links recompose to the exposed name.[Gadget] β Gadget (no override, so
its Go name); the [Order.CustName] member link β Order.customer_name (the
model name plus the field’s json name); and the leading self-name Widget
β Gizmo, because the model is published as swagger:model gizmo (restored to
sentence case). A cross-package [inventory.Ledger] resolves through the
file’s imports to Ledger.
Unresolved links are humanized.[Sprocket] names no scanned model, so it
becomes the plain word sprocket rather than a dangling bracket.
Reference-definition lines are dropped. The [the spec]: https://β¦ line on
the spec field is link plumbing carrying no prose, so the whole line is
removed.
Info
It recomposes to the final exposed name. The substitution runs after
codescan resolves definition names, so a link to a model that gets
renamed to deconflict a collision
points at the renamed definition, not the original Go identifier.
Conservative by design
Only a genuine doc-link is rewritten β a dotted chain ([pkg.Type]) or an
uppercase-led identifier ([Widget]). Ordinary prose brackets are left exactly
as written, as the index field above shows: [0], [see notes] and the
bare-lowercase [id] all survive untouched.
CleanGoDoc is opt-in and defaults to off β with it off, output is
byte-identical to before, so existing specs never shift under you.
These knobs act at the level of a single property: the format it carries,
whether a pointer is advertised as nullable, and the vendor extensions that
record its Go provenance.
How io.Reader, multipart.File and the other stream types render β type: file
on a formData parameter, base64 bytes everywhere else β and how to say what
the bytes actually are.
Control the x-go-* vendor extensions codescan emits, or suppress them with
SkipExtensions.
Subsections of Field types & formats
Forcing a conformant format
codescan derives a JSON-Schema type and format from each Go type. For the
unsized and large integer kinds it emits Go-specific vendor formats β
uint64 β {type: integer, format: uint64}, uint32 β {integer, uint32},
and so on. These round-trip cleanly back to Go, but they are not part of the
Swagger 2.0 format set, and a uint64 value can exceed what a JSON number
safely represents.
When you need conformant, precision-safe output, place a field-levelswagger:strfmt on the field to override just that property’s format. Overriding
to int64 publishes the value as a string-encoded {type: string, format: int64}:
model
// Measurement forces a JSON-conformant format on a field. A uint64 field emits// the Go-specific `{integer, format: uint64}` by default; overriding it with a// field-level `swagger:strfmt int64` (below) yields a precision-safe,// string-encoded `{string, format: int64}`.//
// swagger:modeltypeMeasurementstruct {
// Raw keeps the default Go-derived vendor format (uint64).Rawuint64`json:"raw"`// Bounded is forced to a conformant, string-encoded int64.//
// swagger:strfmt int64Boundeduint64`json:"bounded"`}
Raw keeps the default {integer, uint64} vendor format; Bounded carries
swagger:strfmt int64, so it renders as a string-encoded int64. The override
is per-field β the underlying Go type is untouched everywhere else.
Info
swagger:strfmt also names a custom string format on a type declaration (e.g.
a UUID type β {string, format: uuid}); see
Model definitions β swagger:strfmt.
The swagger:type annotation is the related tool when you want to override the
whole type, not just its format β see
Type discovery and the
swagger:type reference.
File uploads and byte streams
A Go type like io.Reader says that bytes will flow. It says nothing about
what they are, how they are framed, or how long they run. codescan recognizes
these types and answers with the only two things Swagger 2.0 lets it say about
opaque bytes β picked by where the field sits, not by anything in the
declaration.
The two answers
Position
Rendering
in: formData parameter
type: file
model field, body, response body, header, other parameters
{type: string, format: byte}
type: file is the canonical upload shape, and formData is the only location
Swagger 2.0 permits it in. Everywhere else the bytes travel inside a JSON
document, which cannot carry raw octets β so they render as format: byte, the
base64-encoded string the specification defines for exactly this.
Uploading a file
Put the stream in a formData parameter and consume multipart/form-data:
parameters
// UploadParams uploads a file and its metadata.//
// swagger:parameters uploadAttachmenttypeUploadParamsstruct {
// Upload is the file to store.//
// in: formDataUploadmultipart.File`json:"upload"`// Caption describes the upload.//
// in: formDataCaptionstring`json:"caption"`}
upload becomes type: file; the sibling caption is an ordinary form field.
multipart.File and io.Reader are interchangeable here β both are recognized.
Streams in a model or a body
Anywhere that is not a formData parameter, the same types render as base64
bytes:
model
// Attachment carries opaque byte streams as model fields.//
// A stream says nothing about its own framing, so codescan does not invent one:// each field renders as `{string, format: byte}` β the base64-encoded string// Swagger 2.0 uses for arbitrary bytes.//
// swagger:modeltypeAttachmentstruct {
// Content is the attachment payload.Contentio.Reader`json:"content"`// Thumbnail is a closeable stream; the same answer applies.Thumbnailio.ReadCloser`json:"thumbnail"`// Checksum says what its bytes are, so the annotation wins over the default.//
// swagger:strfmt base64Checksumio.Reader`json:"checksum"`}
{
"description": "A stream says nothing about its own framing, so codescan does not invent one:\neach field renders as `{string, format: byte}` β the base64-encoded string\nSwagger 2.0 uses for arbitrary bytes.",
"type": "object",
"title": "Attachment carries opaque byte streams as model fields.",
"properties": {
"checksum": {
"description": "Checksum says what its bytes are, so the annotation wins over the default.",
"type": "string",
"format": "base64",
"x-go-name": "Checksum",
"x-go-type": "io.Reader" },
"content": {
"description": "Content is the attachment payload.",
"type": "string",
"format": "byte",
"x-go-name": "Content",
"x-go-type": "io.Reader" },
"thumbnail": {
"description": "Thumbnail is a closeable stream; the same answer applies.",
"type": "string",
"format": "byte",
"x-go-name": "Thumbnail",
"x-go-type": "io.ReadCloser" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/streams"}
content and thumbnail carry {string, byte}. checksum carries
swagger:strfmt base64, and the annotation wins β which is the point of the
next section.
Say what the bytes are
The default is deliberately uninformative, because a stream is
uninformative. When you know more, say so and codescan will step aside:
swagger:strfmt β name the format
(base64, binary, a custom one);
Recognition is by identity β the exact named type β never by shape. An
interface of your own that happens to have a Read method is your type and is
documented as you declared it.
Because both renderings erase which stream it was β every type in the table
produces the same schema β each one also carries an
x-go-type extension naming the Go type it
came from, so a consumer can tell an io.Reader field from a multipart.File
one. SkipExtensions suppresses it along with the rest of the x-go-* family.
Note
io.Writer is not recognized, nor are the write-only closers. A sink the
caller writes into is not something that travels on the wire, so codescan does
not assume what you meant by putting one in an API type β it documents the type
structurally, and you override it if you had something in mind.
Swagger 2.0 has no native nullable flag; the go-openapi toolchain uses the
x-nullable vendor extension. Options.SetXNullableForPointers decides whether
pointer-typed struct fields acquire it automatically. The model below has two
pointer fields:
// Profile has required and optional (pointer) fields.//
// swagger:modeltypeProfilestruct {
// Name is always present.Namestring`json:"name"`// Nickname is optional.Nickname*string`json:"nickname"`// Age is optional.Age*int32`json:"age"`}
omitempty changes the meaning. A pointer field tagged
json:"β¦,omitempty" is treated as optional (may be absent) rather than
nullable (may be null), so it does not receive x-nullable even with the
option on. Drop omitempty when you mean the value can be present-but-null.
Vendor extensions
By default codescan records where each spec object came from in Go via
x-go-name (and x-go-package on definitions) β useful for round-tripping and
code generation. Options.SkipExtensions removes them for a leaner spec.
// Widget is a small model.//
// codescan records each field's Go origin as vendor extensions unless// SkipExtensions is set.//
// swagger:modeltypeWidgetstruct {
// Label is the display label.Labelstring`json:"label"`// Size is the widget size in pixels.Sizeint32`json:"size"`}
{
"description": "codescan records each field's Go origin as vendor extensions unless\nSkipExtensions is set.",
"type": "object",
"title": "Widget is a small model.",
"properties": {
"label": {
"description": "Label is the display label.",
"type": "string",
"x-go-name": "Label" },
"size": {
"description": "Size is the widget size in pixels.",
"type": "integer",
"format": "int32",
"x-go-name": "Size" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/extensions"}
{
"description": "codescan records each field's Go origin as vendor extensions unless\nSkipExtensions is set.",
"type": "object",
"title": "Widget is a small model.",
"properties": {
"label": {
"description": "Label is the display label.",
"type": "string" },
"size": {
"description": "Size is the widget size in pixels.",
"type": "integer",
"format": "int32" }
}
}
SkipExtensions removes the scanner-derived x-go-* extensions. Extensions you
author yourself (via the Extensions: keyword) are not affected, and neither is
x-deprecated (it carries semantic intent β see
Other type decorators).
Stamping x-go-type
x-go-name and x-go-package record where a definition came from, but not the
originating type’s own name. Options.EmitXGoType adds an x-go-type extension
carrying the fully-qualified Go type (<package path>.<type name>) β useful for
round-tripping a generated spec back to its source types:
{
"description": "codescan records each field's Go origin as vendor extensions unless\nSkipExtensions is set.",
"type": "object",
"title": "Widget is a small model.",
"properties": {
"label": {
"description": "Label is the display label.",
"type": "string",
"x-go-name": "Label" },
"size": {
"description": "Size is the widget size in pixels.",
"type": "integer",
"format": "int32",
"x-go-name": "Size" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/extensions"}
{
"description": "codescan records each field's Go origin as vendor extensions unless\nSkipExtensions is set.",
"type": "object",
"title": "Widget is a small model.",
"properties": {
"label": {
"description": "Label is the display label.",
"type": "string",
"x-go-name": "Label" },
"size": {
"description": "Size is the widget size in pixels.",
"type": "integer",
"format": "int32",
"x-go-name": "Size" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/extensions",
"x-go-type": "github.com/go-openapi/codescan/docs/examples/shaping/extensions.Widget"}
The stamp lands on the definition, beside x-go-package. It is opt-in and
default-off, so existing specs are unchanged; it is presence-guarded, so it never
overwrites the deliberate x-go-type the special-type recognizers already set
(error, the unmodellable generic-type fallback). Like the other x-go-*
extensions it rides the SkipExtensions umbrella β set SkipExtensions and no
x-go-type is emitted either.
Enum descriptions
A swagger:enum type backed by Go const declarations folds the constβvalue
mapping into the field’s descriptionand duplicates it in the
x-go-enum-desc extension. When the prose already says everything you want, the
folded mapping is noise. Options.SkipEnumDescriptions keeps the authored prose
as the description; the mapping then rides x-go-enum-desc only:
This knob is independent of SkipExtensions: set both to drop the mapping
everywhere (no description folding, no x-go-enum-desc).
Authoring x-* on parameters and headers
The x-go-* extensions above are scanner-derived. To attach your own vendor
extension β say x-example for a tool like Dredd β use an Extensions: block in
the doc comment. It works on a model (the x-* lands on the definition), a
model field, a parameter, and a response header alike. (A bare
// x-example: 2 line would be read as the description; the Extensions: block
is the supported form.)
// ListWidgetsParams decorates a query parameter with an author-supplied vendor// extension through an Extensions: block β useful for tools (e.g. Dredd) that// read x-example. A bare `x-example:` line would be swallowed as the// description, so the Extensions: block is the supported form.//
// swagger:parameters listWidgetstypeListWidgetsParamsstruct {
// Page is the page number.//
// in: query//
// Extensions:// x-example: 2Pageint32`json:"page"`}
// WidgetList responds with a header that also carries a vendor extension β// parameters and response headers both honour Extensions:.//
// swagger:response widgetListtypeWidgetListstruct {
// X-Rate-Limit is the per-window request budget.//
// Extensions:// x-units: requests-per-minuteXRateLimitint32`json:"X-Rate-Limit"`}
// swagger:route GET /widgets widgets listWidgets//
// responses://
// 200: widgetList
Author-supplied extensions are not stripped by SkipExtensions β the fragment
above is produced withSkipExtensions: true, yet x-example and x-units
survive, because the flag only removes the scanner-derived x-go-* set.
Response bodies
When a handler’s actual Go return type doesn’t map cleanly to the payload you
want documented, these knobs let you pin the response body the spec describes β
inline on the route, or via a doc-only struct that stands in for a generic
envelope.
Declare a route’s responses inline with the body: sub-language β a primitive,
an array, or a model $ref β without writing a swagger:response struct.
Your handlers return one generic envelope with an interface{} payload, but you
want the spec to describe a concrete type per operation. Doc-only structs that
embed the envelope and shadow the payload field close the gap.
Subsections of Response bodies
Inline response bodies
The Routes & operations
tutorial declares each response as a swagger:response struct with a Body
field. That is the right tool when a response is reused across operations or has
headers. But when a response body is just “a string”, “an array of Pet”, or
“a Pet”, the wrapper struct is pure boilerplate.
The Responses: block of a swagger:route accepts the body: sub-language,
which names the body shape directly:
body:string (or number / integer / boolean) β a primitive body;
body:Pet β a $ref to the Pet definition;
body:[]Pet β an array of that $ref (repeat [] to nest deeper);
any trailing words after the body token become the response description;
omit them and codescan derives one β the referenced model’s godoc (default
above β the Pet doc comment), or the HTTP status reason for a numeric code
(400 above β “Bad Request”).
swagger:route
// swagger:route GET /pets pets listPets//
// Lists pets. Each response is declared inline with the body: sub-language β a// primitive, an array of a model, or a single model $ref β so no wrapper// response type is needed. Trailing words become the response description; omit// them and codescan derives one (the model's godoc, or the HTTP status reason).//
// Responses:// 200: body:[]Pet the list of pets// 400: body:string// default: body:Pet
{
"get": {
"description": "Lists pets. Each response is declared inline with the body: sub-language β a\nprimitive, an array of a model, or a single model $ref β so no wrapper\nresponse type is needed. Trailing words become the response description; omit\nthem and codescan derives one (the model's godoc, or the HTTP status reason).",
"tags": [
"pets" ],
"operationId": "listPets",
"responses": {
"200": {
"description": "the list of pets",
"schema": {
"type": "array",
"items": {
"$ref": "#/definitions/Pet" }
}
},
"400": {
"description": "Bad Request",
"schema": {
"type": "string" }
},
"default": {
"description": "Pet is the model the inline responses reference.",
"schema": {
"$ref": "#/definitions/Pet" }
}
}
}
}
No swagger:response struct is defined β the three responses are produced
entirely from the body: tokens, and the Pet model is pulled into
definitions because the body $refs reach it.
Info
A bare untagged token is read as a response name, never a type: 200: string
is a (dangling) $ref to a response called string, not a primitive body. Use
the explicit body:string form for a primitive. The full grammar β tags,
untagged-token rules, and the reserved array/object/file keywords β is in
sub-languages β Responses.
Documenting generic responses
A common Go pattern is a single response envelope β a JSend-style wrapper that
every handler returns, with an open any (interface{}) field for the payload:
The generic envelope
// APIResponse is the one generic envelope every handler returns. Because Data is// an open type (any, i.e. interface{}), the scanner can only render it as an// open schema β it has no way to know which concrete payload a given operation// puts there.//
// swagger:modeltypeAPIResponsestruct {
Statusstring`json:"status"`Dataany`json:"data"`Messagestring`json:"message,omitempty"`}
{
"description": "APIResponse is the one generic envelope every handler returns. Because Data is\nan open type (any, i.e. interface{}), the scanner can only render it as an\nopen schema β it has no way to know which concrete payload a given operation\nputs there.",
"type": "object",
"properties": {
"data": {
"x-go-name": "Data" },
"message": {
"type": "string",
"x-go-name": "Message" },
"status": {
"type": "string",
"x-go-name": "Status" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"}
codescan reads this faithfully: Data is any, so in the spec it becomes an
open schema β {"x-go-name":"Data"}, no type, no $ref. That is correct
(the field genuinely accepts anything), but it is not what you want in an API
contract, where each operation returns a specific payload.
codescan will not grow a per-route override syntax like swaggo’s
body:APIResponse{Data: StatusReport} β that asks the scanner to invent a type
the code never declares. Instead, declare the type: a doc-only struct that
mirrors the envelope but pins the payload.
Embed the envelope, shadow the payload
The DRY way is to embed the generic envelope β promoting its Status and
Message β and re-declare only the one opaque field with a concrete type:
Doc-only envelope
// StatusEnvelope documents the /status response: it embeds APIResponse and// shadows the open Data with the concrete StatusReport. Handlers keep returning// APIResponse β this type just gives the scanner a concrete shape.//
// swagger:modeltypeStatusEnvelopestruct {
APIResponse// Data carries the concrete status report.DataStatusReport`json:"data"`}
{
"description": "StatusEnvelope documents the /status response: it embeds APIResponse and\nshadows the open Data with the concrete StatusReport. Handlers keep returning\nAPIResponse β this type just gives the scanner a concrete shape.",
"type": "object",
"properties": {
"data": {
"x-go-name": "Data",
"$ref": "#/definitions/StatusReport" },
"message": {
"type": "string",
"x-go-name": "Message" },
"status": {
"type": "string",
"x-go-name": "Status" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"}
The locally-declared Data is shallower than the embedded one, so it wins β
both in codescan’s view and in encoding/json at runtime. The result is a clean
flat object whose data is a concrete $ref, with status and message
carried over from the embed:
Generic β data is open
{
"description": "APIResponse is the one generic envelope every handler returns. Because Data is\nan open type (any, i.e. interface{}), the scanner can only render it as an\nopen schema β it has no way to know which concrete payload a given operation\nputs there.",
"type": "object",
"properties": {
"data": {
"x-go-name": "Data" },
"message": {
"type": "string",
"x-go-name": "Message" },
"status": {
"type": "string",
"x-go-name": "Status" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"}
{
"description": "StatusEnvelope documents the /status response: it embeds APIResponse and\nshadows the open Data with the concrete StatusReport. Handlers keep returning\nAPIResponse β this type just gives the scanner a concrete shape.",
"type": "object",
"properties": {
"data": {
"x-go-name": "Data",
"$ref": "#/definitions/StatusReport" },
"message": {
"type": "string",
"x-go-name": "Message" },
"status": {
"type": "string",
"x-go-name": "Status" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"}
You restate one field, not the whole envelope. Point the route’s response at
the doc-only struct with the body: sub-language,
and specialise the same envelope per operation with a different payload each
time:
swagger:route
// swagger:route GET /status status getStatus//
// Returns the service status wrapped in the generic envelope. The response is// declared as the doc-only StatusEnvelope, so data is the concrete StatusReport// rather than an open schema.//
// Responses:// 200: body:StatusEnvelope the status envelope// swagger:route GET /users/{id} users getUser//
// Returns a user wrapped in the same envelope, specialised to UserSummary.//
// Responses:// 200: body:UserEnvelope the user envelope
{
"/status": {
"get": {
"description": "Returns the service status wrapped in the generic envelope. The response is\ndeclared as the doc-only StatusEnvelope, so data is the concrete StatusReport\nrather than an open schema.",
"tags": [
"status" ],
"operationId": "getStatus",
"responses": {
"200": {
"description": "the status envelope",
"schema": {
"$ref": "#/definitions/StatusEnvelope" }
}
}
}
},
"/users/{id}": {
"get": {
"tags": [
"users" ],
"summary": "Returns a user wrapped in the same envelope, specialised to UserSummary.",
"operationId": "getUser",
"responses": {
"200": {
"description": "the user envelope",
"schema": {
"$ref": "#/definitions/UserEnvelope" }
}
}
}
}
}
The handler never changes. Your code keeps returning the generic
APIResponse; the doc-only structs exist only to give the scanner a concrete
shape. They are valid Go, though β because the shadowing Data wins,
StatusEnvelope{} marshals to exactly the same JSON the generic envelope would,
so you may return one directly if you prefer a typed handler.
When embedding doesn’t fit
If your envelope has fields you would rather not promote β or you want the
documented type to live behind a reusable swagger:response β restate the
fields explicitly instead of embedding:
This produces the same concrete data, at the cost of repeating every field. The
embed-and-shadow form above is preferred whenever the envelope’s other fields map
through unchanged.
Composing a body from several models
A related need is wrapping a response with an extra payload β say a domain
model plus an auth token β rather than specialising one open field. Embed the
parts with swagger:allOf
and the body renders as the union of their $refs, with no open field at all:
swagger:allOf body
// AuthToken is an extra payload some responses wrap alongside the domain model.//
// swagger:modeltypeAuthTokenstruct {
Tokenstring`json:"token"`}
// LoginResult composes UserSummary with AuthToken via swagger:allOf, so the// response body is the union of both models β a way to wrap a response with an// added payload without an open Data field. The body renders as// allOf:[{$ref:UserSummary},{$ref:AuthToken}].//
// swagger:modeltypeLoginResultstruct {
// swagger:allOfUserSummary// swagger:allOfAuthToken}
{
"description": "LoginResult composes UserSummary with AuthToken via swagger:allOf, so the\nresponse body is the union of both models β a way to wrap a response with an\nadded payload without an open Data field. The body renders as\nallOf:[{$ref:UserSummary},{$ref:AuthToken}].",
"allOf": [
{
"$ref": "#/definitions/UserSummary" },
{
"$ref": "#/definitions/AuthToken" }
],
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/genericenvelopes"}
Point a response body at LoginResult, or embed the same swagger:allOf fields
directly in an in:body field to compose the allOf inline on the response
schema.
Annotation index
The complete swagger:* vocabulary, one row each.
By example jumps to the tutorial that shows the annotation as runnable Go next to the spec it produces;
Reference jumps to the exhaustive rule in the Maintainers compendium.
Validations, examples and defaults inside a block are driven by keywords
(minimum:, pattern:, enum:, example:, default:, β¦), not annotations.
See the Validations and
Examples & defaults
tutorials, and the Keyword reference.
Maintainers
This section is the reference compendium: the precise, exhaustive
description of the language codescan parses, and of the tools built around it.
It is written for people who want the full contract β annotation authors looking
up an exact rule, and contributors porting, extending, or debugging the parser.
Looking up an option rather than an annotation? That moved: the
Options reference lives under
Usage, beside the flag and configuration-key
spellings of the same knobs.
If you are learning codescan by example, start with the
Tutorials instead β they show the same concepts
as runnable Go with the spec they produce, side by side. The
Annotation index cross-references every
annotation to both its tutorial and its entry here.
The keyword: value forms recognised inside annotation blocks β grouped by class, with the annotation contexts that accept each one and its value shape.
What a scan costs, and what six months of work did to it.
The two independent gains β the annotation parser and the package loader β measured on the same generated server, emitting the same document.
Annotations β the swagger:* vocabulary:
what each annotation does, where it attaches, its argument shape, and the
keywords it admits. The author-facing normative reference.
Keywords β the per-keyword reference card:
every keyword: value form, its value shape, and the contexts where it is
legal.
Sub-languages β the smaller languages
embedded inside annotation bodies (Parameters: / Responses: grammars,
YAML surfaces, prose classification).
Grammar β the formal ISO-14977 EBNF the
parser implements, from comment preprocessing through the typed walker.
The commands β how the three CLI tools are put
together: why each lives where it does, where their shared flag surface is
declared, and what keeps it whole.
Performance β what a scan costs: what the
grammar and the loader each changed since the code left go-swagger, and how the
loader options compare warm and cold.
Subsections of Maintainers
Roadmap
What’s next with this project?
timeline
title Planned releases
section Q1 2026
β v0.32.x (March 2026) : Repo carved out of go-swagger
: relint
: library setup (not env. sensitive)
: go1.25+
section Q2 2026
β v0.33.x (April 2026) : Reduced exposed interface
: type array for parameters
: new package layout (internal, layered)
β v0.34.x (May 2026) : Grammar-based parser
: Replace regexp-based parser by lexer+grammar
: Fixed many parsing quirks
β v0.35.0 (June 2026) : Large bug-bashing
: Documentation site
: Fixes ~200+ go-swagger issues
: All validations
: Parser diagnostics
section Q3 2026
β v0.35.x (July 2026) : Minor features
: more tunable knobs, new annotations
: Name conflict handling & circular $ref, missing validations, ...
: go doc filter, private comments, inner markdown
β v0.36.0 (July 2026) : TUI
: interactive spec building with TUI tool
: polymorphic subtypes discovery
β v0.36.x (August 2026) : faster code scanner
: Optimized incremental type scanner
: toolchain-independent loader
: playground UI (wasi build)
: dedicated CLI, independent from go-swagger
: more go-swagger backlog fixes & tunable knobs
β¬ v0.37.0 (September 2026) : decouple from `Spec`
: go1.26+
: Internal document model
section Q4 2026
π v0.38.x (Oct 2026) : LSP & IDE integration (tentative)
π v0.39.x (Oct-Nov 2026) : OAI v3 support (tentative)
Annotations
Annotations are the swagger:<name> markers the scanner recognises in
Go doc comments. Each annotation classifies the surrounding
declaration β telling the scanner “this is a model definition”, “this
is a route handler”, “this is meta-information about the API” β and
opens the door for keywords inside the same comment
block.
There are twenty annotations. They divide cleanly by what they
attach to:
Spec-level: swagger:meta.
Model declarations: swagger:model, swagger:strfmt,
swagger:enum, swagger:allOf, swagger:alias,
swagger:additionalProperties, swagger:patternProperties.
Deprecated no-ops, parsed and reported but without effect:
swagger:alias, swagger:default.
This section is the author-first reference. Each annotation has its
own page covering what it produces, where it goes, its EBNF-like
syntax, the keywords legal inside its block, and at least one worked
example. Browse them below (sorted alphabetically), or start from the
Annotation index for the one-row-each
overview.
Replaces a field’s or named type’s inferred Swagger type with an inlined type.
For the per-keyword reference, see keywords.md.
For the embedded sub-languages (Parameters: and Responses: body
grammars, YAML extensions, etc.), see
sub-languages.md. For the formal grammar,
see grammar.md.
How annotations attach
An annotation is recognised when it appears at the start of a comment
line in a doc comment. Leading whitespace, the // marker, and any
/* */ block-comment continuation noise are stripped β the lexer
applies the same content-prefix-trim that every other godoc-aware
tool does.
Annotations attach to whichever Go declaration owns the comment
group:
Package doc (// Package foo β¦ followed by package foo) β
carries swagger:meta.
Type declaration (type T struct { β¦ }, type T int,
type T = Other) β carries swagger:model, swagger:strfmt,
swagger:enum, swagger:allOf, swagger:alias, swagger:ignore,
swagger:type. Inside a grouped declaration (type ( A β¦; B β¦ ))
the comment on each individual spec is honoured independently β the
annotation attaches to its own TypeSpec, not to the enclosing group β
so two types in one group can carry distinct docs and annotations.
Function or variable declaration (func ServeAPI() { β¦ },
var DoIt = func() { β¦ }) β carries swagger:route,
swagger:operation. These two are recognised whether the annotation
sits in the function’s doc comment or inside the function body.
A swagger:model or swagger:parameters declared on a type local
to a function body is likewise discovered.
Struct field doc β carries swagger:name, swagger:type,
swagger:ignore, plus any of the keyword reference
entries legal in schema / param / header context.
One comment group may carry MORE than one annotation when the
combinations are semantically compatible β e.g. swagger:model +
swagger:type together overrides the auto-detected Go type while
still publishing the model. The grammar parses both and the builder
honours both.
The first annotation in source order wins as the “primary”
classifier β for example, a comment carrying swagger:model followed
by swagger:ignore produces a model (the ignore is silently
overridden because only the source-order-first annotation drives the
short-circuit). Subsequent annotations are still parsed and visible
via Block.AnnotationKind()-iteration, but the primary classifier
determines which builder owns the decl.
Warning
Recognition is purely positional: any comment line that begins with a
swagger:<name> token is treated as that annotation β even when you meant it as
prose. A description line like swagger:type controls the emitted type on a
type’s doc comment is parsed as a swagger:type annotation. Keep annotation
names mid-sentence in descriptions (The swagger:type directive β¦) or wrap them
in backticks so the line does not start with the token.
Annotation argument shapes
After the swagger:<name> head, an annotation may carry positional
arguments. The shapes:
No args: swagger:meta, swagger:ignore, swagger:enum,
swagger:allOf, swagger:file β bare annotation, the surrounding
decl supplies the entity name. swagger:default also accepts a bare
form, but its argument is optional and unread β it is deprecated.
One IDENT arg: swagger:model Pet, swagger:response errorResponse, swagger:strfmt uuid, swagger:name fullName,
swagger:type integer, swagger:alias TimestampAlias β the
argument overrides or names the entity.
One IDENT arg, optional: swagger:model (bare β derives the
name from the Go decl) vs swagger:model Pet (overrides).
List of IDENT args: swagger:parameters listItems createItem
β declares the parameters group as legal for multiple operations.
Header line: swagger:route GET /pets pets users listPets and
swagger:operation GET /pets users listPets β a structured header
carrying method, path, tags, and operation ID. See the
per-annotation pages for the exact rules.
Annotation Γ keyword compatibility matrix
A quick orientation for which annotations can carry which keyword
families. See keywords.md for the per-keyword
contracts, and each annotation’s own page for the detail.
Annotation
Numeric/length validations
Schema decorators
in:
Meta keywords
Parameters: body
Responses: body
YAML body
swagger:meta
β
β
β
β
β
β
β (security defs, extensions)
swagger:model
β (on fields)
β
β
β
β
β
β
swagger:strfmt
β
β
β
β
β
β
β
swagger:enum
β
(enum keyword via const)
β
β
β
β
β
swagger:allOf
β (on member fields)
β
β
β
β
β
β
swagger:alias
β
β
β
β
β
β
β
swagger:route
β
(deprecated only)
β
(schemes/consumes/produces/security)
β
β
(extensions)
swagger:operation
β
β
β
β
β
β
β (full op as YAML)
swagger:parameters
β (on fields)
β (on fields)
β
β
β
β
β
swagger:response
β (on header fields)
β (on body field)
β (body/header)
β
β
β
β
swagger:ignore
β
β
β
β
β
β
β
swagger:name
β
β
β
β
β
β
β
swagger:title
β
β (override)
β
β
β
β
β
swagger:description
β
β (override)
β
β (body/header)
β
β
β
swagger:type
β
β
β
β
β
β
β
swagger:additionalProperties
β
β (object schema)
β
β
β
β
β
swagger:patternProperties
β
β (object schema)
β
β
β
β
β
swagger:file
β
β
β
β
β
β
β
swagger:default(deprecated)
β
β
β
β
β
β
β
A blank cell means the keyword family is not legal in that context;
attempting to use it emits CodeContextInvalid and the keyword is
dropped.
Sets a schema’s additionalProperties β the policy for keys beyond the
named properties.
On a struct it complements the named properties; on a map type it
overrides the element-derived value schema; on a type that resolved to
a bare $ref it defines a clean object. See the
Maps & free-form objects
tutorial.
Where it goes
On a type declaration (alongside swagger:model). A field-level
equivalent exists as the
additionalProperties: keyword.
true β allow arbitrary extra keys (additionalProperties: true);
false β forbid extra keys, closing the object
(additionalProperties: false);
a value type β a primitive / Go-builtin / []T, or a known type
name (which resolves to a $ref, and is registered for discovery).
This reuses the /codescan/maintainers/annotations/swagger-type/ value grammar, except a
type name becomes a $ref rather than an inline expansion.
Supported keywords
None of its own. It composes with maxProperties / minProperties /
patternProperties.
Example
Annotated Go
// Settings is an open object: it keeps its named property and complements it// with typed (integer) extra values β the swagger:additionalProperties marker// sets the policy for keys beyond the named ones.//
// swagger:model// swagger:additionalProperties integertypeSettingsstruct {
Namestring`json:"name"`}
{
"description": "Settings is an open object: it keeps its named property and complements it\nwith typed (integer) extra values β the swagger:additionalProperties marker\nsets the policy for keys beyond the named ones.",
"type": "object",
"properties": {
"name": {
"type": "string",
"x-go-name": "Name" }
},
"additionalProperties": {
"type": "integer" },
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
Precedence β lowest priority.additionalProperties only rides on an
object. If a prior rule fixed a non-object type (a swagger:type
scalar, swagger:strfmt, a special type), the marker is dropped with a
CodeShapeMismatch diagnostic. It has no OAS-2 SimpleSchema form, so it
never applies on a non-body parameter or response header.
Full example.testdata/enhancements/additional-properties/api.go.
swagger:alias
Warning
Deprecated.swagger:alias no longer affects the emitted spec. It is an
empty sink that only raises a validate.deprecated diagnostic.
Usage
// swagger:alias [ IDENT_NAME ]
What it does
Nothing, today. Earlier documentation claimed it published a $ref to
the alias target; that was never accurate. Its only real effect was to
force a named primitive type to inline its scalar (e.g.
{type: string}) instead of producing the $ref a named type
otherwise gets β and that force-inline behaviour has been removed.
The optional IDENT_NAME is ignored β the annotation has no effect.
Migration
To inline a type at a use site, use swagger:type inline on the
field (see swagger:type).
To publish a type as a first-class definition that fields $ref,
use swagger:model.
To control alias rendering globally, use the RefAliases /
TransparentAliases options. A plain (unannotated) Go alias
type T = Other dissolves to its target by default. See
Alias rendering.
Supported keywords
None β the annotation is inert.
swagger:allOf
Usage
// swagger:allOf
What it does
Marks a struct as participating in an allOf composition.
The struct’s fields plus any embedded swagger:model-tagged base produce
an allOf: [$ref base, {inline fields}] schema. The companion convention
is to embed the base type as an anonymous field with this annotation on the
embedding’s doc comment (or on the embedded type itself).
Where it goes
On a struct field that embeds another type, or on a struct type that has
at least one embedded base.
swagger:allOf takes no arguments, and no other classifier annotation belongs in
an embedded field’s doc comment. swagger:strfmt and swagger:type written
there are ignored, and codescan reports them under
scan.ineffective-annotation:
The reason is that an embed contributes the shape of the type it embeds, and
that type’s own declaration fixes the shape β never the site that embeds
it. So the annotation belongs one level down:
// Token is rendered as a formatted string wherever it appears.//
// swagger:strfmt uuidtypeToken [16]bytetypeRightstruct {
// swagger:allOfToken}
This is not specific to allOf: the same annotations are ignored on a plain
(uncomposed) embed too, and reported the same way. They are honoured on an
ordinary β non-embedded β field, which is what makes the mistake an easy one.
Example
A struct embedding a swagger:model base with swagger:allOf on the embed
produces an allOf of the base $ref and an inline-object member carrying
the embedding struct’s own fields:
Annotated Go
// Animal is one abstract base.//
// swagger:modeltypeAnimalstruct {
// Kind discriminates the animal.Kindstring`json:"kind"`}
// Tagged is a second reusable base.//
// swagger:modeltypeTaggedstruct {
// Tags label the resource.Tags []string`json:"tags"`}
// Dog composes two base models plus its own fields: each embedded base becomes// a $ref arm of the allOf, and the struct's own (non-embedded) fields β which// are new and cannot be a $ref β form the final inline arm.//
// swagger:modeltypeDogstruct {
// swagger:allOfAnimal// swagger:allOfTagged// Breed is the dog's breed.Breedstring`json:"breed"`}
{
"description": "Dog composes two base models plus its own fields: each embedded base becomes\na $ref arm of the allOf, and the struct's own (non-embedded) fields β which\nare new and cannot be a $ref β form the final inline arm.",
"allOf": [
{
"$ref": "#/definitions/Animal" },
{
"$ref": "#/definitions/Tagged" },
{
"type": "object",
"properties": {
"breed": {
"description": "Breed is the dog's breed.",
"type": "string",
"x-go-name": "Breed" }
}
}
],
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
The same composition applies when the embedding struct is a
swagger:response body: the embedded base emits an allOf: [{$ref}, β¦]
arm only when it is a swagger:model (a definition exists to point at);
an embedded swagger:response has its fields inlined instead.
Full example.testdata/enhancements/allof-edges/types.go.
swagger:default
Warning
Deprecated.swagger:default never emitted a default into the spec, in any
placement or form. It is now an empty sink that only raises a
validate.deprecated diagnostic. Use the
default: keyword,
or a default response code in a route’s Responses: body.
Usage
// swagger:default [ VALUE ]
What it does
Nothing. It is parsed, reported as deprecated, and ignored.
Previously it also suppressed the schema of a named basic type it was placed
on: the classifier claimed the target without writing it, so the declared type
published a typeless definition and every field referencing it emitted a typeless
property, silently. That is fixed β an annotated type now emits exactly what it
would emit unannotated.
Why it was retired
Every place OpenAPI 2.0 admits a default is already served, so the annotation
had no meaning left to implement:
Where a default can appear
How to write it
Schema object β a model field, or a type declaration
Responses object β an operation’s default response
default: as the response code in a Responses: body
The keyword’s context set is exactly the list of OAS 2.0 objects that carry a
default; the response-code head closes the remainder.
Where it goes
Anywhere it used to β the annotation is still recognised so existing source keeps
scanning. It has no effect wherever it appears.
Grammar (EBNF)
DefaultClassifierBlock =ANN_DEFAULT , [ VALUE ] , [ Title ] , [ Description ] ;
The value argument is optional and unread. It used to be mandatory, which made
the bare form this page once documented a hard parse error.
Supported keywords
None.
Example
Replace it with the keyword:
// Port is the listen port.//
// swagger:model Port// default: 8080typePortint
For an operation’s default response, use the response code:
// swagger:route GET /things things listThings//
// Responses:// 200: thingList// default: genericError
swagger:description
Usage
// swagger:description <text> (single line, or a blank-terminated body)
// swagger:description | (opens a verbatim literal markdown block)
What it does
Replaces the godoc-derived description on a schema with explicit text.
By default a description comes from a declaration’s doc comment;
swagger:description overrides it when the godoc prose isn’t what you want to
publish. It is a schema-family override β a sibling of
swagger:title.
A trailing | opens a verbatim literal markdown block: the body is captured
exactly β blank lines, indentation, and table pipes preserved β until the next
line-leading annotation or end of comment. See
Markdown descriptions.
Where it goes
On a type (model) doc comment, a struct-field doc comment, a swagger:response
struct, or a response header field.
RAW_VALUE is the rest of the head line; under Option B a blank-terminated body
extends it, and a trailing | switches the body to verbatim literal capture.
The annotation dispatches through the
schema parser (not the classifier
parser), so validation keywords co-located on the same comment group still
surface.
Supported keywords
None of its own β the text (plus any folded body) is the entire argument. A bare
swagger:description with no text suppresses the godoc-derived description
and emits a CodeEmptyOverride diagnostic.
Example
A plain override on a model and its fields:
Annotated Go
// Widget is the Go-facing widget doc, written for Go readers.//
// It explains internal Go usage that should not leak into the API spec.//
// swagger:model// swagger:title A Public Widget// swagger:description A widget exposed via the public API.typeWidgetstruct {
// ID explains the Go field for Go readers.//
// swagger:description The unique widget identifier.IDstring`json:"id"`// Label is the Go-facing field doc. Fields carry no title by default;// the override is the only way a property gets one.//
// swagger:title Display Label// swagger:description Human-readable label shown to API consumers.Labelstring`json:"label"`// Plain keeps its godoc description because it carries no override.Plainstring`json:"plain"`// Capacity combines a description override with an inline validation// keyword on the same field: the override applies AND maximum is kept,// because the override annotations dispatch through the schema family.//
// swagger:description The maximum capacity, in liters.// maximum: 1000Capacityint64`json:"capacity"`// Suppressed has a godoc that a bare swagger:description suppresses: the// empty value is applied (description omitted) and scan.empty-override is// raised, in case the bare marker was left behind by mistake.//
// swagger:descriptionSuppressedstring`json:"suppressed"`// Notes carries a multi-line description override: the lines following the// annotation fold into the description until the blank line, joined with// newlines.//
// swagger:description Free-form notes about the widget.// They may span several lines, all folded into one description.//
// The blank line above terminates the override body; this paragraph is// ordinary godoc and is discarded (the override won).Notesstring`json:"notes"`// Gadget is a $ref field carrying title + description overrides. They are// symmetric $ref siblings: kept under EmitRefSiblings, dropped to a bare// $ref under the default flags β the same rule a prose description follows.//
// swagger:title Gadget Ref// swagger:description The attached gadget, described for API consumers.GadgetGadget`json:"gadget"`}
// Gadget is a plain referenced model.//
// swagger:modeltypeGadgetstruct {
Serialstring`json:"serial"`}
The | literal block captures a verbatim markdown body (table and list preserved):
Annotated Go
// Markdown opts into a verbatim body with the literal block marker.//
// swagger:description |// The body is captured **verbatim** β pipes, blank lines and all://
// | name | purpose |// |------|---------|// | foo | bars |//
// - point one// - point two//
// swagger:model MarkdowntypeMarkdownstruct {
// Name of the widget.//
// swagger:description |// The name must be://
// 1. unique// 2. lowercaseNamestring`json:"name"`}
{
"description": "The body is captured **verbatim** β pipes, blank lines and all:\n\n| name | purpose |\n|------|---------|\n| foo | bars |\n\n- point one\n- point two",
"type": "object",
"title": "Markdown opts into a verbatim body with the literal block marker.",
"properties": {
"name": {
"description": "The name must be:\n\n 1. unique\n 2. lowercase",
"type": "string",
"x-go-name": "Name" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/shaping/markdowndesc"}
Not to be confused with the enum: keyword,
which produces the same spec keyword from the opposite direction: it takes the
members you write literally, typed from the schema it sits on, whereas this
annotation collects them from a Go const block, typed from the declared Go
type. Side-by-side in the enumerations tutorial.
What it does
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 β this raises a
parse.invalid-enum-option warning suggesting a named type), and a rune
or byte enum emits integers, which is what those types are on the wire.
An alias to a named enum type is fine, and is not warned about.
See Enumerations.
Without swagger:model (the default): the values are applied
inline on each model field that references the type β the property
gets an enum array plus an x-go-enum-desc extension carrying the
per-value godoc descriptions in <value> <doc-text> shape. The enum
type itself is not a standalone definition.
With swagger:model: the enum becomes a first-class definition
carrying the enum array (+ x-go-enum-desc), and referencing fields
point at it via $ref β the general swagger:model β definition + $ref
rule applied to enums.
If swagger:enum names a type for which no matching const values are
found, the enum semantics are dropped and the type falls through to
ordinary type resolution (typically a plain $ref, no enum array).
Where it goes
On a named type declaration. The type’s const values are discovered via
Go’s type-system traversal; they do not need to live in the same file.
The values surface only when a model reaches the enum type through a field.
The optional IDENT_NAME names the type whose const values to collect.
On a type declaration the name is redundant, so the bare swagger:enum
form is accepted and infers the name from the declared type:
swagger:enum Priority and a bare swagger:enum on type Priority β¦
are equivalent.
Supported keywords
Schema-context keywords. The
enum: keyword can ALSO be used inline on the type doc to force a value
set; when present, it overrides the const-derived values and the
x-go-enum-desc is recomputed (or dropped) accordingly.
Example
A named type marked swagger:enum with const values, referenced by a
model field, lands the values on that property (not on a standalone
definition) together with the x-go-enum-desc extension:
Annotated Go
// Priority is the urgency level on a task.//
// swagger:enum PrioritytypePrioritystringconst (
// PriorityLow is for tasks that can wait.PriorityLowPriority = "low"// PriorityMedium is the default.PriorityMediumPriority = "medium"// PriorityHigh is for tasks that must run soon.PriorityHighPriority = "high")
// Task is a unit of work carrying an enum-typed field. Referencing Priority// from a model is what makes the enum reachable, and so emitted.//
// swagger:modeltypeTaskstruct {
// Priority is the task's urgency.PriorityPriority`json:"priority"`}
{
"description": "Task is a unit of work carrying an enum-typed field. Referencing Priority\nfrom a model is what makes the enum reachable, and so emitted.",
"type": "object",
"properties": {
"priority": {
"description": "Priority is the task's urgency.\nlow PriorityLow is for tasks that can wait.\nmedium PriorityMedium is the default.\nhigh PriorityHigh is for tasks that must run soon.",
"type": "string",
"enum": [
"low",
"medium",
"high" ],
"x-go-enum-desc": "low PriorityLow is for tasks that can wait.\nmedium PriorityMedium is the default.\nhigh PriorityHigh is for tasks that must run soon.",
"x-go-name": "Priority" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
By default the constβvalue mapping is folded into the property’s
descriptionand duplicated in x-go-enum-desc. Set the scanner
option SkipEnumDescriptions: true to keep the authored prose as the
description; the mapping then rides x-go-enum-desc only. See
Vendor extensions.
Full example.testdata/enhancements/enum-overrides/types.go.
swagger:file
Note
Prefer swagger:type file. The two are exact
synonyms β same output, same location gate. swagger:file is expected to be
deprecated as an extraneous annotation; it is not deprecated yet and still works.
Usage
// swagger:file
What it does
Marks a parameter or response body as a binary file ({type: file}).
The scanner emits the file-type marker without further introspection of the
Go type.
Where it goes
On a struct field doc inside a swagger:parameters (multipart file
upload) or swagger:response (file download) struct.
Takes no argument β an optional title/description may follow on the
doc comment.
Supported keywords
Standard parameter / response keywords; the file marker stacks with
in: and other parameter shape keywords. See the
keywords reference.
Example
Annotated Go
// swagger:route POST /pets/{id}/photo pets uploadPetPhoto//
// responses://
// 200: petsResponse// UploadParams is the multipart upload for the uploadPetPhoto operation.//
// swagger:parameters uploadPetPhototypeUploadParamsstruct {
// Photo is the image to upload.//
// in: formData// swagger:filePhotoio.ReadCloser`json:"photo"`}
Excludes the surrounding declaration from the generated spec.
The scanner sees the decl and the doc, classifies it, then drops it.
When swagger:ignore appears after another classifier on the same
comment block (e.g. swagger:model first, then swagger:ignore), the
first annotation wins and the ignore is silently overridden. Place
swagger:ignore first if you genuinely want the decl excluded.
Where it goes
On a type declaration to exclude the whole type, or on a struct field
doc to exclude that one field.
Takes no argument β an optional title/description may follow on the
doc comment.
Supported keywords
None β the annotation is a stateless classifier marker.
Example
swagger:ignore produces no schema, so there is no live spec pane here: the
type below is scanned, classified, then dropped β it never reaches
definitions. On a type it excludes the whole declaration; on a struct
field it excludes just that one property (e.g. a PasswordHash kept out of
the wire shape).
// Secret never reaches the spec.//
// swagger:ignoretypeSecretstruct {
// Token is internal.Tokenstring`json:"token"`}
Full example.testdata/enhancements/top-level-kinds/types.go.
swagger:meta
Usage
// swagger:meta
What it does
Declares the package as the OpenAPI spec container.
The scanner reads the package doc comment for the top-level spec fields:
title (via stripPackagePrefix of the
doc’s first line), description, license, contact, host, basePath, version,
schemes, consumes, produces, securityDefinitions, extensions, and the rest
of the meta keyword surface.
Where it goes
On the package doc comment. No arguments β a bare annotation.
The body is a MetaBody of single-line MetaKeywords
(version, host, basePath, license, contact, schemes) and
MetaRawBlocks (consumes, produces, security,
securityDefinitions, tos). See grammar Β§meta-family.
Supported keywords
All meta single-line keywords
(schemes, version, host, basePath, license, contact) plus the
meta-scope body keywords
(consumes, produces, security, securityDefinitions, extensions,
infoExtensions, tos, externalDocs, tags). A Tags: block declares the
spec’s top-level tags (name, description, nested externalDocs, x-*
extensions per tag).
Example
Annotated Go
// Package meta Pet Store.//
// A small API that demonstrates the document-level swagger:meta block: the// package doc comment carries the spec's top-level metadata.//
// Schemes: https// Host: api.example.com// BasePath: /v1// Version: 1.2.0// License: Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0.html// Contact: API Team <api@example.com> https://example.com/support//
// Consumes:// - application/json//
// Produces:// - application/json//
// ExternalDocs:// description: Full API guide// url: https://example.com/docs//
// Tags:// - name: pets// description: Everything about your Pets// externalDocs:// description: Find out more// url: https://example.com/docs/pets// - name: store// description: Access to Petstore orders// x-display-name: Store//
// SecurityDefinitions:// basic_auth:// type: basic// api_key:// type: apiKey// in: header// name: X-API-Key//
// Security:// basic_auth://
// InfoExtensions:// x-logo:// url: https://example.com/logo.png// altText: Example//
// swagger:metapackagemeta
// swagger:model [<name>] (where <name> overrides the definition name; defaults to the Go type name)
What it does
Declares a Go type as a published model.
The scanner walks the type, emits a schema into the spec’s definitions map,
and resolves cross-references between models.
The title/description split follows a heuristic: a single-line comment
ending in a period becomes the title. One without a trailing period
becomes the description; a multi-line comment uses the first line as
title and the rest as description.
The descriptive prose must come before the swagger:model line β an
annotation-first block still publishes the model but drops its title and
description.
Where it goes
On a type declaration (type T struct { β¦ }, type T int,
type T = Other, β¦).
Grammar (EBNF)
ModelAnnotation =ANN_MODEL , [ IDENT_NAME ] ;
The optional IDENT_NAME is the name the model takes in definitions
(default: the Go type’s name). It must be a plain identifier (a JSON
label), not a Go-qualified name β a dotted name such as utils.Error
is rejected with a warning and dropped. Cross-package types resolve
automatically, so reference a model by its bare name.
The annotation opens a SchemaBlock
body β its fields and their doc comments carry the schema validations.
Supported keywords
Every schema decorator and
validation keyword is accepted
on a field doc comment. A keyword that is not compatible with the field’s
inferred schema type (e.g. minLength on an integer) is ignored and raises a
diagnostic.
Example
The doc comment above the type drives the model’s name, title and description:
// Pet is the petstore's primary entity. <- title (first line, ends with a period)//
// A pet can be any little animal you care about. <- description// In this example the model name is inferred from the type name, here "Pet".//
// swagger:modeltypePetstruct {
// ID is the unique identifier.IDint64`json:"id"`// Name is the pet's display name.Namestring`json:"name"`// Tags categorise the pet.Tags []string`json:"tags,omitempty"`}
Pass an argument to override the name; the type is then published as
#/definitions/PetWithExtras:
A single field group declaring several names emits one property per name.
A json: tag on the group cannot rename the individual fields β each keeps its
own name β though tag options still apply:
Annotated Go
// Color is an RGBA colour. A single field group declaring several names emits// one property per name β R, G, B and A each become their own integer property.// A json tag on the group cannot rename the individual fields (each keeps its// own name), though tag options such as omitempty still apply.//
// swagger:modeltypeColorstruct {
R, G, B, Auint8`json:",omitempty"`}
{
"description": "Color is an RGBA colour. A single field group declaring several names emits\none property per name β R, G, B and A each become their own integer property.\nA json tag on the group cannot rename the individual fields (each keeps its\nown name), though tag options such as omitempty still apply.",
"type": "object",
"properties": {
"A": {
"type": "integer",
"format": "uint8" },
"B": {
"type": "integer",
"format": "uint8" },
"G": {
"type": "integer",
"format": "uint8" },
"R": {
"type": "integer",
"format": "uint8" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
Full example.testdata/enhancements/named-struct-tags-ref/types.go.
swagger:name
Usage
// swagger:name IDENT_NAME
What it does
Overrides the JSON property name that a struct field or interface method
renders as.
By default the scanner derives names from json:"β¦" struct tags (or the
Go identifier for fields / methods with no tag); swagger:name overrides
that derivation when the tag-based shape isn’t appropriate β typically on
interface methods, which cannot carry struct tags.
Where it goes
On a struct field doc OR an interface method doc.
Grammar (EBNF)
NameAnnotation =ANN_NAME , IDENT_NAME ;
The required IDENT_NAME is the JSON property name to use.
Supported keywords
None β the override name is the entire surface.
Example
On an interface method, swagger:name overrides the property name the
method would otherwise publish under (PascalCase Go method name) with the
chosen JSON name:
Annotated Go
// Car is exposed as a schema via its method set. Interface methods cannot carry// a json tag, so by default each property takes the camelCased method name;// swagger:name overrides that where the default is not what you want.//
// swagger:modeltypeCarinterface {
// Maker is the manufacturer. With no override the property is the// camelCased method name, "maker".Maker() string// StructType is the polymorphic class. Without the override the property// would be "structType"; swagger:name publishes it as "jsonClass".//
// swagger:name jsonClassStructType() string}
// Account shows the universal name: keyword renaming model struct fields. The// same keyword used on parameters and response headers also sets a property key// here, winning over a json tag, the legacy swagger:name annotation, and the Go// field name.//
// swagger:modeltypeAccountstruct {
// Bal has no json tag; the keyword sets the property key directly.//
// name: balanceBalfloat64// Currency carries both naming forms; the keyword wins over the// legacy annotation and the json tag.//
// name: currencyCode// swagger:name legacyCurrencyCurrencystring`json:"currency"`}
{
"description": "Car is exposed as a schema via its method set. Interface methods cannot carry\na json tag, so by default each property takes the camelCased method name;",
"type": "object",
"properties": {
"jsonClass": {
"description": "StructType is the polymorphic class. Without the override the property\nwould be \"structType\"; swagger:name publishes it as \"jsonClass\".",
"type": "string",
"x-go-name": "StructType" },
"maker": {
"description": "Maker is the manufacturer. With no override the property is the\ncamelCased method name, \"maker\".",
"type": "string",
"x-go-name": "Maker" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
Full example.testdata/enhancements/interface-methods/types.go.
Deprecated / legacy forms
swagger:name is the legacy annotation form. The canonical,
universal field-naming mechanism is the
name: keyword, which
works at every field site β model properties, interface methods,
parameters, and response headers β with the precedence name: >
swagger:name > json: tag > Go field name. swagger:name remains
honoured (and idiomatic on interface methods, shown above), but reach
for name: in new code; it is the only form that works on parameters
and headers.
swagger:omit
Usage
// swagger:omit <field>[,<field>β¦]
What it does
Stops the named fields being promoted out of an embedded type, so they never
reach the enclosing schema.
Embedding a shared type is how Go reuses a struct, but the reused type often
carries more than one particular endpoint should: server-assigned fields on a
create request, or a field the enclosing struct re-declares for itself.
swagger:omit is how the author resolves that β codescan does not guess
which fields were meant, it documents the type as written unless told otherwise.
Names are Go field names, never JSON aliases: the annotation acts before
names are computed, so it is indifferent to json tags and to
NameFromTags.
It is a pre-filter, not an edit of the finished schema, so it reads the same
whether the embed is inlined or composed into an allOf member
(see Composing embeds with allOf):
the field is simply never written.
Where it goes
Two placements:
on the embed β targets are plain field names of that embedded type. This
is the ergonomic form and needs no qualification;
on the type declaration β for an embed you cannot annotate (a type you do
not own, or one nested deeper). A bare name resolves against the promoted set;
a dotted path names the embed chain, Base.ID or Outer.Inner.Deep.
Embeds only. Every path segment but the last must name an embedded field:
swagger:omit removes promoted content, which is the only thing the enclosing
schema owns. To exclude a struct’s own field, use
swagger:ignore on the field itself.
The whole remainder of the line is the argument list; spaces after commas are
allowed (swagger:omit ID, Created).
Supported keywords
None. swagger:omit is a classifier: it takes arguments and opens no keyword
block.
Example
The go-swagger#1992 shape: a request body embeds the shared domain type, and the
server-assigned fields are dropped from this body only.
Annotated Go
// CreateUserParams is the request body: the same User, minus the fields the// server assigns. `swagger:omit` sits on the embed, so the targets are plain// field names of the embedded type.//
// swagger:parameters createUsertypeCreateUserParamsstruct {
// in: bodyBodystruct {
// swagger:omit ID,CreatedUser }
}
The shared type is never touched, so its own definition β which the response
$refs β still documents every field:
{
"description": "The point of the idiom is that you do not have to touch it.",
"type": "object",
"title": "User is the shared domain type β deliberately free of any swagger annotation.",
"properties": {
"Created": {
"type": "string",
"format": "date-time" },
"ID": {
"type": "integer",
"format": "int64" },
"Name": {
"type": "string" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/omit"}
All three are Hints β informational, never blocking:
code
fires when
scan.omit-unresolved
the target names no field of the embedded type: a typo, or a field renamed upstream
scan.omit-behind-ref
the embed is composed as a $ref (an annotated swagger:model); Swagger 2.0 cannot subtract a property from a reference, so the omission is dropped rather than silently forking the definition
scan.shadowed-embed-field
a field re-declared with json:"-" carries the Go name of a promoted one β see below
swagger:omit is the only annotation whose output depends on a name the Go
compiler never checks; everything else codescan emits is derived from types.
scan.omit-unresolved reports it when a field is renamed upstream, instead of
letting the annotation rot silently, so wire
OnDiagnostic if
you rely on the annotation.
json:"-" does not hide a promoted field
Re-declaring a promoted field with json:"-" looks like it should hide it. It
does not: encoding/json ignores a - field entirely, so it never enters
the name set, never shadows the promoted one, and Go keeps marshalling the
embedded field. swagger:omit is the annotation that removes it for real.
Declares an HTTP route + operation with a YAML-document body.
Same header line as /codescan/maintainers/annotations/swagger-route/ (method, path, optional
tags, operation ID), but with a different body shape: instead of the
structured Parameters: / Responses: keyword surface, swagger:operation’s
body is a single YAML document spelling out the OpenAPI operation object
directly.
Use swagger:operation when you want to author the operation in YAML (closer
to the OpenAPI spec text) or when the operation has shapes the keyword surface
doesn’t cover.
Where it goes
On a function or variable declaration whose doc comment carries the
annotation. The Go entity itself doesn’t have to be a handler β the annotation
publishes a path/operation independent of the carrier.
InlineOperationBody is an OPAQUE_YAML document. The trailing IDENT_NAME
is the operation ID; the run before it is the tag list. The header line shape
authors rely on:
<METHOD> β GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
Case insensitive.
<path> β starts with /; supports path-parameter braces (/items/{id}).
Only RFC 6570 Level-1 expansion (simple {name} substitution) is allowed;
an inline regex constraint (/items/{id:[0-9]+}) is stripped to the bare
form with a warning.
[tag1 tag2 β¦] β optional whitespace-separated tag list (at least two
characters each).
<operationID> β the unique operation identifier.
Supported keywords
None inside the YAML body β it is structurally YAML, not the keyword grammar.
The header line is the entire annotation surface.
Example
Annotated Go
// swagger:operation GET /pets/{id} pets getPet//
// ---// summary: Get a pet by ID.// parameters:// - name: id// in: path// required: true// type: integer// format: int64// responses:// '200':// description: the requested pet// schema:// $ref: '#/definitions/Pet'// default:// $ref: '#/responses/errorResponse'
The --- delimits the YAML body; everything between the fences is parsed as an
OpenAPI 2.0 operation object.
Full example.testdata/enhancements/parameters-map-postdecl/api.go.
Deprecated / legacy forms
swagger:operation also accepts a structured Parameters: body (shared with
/codescan/maintainers/annotations/swagger-route/). In that body the per-parameter chunk sigil
+ name: is the historic chunk-start; - name: is accepted as a YAML-friendly
alias and is preferred for YAML-correctness. See the chunk grammar in
/codescan/maintainers/sub-languages/#parameters.
Declares a Go struct as the parameter set for one or more operations.
Each field becomes one parameter on the named operation(s), and the
field’s doc comment carries its in:, required:, validations, and
description.
A parameter’s name comes from the field’s json: tag, falling
back to the Go field name when there is no tag (the form: tag is not
consulted). A name: keyword in the field doc takes precedence over
both and is the canonical, preferred way to set the name β the legacy
swagger:nameannotation is inert here and emits a context-invalid
diagnostic pointing at name:. See the
universal name keyword.
Operation IDs accumulate: the same ID may appear in several
swagger:parameters lines to compose a set from multiple structs, and
one struct may carry several lines splitting a long ID list.
swagger:parameters declarations are collected across all scanned
packages and matched to operations by ID, so a shared set can live in
its own package.
Where it goes
On a struct declaration. A bare slice variable (var Filters []string)
carries no per-field in:/type:/required:, so parameters must be a
struct.
The IDENT_NAME arguments are the operation IDs this set applies to (at
least one). The first argument may instead be a * wildcard (spec-level
shared #/parameters/{name}) or a /path (inlined into that exact
path-item) β see
Sharing parameters & responses.
The annotation opens a SchemaBlock
body β field doc comments carry the parameter validations.
Supported keywords
param-context keywords on
fields: in, required, the numeric / length / format validations,
default, example, enum, allowEmptyValue, collectionFormat.
Example
Annotated Go
// ListPetsParams is the parameter set for the listPets operation. Each field// becomes one parameter; the operation IDs after swagger:parameters name the// operations the set applies to.//
// swagger:parameters listPetstypeListPetsParamsstruct {
// Tag filters pets by tag.//
// in: queryTagstring`json:"tag"`// Limit caps the number of results.//
// in: query// minimum: 1// maximum: 100Limitint32`json:"limit"`}
Adds typedpatternProperties entries β each maps a property-name
regex to a value schema.
It is the typed counterpart of the regex-only
patternProperties: keyword
(which uses an empty, any-value schema).
Note
patternProperties is a JSON-Schema (draft-4) keyword, beyond the
Swagger 2.0 subset. codescan emits it ungated β your downstream tooling
must understand it.
A comma-separated list of "<regex>": <spec> pairs. The regex
(STRING_VALUE) is double-quoted β it may contain spaces, colons,
commas; only \" is an escape inside it, other backslashes like \d
are preserved. Each <spec> reuses the /codescan/maintainers/annotations/swagger-type/
value grammar (primitive / []T / type-name β $ref).
Supported keywords
None of its own. It composes with maxProperties / minProperties /
additionalProperties.
Example
Annotated Go
// TypedPatterns maps property-name regexes to typed value schemas. Each quoted// regex pairs with a value spec β a primitive or a model name (which becomes a// $ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0// subset; codescan emits it ungated.//
// swagger:model// swagger:patternProperties "^x-": string, "^\d+$": integer, "^item-": ThingtypeTypedPatternsstruct {
Knownstring`json:"known"`}
{
"description": "TypedPatterns maps property-name regexes to typed value schemas. Each quoted\nregex pairs with a value spec β a primitive or a model name (which becomes a\n$ref). The pattern-properties keyword is JSON-Schema, beyond the Swagger 2.0\nsubset; codescan emits it ungated.",
"type": "object",
"properties": {
"known": {
"type": "string",
"x-go-name": "Known" }
},
"patternProperties": {
"^\\d+$": {
"type": "integer" },
"^item-": {
"$ref": "#/definitions/Thing" },
"^x-": {
"type": "string" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/maps"}
Precedence. Same lowest-priority, object-only rule as
swagger:additionalProperties.
Each regex is RE2-hygiene-checked: one that does not compile raises a
CodeInvalidAnnotation warning but is preserved; a structurally
malformed pair list is dropped with a diagnostic.
Full example.testdata/enhancements/pattern-properties-typed/api.go.
swagger:response
Usage
// swagger:response [ IDENT_NAME ]
What it does
Declares a Go struct as a named response object.
It is emitted into the spec’s top-level responses map. Routes /
operations reference it by name via the response sub-language (Responses:
body in swagger:route, or the YAML $ref form in swagger:operation).
The struct’s fields contribute the response shape:
A field named Body (or carrying in: body) becomes the response
body schema. The body may be a struct, a $ref’d model, or a
primitive β Body string emits schema: {type: string}.
Other fields default to response headers: a field with neither
Body/in: body nor in: header is treated as a header, not a body
property. A header’s key comes from the json: tag / Go field name, or
a name: keyword (e.g. name: X-Rate-Limit) β the canonical, preferred
form, see the name keyword.
An anonymously embedded struct marked in: bodyis the body (a
$ref to the model), not a promotion of its fields.
An interface{} / any-typed field emits an empty schema ({}, or
{type: array, items: {}} for a slice) β “any type”, valid OpenAPI 2.0.
The optional IDENT_NAME is the published response name (default: the
Go type’s name). A * wildcard (swagger:response *) explicitly marks
the response as a shared one, registered at #/responses/{name} for
operations to $ref by name β see
Sharing parameters & responses.
Header field: header-context keywords β numeric / length / format
validations, pattern, enum, default, example,
collectionFormat. required: is silently dropped (the OAS v2 Header
object has no required field).
Example
Annotated Go
// PetsResponse is the list returned by listPets.//
// swagger:response petsResponsetypePetsResponsestruct {
// in: bodyBody []Pet}
// ErrorResponse is the default error payload.//
// swagger:response errorResponsetypeErrorResponsestruct {
// in: bodyBodystruct {
// Message is a human-readable error message.Messagestring`json:"message"` }
}
Declares an HTTP route + operation in one annotation.
The header line carries the method, path, optional tags, and the operation ID;
the comment body carries the operation’s metadata (consumes / produces /
schemes / security / parameters / responses / extensions).
This is the terser of the two operation-declaration annotations. Most
go-swagger projects use swagger:route for hand-written operations; see
/codescan/maintainers/annotations/swagger-operation/ for the YAML-body alternative.
Where it goes
On a function or variable declaration whose doc comment carries the
annotation. The Go entity itself doesn’t have to be a handler β the annotation
publishes a path/operation independent of the carrier.
A godoc-style identifier may precede the annotation on the same comment line
(// ListPets swagger:route GET /pets pets users listPets); that leading
identifier is recognised as a godoc convention and is not part of the
annotation surface.
<METHOD> β GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS.
Case insensitive.
<path> β starts with /; supports path-parameter braces (/items/{id}).
Only RFC 6570 Level-1 expansion (simple {name} substitution) is allowed;
an inline regex constraint (/items/{id:[0-9]+}) is stripped to the bare
form with a warning.
[tag1 tag2 β¦] β optional whitespace-separated tag list (at least two
characters each).
<operationID> β the unique operation identifier.
Supported keywords
All body keywords legal in route
context (consumes, produces, schemes, security, parameters,
responses, extensions, externalDocs) plus inline deprecated: and a body
tags: list (a string list, unioned and deduplicated with the header-line
tags). The Parameters: and Responses: sub-languages are documented in
/codescan/maintainers/sub-languages/#parameters and
/codescan/maintainers/sub-languages/#responses.
Example
Annotated Go
// swagger:route GET /pets pets listPets//
// Lists pets in the store, optionally filtered by tag.//
// responses://
// 200: petsResponse// default: errorResponse
Full example.testdata/enhancements/routes-full-petstore-shape/handlers.go.
Deprecated / legacy forms
In the Parameters: body the per-parameter chunk sigil + name: (used in the
sample above) is the historic chunk-start; - name: is accepted as a
YAML-friendly alias and is preferred for YAML-correctness. See the chunk
grammar in /codescan/maintainers/sub-languages/#parameters.
swagger:strfmt
Usage
// swagger:strfmt FORMAT_NAME
What it does
Marks a named type as a custom string format.
Wherever the type appears as a field, the emitted schema is
{type: string, format: <name>}. Useful for UUID, Email, URL-style
types that have a Go type but should serialise as a JSON string with a
known format.
A field typed by the marked type emits with the format; the underlying
type does NOT appear as a top-level model definition (strfmt-tagged types
are replaced by their format at every reference). A slice carries the
format onto its items: {type: array, items: {type: string, format: β¦}}.
Where it goes
On a type declaration whose underlying form is a string-marshalable type
(typically implementing encoding.TextMarshaler / encoding.TextUnmarshaler).
swagger:strfmt may also sit on a struct field doc to override just
that field’s published format.
The required IDENT_NAME is the format name (uuid, email, mac, β¦) β
the entire surface of the annotation.
Supported keywords
None at the type level beyond swagger:strfmt itself; the format name is
the entire surface.
Example
A named type marked swagger:strfmt (here a MarshalText/UnmarshalText
hardware address) emits as {type: string, format: β¦} wherever it is
referenced β a field typed MAC comes out as {type: string, format: mac}:
Annotated Go
// MAC is a hardware address rendered as a colon-separated hex string.//
// swagger:strfmt mactypeMACstringfunc (mMAC) MarshalText() ([]byte, error) { return []byte(m), nil }
func (m*MAC) UnmarshalText(b []byte) error { *m = MAC(b); returnnil }
// Device exposes a strfmt-typed field: wherever MAC appears it renders inline// as {type: string, format: mac}.//
// swagger:modeltypeDevicestruct {
// Addr is the hardware address.AddrMAC`json:"addr"`}
Adding swagger:model opts the type into a first-class definition
carrying the full {type: string, format: β¦} schema, with referencing
fields pointing at it via $ref β the general
swagger:model β definition + $ref rule. Without swagger:model, the
format inlines at every reference.
A field-level override targets one field’s format β e.g.
// swagger:strfmt int64 on a uint64 field emits
{type: string, format: int64}, a precision-safe, JSON-conformant string
encoding (the conformant alternative to the Go-specific {integer, format: uint64} codescan emits for unsized/large ints by default).
Full example.testdata/enhancements/text-marshal/types.go.
swagger:title
Usage
// swagger:title <text> (where <text> is the rest of the line)
What it does
Replaces the godoc-derived title on a schema with explicit text.
By default a model’s title comes from the first line of its doc comment;
swagger:title overrides that when the prose isn’t the title you want to
publish. It is a schema-family override β a sibling of
swagger:description.
Where it goes
On a type (model) doc comment or a struct-field doc comment. It is
schema-only: a response has no title (the annotation is ignored there), and on
a non-body parameter or response header it is rejected with a
CodeContextInvalid diagnostic.
Grammar (EBNF)
TitleAnnotation =ANN_TITLE , RAW_VALUE ;
RAW_VALUE is the rest of the head line, captured verbatim. The annotation
dispatches through the schema parser
(not the classifier parser), so validation keywords co-located on the same
comment group still surface as schema validations.
Supported keywords
None of its own β the text is the entire argument. A blank swagger:title
emits a CodeEmptyOverride diagnostic.
Example
Annotated Go
// Widget is the Go-facing widget doc, written for Go readers.//
// It explains internal Go usage that should not leak into the API spec.//
// swagger:model// swagger:title A Public Widget// swagger:description A widget exposed via the public API.typeWidgetstruct {
// ID explains the Go field for Go readers.//
// swagger:description The unique widget identifier.IDstring`json:"id"`// Label is the Go-facing field doc. Fields carry no title by default;// the override is the only way a property gets one.//
// swagger:title Display Label// swagger:description Human-readable label shown to API consumers.Labelstring`json:"label"`// Plain keeps its godoc description because it carries no override.Plainstring`json:"plain"`// Capacity combines a description override with an inline validation// keyword on the same field: the override applies AND maximum is kept,// because the override annotations dispatch through the schema family.//
// swagger:description The maximum capacity, in liters.// maximum: 1000Capacityint64`json:"capacity"`// Suppressed has a godoc that a bare swagger:description suppresses: the// empty value is applied (description omitted) and scan.empty-override is// raised, in case the bare marker was left behind by mistake.//
// swagger:descriptionSuppressedstring`json:"suppressed"`// Notes carries a multi-line description override: the lines following the// annotation fold into the description until the blank line, joined with// newlines.//
// swagger:description Free-form notes about the widget.// They may span several lines, all folded into one description.//
// The blank line above terminates the override body; this paragraph is// ordinary godoc and is discarded (the override won).Notesstring`json:"notes"`// Gadget is a $ref field carrying title + description overrides. They are// symmetric $ref siblings: kept under EmitRefSiblings, dropped to a bare// $ref under the default flags β the same rule a prose description follows.//
// swagger:title Gadget Ref// swagger:description The attached gadget, described for API consumers.GadgetGadget`json:"gadget"`}
// Gadget is a plain referenced model.//
// swagger:modeltypeGadgetstruct {
Serialstring`json:"serial"`}
// swagger:type <type> (where <type> is a scalar, []T, inline, or a known type name)
What it does
Replaces a field’s (or named type’s) inferred Swagger type with an
inlined type.
swagger:type is an inline directive β it never emits a $ref; the chosen
type is rendered directly in place (the default $ref-for-named-types is
the no-annotation behaviour).
Where it goes
On a type declaration, a struct field doc, OR a swagger:parameters field
doc.
Note
On a parameter field the override collapses the field to a simple
parameter β useful when a struct- or defined-typed field would otherwise
come out typeless (invalid Swagger 2.0). The argument is restricted to a
scalar or a []-wrapped scalar there: the inline and type-name
forms are rejected with a diagnostic, since a non-body parameter has no
schema to inline a type into. A compatible swagger:strfmt on the same
field still rides as a supplementary format.
a scalar type β string, integer, number, boolean, object
(or a Go-builtin spelling such as int64, uint32);
[]T β an array whose items are the inlined T (recursive:
[][]int64, []Custom);
inline β expand the field’s own Go type in place, instead of the
$ref a named type would otherwise produce;
a known type name β inline that type’s schema (again, no $ref).
An unknown name falls back to inlining the field’s Go type, with a
validate.unsupported-type diagnostic.
Supported keywords
None β the override type is the entire surface.
Example
Type-level override β a named type whose underlying shape is irrelevant to
the wire form is inlined to the chosen scalar; fields typed by it emit as
{type: string} regardless of the underlying shape:
Annotated Go
// ULID is a 128-bit identifier stored as bytes but rendered as a string.//
// swagger:type stringtypeULID [16]byte// Token carries a field whose inferred type is overridden, inline.//
// swagger:modeltypeTokenstruct {
// ID renders as a string despite its [16]byte Go type.IDULID`json:"id"`}
// RawID is a custom 16-byte identifier β an array under the hood, so left to// itself a field of this type would render as an array of integers.typeRawID [16]byte// Coupon overrides the type of a single field directly on the field doc β no// wrapper-type annotation. Code publishes as a bare string while RawID is left// untouched everywhere else it appears.//
// swagger:modeltypeCouponstruct {
// Code is an opaque identifier published as a string.//
// swagger:type stringCodeRawID`json:"code"`// Amount is the discount in cents.Amountint64`json:"amount"`}
{
"type": "object",
"title": "Token carries a field whose inferred type is overridden, inline.",
"properties": {
"id": {
"description": "ID renders as a string despite its [16]byte Go type.",
"type": "string",
"x-go-name": "ID" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
Field-level override β the same directive on a single struct field replaces
just that field’s inferred type in place (e.g. an opaque payload published
as a string blob):
Annotated Go
// RawID is a custom 16-byte identifier β an array under the hood, so left to// itself a field of this type would render as an array of integers.typeRawID [16]byte// Coupon overrides the type of a single field directly on the field doc β no// wrapper-type annotation. Code publishes as a bare string while RawID is left// untouched everywhere else it appears.//
// swagger:modeltypeCouponstruct {
// Code is an opaque identifier published as a string.//
// swagger:type stringCodeRawID`json:"code"`// Amount is the discount in cents.Amountint64`json:"amount"`}
{
"description": "Coupon overrides the type of a single field directly on the field doc β no\nwrapper-type annotation. Code publishes as a bare string while RawID is left\nuntouched everywhere else it appears.",
"type": "object",
"properties": {
"amount": {
"description": "Amount is the discount in cents.",
"type": "integer",
"format": "int64",
"x-go-name": "Amount" },
"code": {
"description": "Code is an opaque identifier published as a string.",
"type": "string",
"x-go-name": "Code" }
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/models"}
Interaction with swagger:strfmt.swagger:type wins on the type
axis; a swagger:strfmt format on the same field is kept only when
compatible with the resolved type (a string accepts any format,
numeric types accept the numeric width formats), otherwise it is dropped
with a shape-mismatch diagnostic. swagger:strfmt alone is unchanged. See
swagger:strfmt.
Interaction with swagger:model. On a type declaration that also
carries swagger:model, the override shapes the type’s first-class
definition (e.g. swagger:type string + swagger:model β a
{type: string} definition) and referencing fields $ref it. The
field-level inline form above is the behaviour withoutswagger:model.
Full example.testdata/enhancements/named-struct-tags-ref/types.go.
Deprecated / legacy forms
The array argument is deprecated β use inline, or []T for an
explicit element type. It still works, with a validate.deprecated
warning.
file used to be rejected as an argument. It is now accepted, and is the
preferred spelling: file is an OAS v2 type name like any other, so the
annotation that names types names it too. It is a synonym for
swagger:file, which is expected to be
deprecated as an extraneous annotation.
file is legal in exactly two places β a formData parameter and a response
body. Both spellings pass through the same location gate, so neither can put
file anywhere OAS 2.0 forbids it; elsewhere the override is refused with a
diagnostic and the Go type stands.
Keyword reference
Keywords are the keyword: value lines that decorate an
annotation block. They come in two flavours:
inline (one line, keyword: value, the value classified by a
value shape) and body (a header line
plus indented continuation lines β a flat token list, a YAML map, or a per-line
sub-language). Three things matter about any keyword: the class it belongs to,
the annotation contexts that accept it, and its value shape.
This section groups the surface by class β pick the page that matches what you’re
decorating. For the formal productions see grammar.md;
for the value-shape and context-token reference tables see the
Appendix.
Keywords that decorate swagger:parameters fields and swagger:response headers β the reduced OAS 2.0 SimpleSchema surface, plus the parameter location and response-level examples.
Top-of-document keywords authored under swagger:meta β version, host, base path, license, contact, terms of service β plus the cross-cutting vendor-extension and external-docs keywords.
Reference tables β the value shapes the lexer classifies, and the meaning of each annotation-context token.
Context matrix
Which annotation family accepts a given keyword β the transpose of the
annotation Γ keyword matrix.
A β means the keyword is legal on that annotation (on the annotation’s own block or
on one of its fields); a blank means it is rejected there with a
CodeContextInvalid diagnostic. The detailed entry for each keyword lives on its
class page (linked above).
Keyword
meta
model
parameters
response
route
operation
maximumminimummultipleOf
β
β
β
maxLengthminLength
β
β
β
maxItemsminItemsunique
β
β
β
pattern
β
β
β
collectionFormat
β
β
maxPropertiesminProperties
β
patternPropertiesadditionalProperties
β
defaultexampleenum
β
β
β
required
β
β
readOnlydiscriminator
β
deprecated
β
β
β
in
β
name
β
β
β
examples
β
schemesconsumesproduces
β
β
β
security
β
β
β
securityDefinitions
β
responsesparameters
β
β
tags
β
β
β
versionhostbasePathlicensecontacttos
β
infoExtensions
β
externalDocs
β
β
β
β
extensions
β
β
β
β
β
β
The parameters / response columns also cover the items sub-context (array
elements): the array-element validations ride there too. model covers
swagger:allOf member fields. See the
Appendix for the precise meaning of
each context token (param, header, schema, items, β¦).
Subsections of Keyword reference
Parameters & responses
These keywords decorate swagger:parameters fields and swagger:response
headers. Both sites ride the reduced OAS 2.0 SimpleSchema surface: the
validations constrain a primitive (or array-of-primitive) value, but the
full-Schema-only keywords (maxProperties / minProperties /
patternProperties / additionalProperties / readOnly / discriminator /
externalDocs) do not apply here. The location keyword in and the
universal name keyword are at home on this page; the validations are shared
with Schema validations & decorators.
Summary
Keyword
Aliases
Shape
Contexts
in
β
string (closed-vocab)
param
name
β
string
param, header, schema, items
collectionFormat
collection format, collection-format
string (closed-vocab)
param, header, items
examples
β
YAML map (mime β payload)
response
maximum
max
number
param, header
minimum
min
number
param, header
multipleOf
multiple of, multiple-of
number
param, header
maxLength
max length, maxLen, β¦
integer
param, header
minLength
min length, minLen, β¦
integer
param, header
maxItems
max items, maximumItems, β¦
integer
param, header
minItems
min items, minimumItems, β¦
integer
param, header
pattern
β
string
param, header
unique
β
boolean
param, header
default
β
raw-value
param, header
example
β
raw-value
param, header
enum
β
raw-value
param, header
required
β
boolean
param
The validation rows (maximum β¦ required) are visiting here: they behave
exactly as on schemas β see
Schema validations & decorators.
Two SimpleSchema restrictions apply on this page: required is dropped on
response headers (it sets parameter.required only on a body/non-body param),
and the object / structural keywords (maxProperties, minProperties,
patternProperties, additionalProperties, readOnly, discriminator,
externalDocs) are not legal here β placing one on a SimpleSchema site
drops it with CodeUnsupportedInSimpleSchema.
Worked example(s)
A parameter set, every field carrying the SimpleSchema validation surface:
Annotated Go
// SearchParams is the simple-schema parameter set for searchProducts. Query// parameters accept the reduced OAS 2.0 validation surface.//
// swagger:parameters searchProductstypeSearchParamsstruct {
// Q is the search text.//
// in: query// min length: 3// max length: 50Qstring`json:"q"`// Limit caps the number of results.//
// in: query// minimum: 1// maximum: 100Limitint32`json:"limit"`// Sort lists the sort fields.//
// in: query// collection format: csv// unique: trueSort []string`json:"sort"`}
A response with a validated header (note in is absent on header fields):
Annotated Go
// RateLimited is a response carrying a validated header (a simple schema).//
// swagger:response rateLimitedtypeRateLimitedstruct {
// XRateRemaining is the remaining request budget.//
// minimum: 0XRateRemainingint32`json:"X-Rate-Remaining"`}
Where the parameter value comes from. Closed-vocab:
query β query string parameter.
path β path-parameter substitution.
header β request header.
body β request body (JSON, etc.).
formData β form-data body field (note: form is accepted as an alias
inside swagger:route Parameters: chunks; the lexer normalises it to
formData at the canonical surface).
A non-matching value emits a context-invalid diagnostic; the parameter loses its
in and may end up incorrectly classified downstream. The keyword is
parameter-only β it has no meaning on a response header (the header name is the
location).
Field naming
name
Sets the published name of any field it decorates, overriding the json:
tag / Go field name. It is the one canonical field-naming keyword and works at
every field site: a swagger:model property, an interface method, a
swagger:parameters field (the parameter name), and a swagger:response header
field (the Headers map key). Being structural, it is stripped from the
description rather than leaking into it.
Precedence, most-explicit-wins and identical in every context:
name: keyword > swagger:name annotation > json: tag > Go field name
swagger:name is the
older annotation form β still honoured, and idiomatic on interface methods β but
name: is the universal keyword. Using swagger:name in a parameter or
response-header context (where name: is canonical) is inert and now emits a
context-invalid diagnostic pointing you at the keyword.
Wire serialisation
collectionFormat
How an array value is serialised on the wire. Closed-vocab:
csv β comma-separated (default).
ssv β space-separated.
tsv β tab-separated.
pipes β pipe-separated.
multi β repeated ?key=val&key=val2 (query params only).
Aliases: collection format, collection-format. Maps to
parameter.collectionFormat / items.collectionFormat. This is a
SimpleSchema-only concept β schema-level contexts ignore it (schemas serialise
via application/json). When the source value doesn’t match the closed vocab,
the raw value is preserved verbatim on the parameter (so pipe as a typo for
pipes round-trips).
Response examples
examples
Response-level examples on a swagger:response struct β a YAML map whose
first-level keys are mime types and whose values are the example payloads,
populating the OAS2 Response.examples field. This is the plural,
response-scoped keyword; contrast the singular, schema/param/header-scoped
example decorator.
The swagger:operation YAML body carries examples natively (it is unmarshalled
straight into the spec types); this keyword is the struct-swagger:response
counterpart.
See also Spec metadata for the document-level
keywords that frame these operations.
Schema validations & decorators
These keywords decorate a swagger:model schema or any struct field doc comment.
The validations constrain a value; the decorators carry defaults, examples, and
structural markers. Several also apply to parameters and response headers β there
they ride the reduced SimpleSchema surface
(Parameters & responses).
Summary
Keyword
Aliases
Shape
Contexts
maximum
max
number
param, header, schema, items
minimum
min
number
param, header, schema, items
multipleOf
multiple of, multiple-of
number
param, header, schema, items
maxLength
max length, maxLen, β¦
integer
param, header, schema, items
minLength
min length, minLen, β¦
integer
param, header, schema, items
maxItems
max items, maximumItems, β¦
integer
param, header, schema, items
minItems
min items, minimumItems, β¦
integer
param, header, schema, items
maxProperties
max properties, β¦
integer
schema
minProperties
min properties, β¦
integer
schema
pattern
β
string
param, header, schema, items
patternProperties
pattern properties, pattern-properties
string (regex)
schema
additionalProperties
additional properties, additional-properties
true/false/type
schema
unique
β
boolean
param, header, schema, items
default
β
raw-value
param, header, schema, items
example
β
raw-value
param, header, schema, items
enum
β
raw-value
param, header, schema, items
required
β
boolean
param, schema
readOnly
read only, read-only
boolean
schema
discriminator
β
boolean
schema
deprecated
β
boolean
operation, route, schema
The shared rows above (param/header/schema/items) are detailed here; on parameters
and headers they behave the same, with the OAS 2.0 SimpleSchema restrictions noted on
the Parameters & responses page.
collectionFormat and in/name live there too.
Worked examples
Every validation on a model’s fields, side by side with the schema it produces:
Annotated Go
// Product is a model whose fields carry the full JSON-schema validation surface.//
// swagger:modeltypeProductstruct {
// SKU is the stock code.//
// required: true// pattern: ^[A-Z]{3}-[0-9]{4}$SKUstring`json:"sku"`// Price is the price in cents.//
// minimum: 1// maximum: 1000000// multipleOf: 1Priceint64`json:"price"`// Name is the display name.//
// min length: 1// max length: 120Namestring`json:"name"`// Grade is a quality band.//
// enum: A,B,CGradestring`json:"grade"`// Tags label the product.//
// min items: 1// max items: 10// unique: trueTags []string`json:"tags"`}
The object-validation keywords constrain the map of properties rather than named
fields:
Annotated Go
// Attributes is a free-form object constrained by the object-validation// keywords: it must carry between 1 and 10 properties, and any property whose// name matches the regex is permitted. Object validations constrain the map of// (additional) properties rather than named struct fields.//
// minProperties: 1// maxProperties: 10// patternProperties: ^x-//
// swagger:model AttributestypeAttributesmap[string]any
{
"description": "Attributes is a free-form object constrained by the object-validation\nkeywords: it must carry between 1 and 10 properties, and any property whose\nname matches the regex is permitted. Object validations constrain the map of\n(additional) properties rather than named struct fields.",
"type": "object",
"maxProperties": 10,
"minProperties": 1,
"additionalProperties": {},
"patternProperties": {
"^x-": {}
},
"x-go-package": "github.com/go-openapi/codescan/docs/examples/concepts/validations"}
Apply to numeric schema types (integer, number). On a typed schema with a
non-numeric type they emit CodeShapeMismatch and drop; on a typeless schema they
apply best-effort.
maximum / minimum
Upper / lower bound on a numeric value (aliases max / min). The value may carry a
leading comparison operator that sets the exclusive/inclusive bound:
maximum: 10 β inclusive (β€ 10);
maximum: <10 β exclusive (< 10);
maximum: <=10 / maximum: =10 β inclusive.
Map to schema.maximum/exclusiveMaximum and schema.minimum/exclusiveMinimum.
multipleOf
Divisibility constraint; the value must be a positive number. Aliases multiple of,
multiple-of. Maps to schema.multipleOf.
Length, array & object validations
maxLength / minLength apply only to string-typed schemas; maxItems /
minItems only to array-typed; maxProperties / minProperties /
patternProperties only to object-typed. The wrong pairing emits
CodeShapeMismatch and drops. The object keywords are additionally
full-Schema-only β no SimpleSchema (non-body param, header, items) form exists in
OAS 2.0, so on such a site they drop with CodeUnsupportedInSimpleSchema.
maxLength / minLength
String length bounds. Many ergonomic aliases (max length, max-length, maxLen,
maximumLength, β¦; min likewise). Map to schema.maxLength / schema.minLength.
maxItems / minItems
Array length bounds (aliases max items, maximumItems, β¦). Map to
schema.maxItems / schema.minItems.
maxProperties / minProperties
Property-count bounds on an object schema (aliases max properties, β¦). Map to
schema.maxProperties / schema.minProperties. Schema-only.
patternProperties
Constrains the names of properties on an object schema by regex. The argument is
one regex string; each line adds an entry to schema.patternProperties mapping the
regex to an empty value schema ({} β any value allowed). Repeated lines accumulate.
Aliases pattern properties, pattern-properties. The regex is RE2-hygiene-checked:
one that doesn’t compile raises CodeInvalidAnnotation but is preserved.
For typed value schemas (a regex β primitive or model $ref), use the
decl-level swagger:patternProperties
marker. patternProperties is JSON-Schema, beyond the Swagger 2.0 subset β see
Maps & free-form objects.
additionalProperties
Policy for keys beyond the named properties on an object schema: true (allow any),
false (close the object), or a value type (primitive / []T, or a model name
β $ref). On a map field it overrides the Go element schema; on a $ref’d field the
value rides an allOf sibling so the reference is kept. Aliases additional properties, additional-properties. Lowest-priority and object-only β dropped with
CodeShapeMismatch on a non-object. The decl-level
swagger:additionalProperties
marker does the same on a type.
Format
pattern
A regex constraint on a string value, preserved verbatim on schema.pattern β
including backslash escapes (\d, \., \n reach the spec as literal two-character
sequences). The grammar runs a best-effort RE2 compile; a failure surfaces
CodeInvalidAnnotation but the value still lands (downstream tools may use a wider
regex dialect).
unique
Marks an array-typed schema as set-valued (no duplicates). Boolean. Maps to
schema.uniqueItems.
Schema decorators
default
Default value for a schema or simple-schema field. Raw-value shape β the post-colon
text is captured verbatim and coerced against the resolved schema type at write time
(ParseDefault / CoerceValue). Single-line for primitives (default: 1),
multi-line bodies for complex literals:
// default:// { "rps": 100, "burst": 200 }
example
An example value for the schema, surfaced in tooling. Same raw-value shape as
default. Maps to schema.example (or parameter.example for SimpleSchema). This
is the singular, schema-scoped keyword; for the plural response-scoped
examples (a map keyed by mime
type) see Parameters & responses.
enum
A closed set of allowed values. Accepted forms: comma list (enum: red, green),
bracketed comma list (enum: [red, green]), JSON array (enum: ["red","green"]), or
a multi-line - list. Each element is coerced against the resolved type; maps to
schema.enum.
For string enums driven by Go consts the
swagger:enum annotation is
more idiomatic β it picks up the constant names + godoc and produces
x-go-enum-desc. The enum: keyword is the manual override. (Set
SkipEnumDescriptions: true to keep the constβvalue mapping on x-go-enum-desc only,
out of the description.)
Do not confuse the two: the annotation collects members from a Go const block
and types them from the declared Go type; the keyword takes the members you write
and types them from the schema it sits on. Side-by-side comparison in the
enumerations tutorial.
required
Marks a field as required. Boolean.
On a swagger:model field: adds the field name to the schema’s required array.
On a swagger:parameters field: sets parameter.required.
On a swagger:response header: not applicable β silently dropped.
readOnly
Marks a schema property read-only. Aliases read only, read-only. Maps to
schema.readOnly. Schema-only β inside a SimpleSchema context it drops with
CodeUnsupportedInSimpleSchema.
discriminator
Marks the property as the discriminator for an allOf polymorphic schema. Boolean;
writes the property name onto the schema’s discriminator. Schema-only. The property
should also be required. Subtypes that allOf-embed the base inherit it; each
subtype’s discriminator value is its definition name. See
Polymorphic models.
deprecated
Marks the carrying entity deprecated. Boolean. On operations/routes it writes the
native OAS 2.0 deprecated; OAS 2.0 has no Schema-object deprecated, so on a model
or field it emits x-deprecated: true. A godoc Deprecated: paragraph is an exact
synonym recognised in any context β and is idiomatic on Go doc comments. Because it
carries intent, x-deprecated survives even under SkipExtensions.
Routes & operations
These keywords live inside a swagger:route or swagger:operation block. They
carry the operation’s transport metadata β the URL schemes, the media types it
consumes and produces β and the two sub-language bodies that declare its
parameters and responses. Several of them double as document-wide defaults under
swagger:meta (Spec metadata), where an
operation-level value overrides the default.
Summary
Keyword
Aliases
Shape
Contexts
schemes
β
flex-list
meta, route, operation
consumes
β
flex-list
meta, route, operation
produces
β
flex-list
meta, route, operation
responses
β
sub-language (<code>: <tokens>)
route, operation
parameters
β
sub-language (+ name: chunks)
route, operation
tags
β
string list / tag objects
meta, route, operation
deprecated
β
boolean
operation, route, schema
security
β
YAML sequence (raw-block)
meta, route, operation
externalDocs
external docs, external-docs
{description, url}
meta, route, operation, schema
extensions
β
x-* YAML map
route, operation (cross-cutting)
The visiting rows are documented where they primarily apply: deprecated is
detailed under
Schema validations & decorators;
security (and its securityDefinitions catalogue) under
Security; externalDocs and extensions under
Spec metadata.
Worked examples
A swagger:route block β the path-line annotation plus its transport metadata
and a Responses: body β side by side with the path item it produces:
Annotated Go
// swagger:route GET /pets pets listPets//
// Lists pets in the store, optionally filtered by tag.//
// responses://
// 200: petsResponse// default: errorResponse
Accepted URL schemes for the operation. Flexible list β comma inline, multi-line
bare, YAML - markers, or any combination all produce the same output
(Schemes: http, https β‘ a - http / - https block). See
sub-languages Β§flex-lists for the unified rule.
Maps to schemes on the enclosing operation. It is also a document default
under swagger:meta (spec.schemes), where an operation-level value overrides
the meta-level one β see Spec metadata.
consumes / produces
Media-type lists β the request body MIME types the operation consumes and the
response MIME types it produces. Same flex-list rule as schemes: comma
inline, multi-line bare, YAML - markers, or any combination.
Map to consumes / produces on the surrounding scope. Like schemes, both are
also swagger:meta document defaults overridden per operation.
Body sub-languages
responses
Per-route / per-operation response declarations. The body is one response per
line in the form <code>: <tokens>, where <code> is an HTTP status (or
default) and <tokens> names the body schema and/or description:
Responses:
200: body:User the requested user
404: description: not found
default: response:genericError
Per-route / per-operation parameter declarations. The body is a sequence of
+ name: chunks β the + is the chunk-start sigil (- is accepted as an
alias) β each chunk a small key/value block describing one parameter:
In swagger:route / swagger:operation the body is a plain string
list. It is unioned and deduplicated with the tags written on the
annotation’s header line, and the result lands on the operation’s tags:
Tags:
- pets
- store
In swagger:meta the body is instead a YAML sequence of tag objects
emitted into the spec’s top-level tags β each with a name, an optional
description, a nested externalDocs, and any x-* vendor extensions:
Tags:
- name: pets
description: Everything about your Pets
externalDocs:
description: Find out more
url: https://example.com/docs/pets
- name: store
x-display-name: Store
The meta tag-objects form is also referenced from
Spec metadata.
Visiting keywords
These keywords also appear in a route/operation block but are detailed on their
home page:
security β the per-route / per-operation requirement list (an empty
Security: [] on an operation is an explicit public opt-out).
See Security.
externalDocs β the operation’s external-documentation pointer.
See Spec metadata.
extensions β vendor x-* entries on the operation.
See Spec metadata.
Security
Two keywords carry authentication into the spec. security lists the
requirements that gate the document, a route, or a single operation;
securityDefinitions is the scheme catalogue β declared once in
swagger:meta and referenced by name from every requirement. A requirement is
only meaningful when the scheme it names is defined, so the two are almost always
authored together (see Spec metadata for the
rest of the swagger:meta surface and Routes & operations
for where per-route requirements live).
Summary
Keyword
Aliases
Shape
Contexts
security
β
YAML sequence (raw-block)
meta, route, operation
securityDefinitions
security definitions, security-definitions
YAML map (raw-block)
meta
Worked example
The scheme catalogue and the document-wide default requirement, declared once in
the package swagger:meta block β the schemes golden captures both
securityDefinitions and the top-level security:
A route then overrides that default with its own Security: requirement β here
oauth2 with the read and write scopes:
Annotated Go
// listReports inherits the document-wide default requirement (api_key) β no// Security: keyword is needed.//
// swagger:route GET /reports reports listReports//
// responses:// 200: description: the reports// createReport overrides the default with its own Security: requirement β// oauth2 with the read and write scopes. The Security: block is YAML: a sequence// of requirement objects, scopes as a flow (or block) list.//
// swagger:route POST /reports reports createReport//
// Security:// - oauth2: [read, write]//
// responses:// 201: description: created// archiveReport requires BOTH schemes at once β two keys in a single sequence// item are ANDed into one requirement object (separate items would mean OR).//
// swagger:route POST /reports/archive reports archiveReport//
// Security:// - api_key: []// oauth2: [write]//
// responses:// 200: description: archived// publicReport opts out of the document default entirely β an empty// `Security: []` emits an explicit empty requirement, marking the operation// public regardless of the document-wide default.//
// swagger:route GET /reports/public reports publicReport//
// Security: []//
// responses:// 200: description: the public reports
A YAML sequence of requirement objects parsed from the Security: body. The
semantics are OAS 2.0:
multiple keys within one sequence item are ANDed β all of those schemes
are required together ({api_key, oauth2} in one item);
separate items are ORed β satisfying any one item grants access;
a scheme’s value is its scope list, a flow ([read, write]) or block list.
For non-scoped schemes (apiKey, basic) the list is empty (api_key: []),
meaning the scheme is required with no scopes;
an empty top-level Security: [] on an operation emits an explicit empty
requirement β an intentional public opt-out that overrides the document-wide
default rather than inheriting it.
A bare top-level mapping (api_key: / oauth2: read, write, comma-split scopes)
is still read as one OR requirement per key for back-compatibility. Maps to
security on the enclosing object. Legal in swagger:meta (the document
default), swagger:route, and swagger:operation. The full per-line body
grammar lives at
sub-languages Β§security requirements.
securityDefinitions
A YAML map, parsed directly into the spec.securityDefinitions shape β each entry
is a named scheme (apiKey, oauth2, basic) with its OAS 2.0 fields (type,
in, name, flow, authorizationUrl, tokenUrl, scopes, β¦); see
OAS v2 Β§securityDefinitionsObject.
Aliases security definitions, security-definitions. Meta-only β the
scheme catalogue is declared once at the top of the document and referenced by
name from every security requirement. Its detail anchor is #securitydefinitions.
Spec metadata
These keywords author the spec’s top-level fields. Most live in the package doc
comment carrying the swagger:meta block; a couple are cross-cutting and merely
have their home here β extensions lands on whatever scope it decorates, and
externalDocs rides meta, operations, schemas, and struct fields alike. The
remaining document-level concerns (schemes/consumes/produces, security/
securityDefinitions, the meta tags form) are owned by sibling pages and only
visit this one.
Summary
Keyword
Aliases
Shape
Home
version
β
string
here
host
β
string
here
basePath
base path, base-path
string
here
license
β
Name [URL]
here
contact
contact info, contact-info
Name <email> [URL]
here
tos
terms of service, terms-of-service, termsOfService
The visiting rows are documented where they primarily apply: schemes,
consumes, and produces are document-wide defaults overridden per operation
(Routes & operations); security and
securityDefinitions are detailed under Security;
the swagger:meta tag-objects form of tags is described under
Routes & operations.
Worked example
A complete swagger:meta block, side by side with the document-level spec it
produces:
Annotated Go
// Package meta Pet Store.//
// A small API that demonstrates the document-level swagger:meta block: the// package doc comment carries the spec's top-level metadata.//
// Schemes: https// Host: api.example.com// BasePath: /v1// Version: 1.2.0// License: Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0.html// Contact: API Team <api@example.com> https://example.com/support//
// Consumes:// - application/json//
// Produces:// - application/json//
// ExternalDocs:// description: Full API guide// url: https://example.com/docs//
// Tags:// - name: pets// description: Everything about your Pets// externalDocs:// description: Find out more// url: https://example.com/docs/pets// - name: store// description: Access to Petstore orders// x-display-name: Store//
// SecurityDefinitions:// basic_auth:// type: basic// api_key:// type: apiKey// in: header// name: X-API-Key//
// Security:// basic_auth://
// InfoExtensions:// x-logo:// url: https://example.com/logo.png// altText: Example//
// swagger:metapackagemeta
The trailing token starting with a URL scheme becomes license.url; the prefix
becomes license.name. A bare name with no URL is accepted too. Maps to
info.license.
contact
Contact declaration. The author writes a Name <email> URL triple, in any
order; the grammar recognises:
Name <email@example.com> β Go’s net/mail.ParseAddress form;
Name <email@example.com> https://example.com β same, plus a trailing URL;
just a URL, with no name.
Aliases: contact info, contact-info. Maps to info.contact.
Document-level keywords
tos
Terms-of-service prose paragraph. The multi-line body is joined with \n after
dropping whitespace-only lines. Aliases: terms of service, terms-of-service,
termsOfService. Maps to info.termsOfService. Meta-only.
infoExtensions
Vendor-extension declarations as a YAML map, landed on info.extensions. Keys
must start with x- or X-; a non-x-* key emits CodeInvalidAnnotation and
drops. Meta-only. Aliases: info extensions, info-extensions.
InfoExtensions:
x-logo:
url: https://example.com/logo.png
altText: Example
For the same map on the surrounding scope rather than info, use
extensions.
extensions
Vendor-extension declarations as a YAML map, landed on the surrounding scope
rather than on info: spec.extensions, operation.extensions,
schema.extensions, parameter.extensions, header.extensions, and so on β
including on parameters and response headers. Keys must start with x- or X-;
a non-x-* key emits CodeInvalidAnnotation and drops.
This keyword is cross-cutting β it is documented here as its home, but applies
wherever a YAML body is parsed. For the meta-only info.extensions variant see
infoExtensions.
externalDocs
External-documentation pointer as a YAML map with description and url keys.
Aliases: external docs, external-docs.
Emitted on:
swagger:meta β the top-level externalDocs object (and, nested under a
Tags: entry, that tag’s externalDocs);
swagger:route / swagger:operation β the operation’s externalDocs;
swagger:model (and any full Schema, e.g. a body parameter’s schema) β
the schema’s externalDocs;
a struct field β the property’s externalDocs. On a $ref’d field (whose
property is a bare $ref) it is lifted onto the wrapping allOf compound,
alongside the field’s description and x-* siblings.
An empty block (no description/url) is skipped rather than emitting a bare
externalDocs: {}. It is a full-Schema-only keyword: on a SimpleSchema site
(a non-body parameter, response header, or items chain) it drops with a
CodeUnsupportedInSimpleSchema diagnostic.
Like extensions, this keyword is cross-cutting; it is
documented here as its home.
Appendix: shapes & contexts
The two reference tables behind the keyword class pages: the value shapes the
lexer assigns to a keyword’s value, and the context tokens used in each page’s
scoped summary table.
Value shapes
The grammar’s lexer classifies every value into one of these shapes. The shape
determines which Walker callback fires for the property and which field of
Property.Typed carries the parsed value.
Shape
Typed payload
Example value forms
number
float64 (with optional </<=/>/>=/= prefix)
5, 1.5, <10, >=0, =42
integer
int64
5, 100
boolean
bool
true, false, 1, 0
string
raw string
^[a-z]+$, date-time, multipart/form-data
comma-list
raw string; split on , by Property.AsList()
http, https, a,b,c
enum-option
typed string (closed-vocab match)
csv, pipes for collectionFormat:
raw-block
accumulated body lines on Property.Body
multi-line YAML, indented token lists
raw-value
the verbatim post-colon text on Property.Value
42, "orange", [1, 2, 3]
When typing fails (e.g. maximum: notanumber) the lexer emits a
CodeInvalidNumber / CodeInvalidInteger / CodeInvalidBoolean diagnostic and the
property reaches the Walker with a zero-value payload. Consumers gate on
Property.IsTyped() to skip malformed-typed values; the corresponding builder field
stays unwritten.
Annotation contexts
The closed set of contexts a keyword can legally appear in. Each class page’s scoped
summary table combines these in its Contexts column.
Context
Meaning
param
Parameter doc on a swagger:parameters struct field, or a + name: chunk inside swagger:route Parameters:
header
Header field on a swagger:response struct
schema
Top-level model or struct field on a swagger:model
items
Items-level (array element) validation on either parameter or schema
route
Route-level metadata under swagger:route
operation
Inline operation metadata under swagger:operation
meta
Package-level metadata under swagger:meta
response
Response-level decorations
Using a keyword outside its legal contexts emits a CodeContextInvalid diagnostic
and the keyword is dropped from the affected block. The
Context matrix maps these tokens onto the
annotation families.
Sub-languages
The annotation body grammar is not a single language β it’s a
top-level keyword grammar that embeds several smaller languages
inside specific body keywords. Each embedded language has its own
shape rules.
This document catalogs the embedded languages and how they fit
together. For the per-keyword surface, see
keywords.md; for the formal grammar that hosts
them, see grammar.md.
Comment lines that don’t match any keyword head OR YAML fence OR
annotation marker are classified as prose β free-form text. The
lexer splits prose into two token kinds:
TITLE β the first paragraph of prose, expected to fit on a
short summary line.
DESC β every prose paragraph after the title (or following a
blank line within the first paragraph).
Three heuristics decide the title-vs-desc boundary, evaluated in
order. The first to fire wins:
Blank-line split. Any blank line inside the prose run ends
the title paragraph and starts the description.
Closing punctuation. If the first prose line ends with
Unicode punctuation (., ?, !, β¦, :, β¦), the title is
just that one line; everything after becomes description.
Markdown ATX heading. If the first prose line matches
markdown’s # Heading shape, the # markers are stripped and
the remaining text becomes the title.
When no heuristic fires, the entire prose run is title (the schema
builder later collapses to a description-only schema when
appropriate).
Package <name> prefix strip
The swagger:meta annotation’s title comes from the package doc
comment, which by Go convention starts with Package <name>. The
spec builder strips that prefix before publishing:
// Package petstore Petstore API.//
// Description of the petstore service.//
// swagger:metapackagepetstore
Produces info.title = "Petstore API." (the Package petstore
prefix stripped) and info.description = "Description of the petstore service."
Only the capital-P Package form is recognised β author prose like
“package this carefully” is not chopped.
/* swagger:route POST /pets pets createPet
Create a pet based on the parameters.
Consumes:
- application/json
*/funcCreatePet() {}
The lexer strips the leading whitespace (\t, *, /, |) per
line via trimContentPrefix before
classification.
Tool-directive markers are dropped
Two families of non-swagger directive lines are filtered out of the prose
surface, so they never leak into a title or description:
Go directives β //go:generate, //nolint:foo, //lint:ignore
(a lowercase word + : + an immediate argument, no leading space).
Kubernetes-style +marker comments β any line whose content begins
with + immediately followed by a letter: +kubebuilder:β¦,
+genclient, +k8s:β¦, +optional, as emitted by
kubebuilder / controller-gen. Requiring a letter after the + keeps
ordinary prose (+1 for β¦) intact. This is also why a stray
+kubebuilder:default:=false no longer crashes the scan
(go-swagger#3007)
β the marker is dropped, not parsed as a keyword.
The +marker filter runs at the prose-classification stage (after
annotation bodies are folded), so the swagger:routeParameters:+ name: chunk separator β a + followed by a space β is unaffected.
Markdown semantics that survive
Bullet lists in descriptions are preserved. A line starting with
- foo lands in the description as "- foo" (not "foo"). A
markdown-style * foo or + foo bullet is recognised the same way and
normalised to - foo (the same rewrite gofmt applies), so the two
forms agree.
Body keywords that publish a flat list of tokens (schemes:,
consumes:, produces:) accept multiple surface forms uniformly.
The unified reader is Property.AsList().
Accepted forms
# Inline, comma-separated
Schemes: http, https
# Multi-line, indented bare lines
Schemes:
http
https
# Multi-line, YAML-style dash markers
Schemes:
- http
- https
# Inline value plus indented continuation
Schemes: http
- https
# All combinations of the above
Consumes: application/json, application/xml
- application/protobuf
All five forms produce the same ["http", "https"] (or
["application/json", "application/xml", "application/protobuf"])
output. The - marker may also be written as a markdown * or +
bullet β all are normalised to the same list.
Algorithm
For each input line β Property.Value first (if non-empty), then
each line of Property.Body:
Trim surrounding whitespace.
Drop a leading - YAML marker if present.
Re-trim whitespace.
Comma-split.
Trim each token; drop empties.
Aggregate into a single slice in source order.
What flex-list does NOT touch
Enum values (enum: ...) β their elements may themselves be
complex (JSON arrays, quoted strings with commas). enum: keeps
its raw-value path; the value coercion layer handles array /
comma-list / multi-line shapes per the schema type.
Parameters chunks β the + name: chunk grammar is not a
simple token list; see Β§parameters.
YAML structural bodies β securityDefinitions:,
extensions:, infoExtensions: parse the body as YAML directly;
their structure isn’t a flat list. See
Β§yaml-extensions.
Parameters
The Parameters: body in swagger:route and swagger:operation
carries a sequence of parameter declarations separated by + name:
chunks (the + is the chunk-start sigil; - is accepted as an
alias for forward compatibility with proper YAML).
Chunk shape
Parameters:
+ name: id
in: path
type: integer
description: the item identifier
required: true
+ name: limit
in: query
type: integer
minimum: 1
maximum: 100
default: 20
+ name: body
in: body
type: User
required: true
Per-chunk fields
The fields are classified into head fields (consumed by the
orchestrator to populate the *spec.Parameter shell) and
validation fields (lowered to grammar properties and dispatched
through the standard validation pipeline).
Head fields:
Field
Lands on
Notes
name:
parameter.name
Required. Identifies the parameter.
in:
parameter.in
One of path / query / header / body / formData. form accepted as an alias for formData.
type:
parameter.type (for SimpleSchema) or determines the body $ref
For non-body: one of string / integer / number / boolean / array. For body: a Go ident referring to a swagger:model-declared type, optionally with [] array prefixes ([][]Pet). bool accepted as an alias for boolean.
format:
parameter.format or parameter.schema.format
Free-form string. Applied after validation dispatch so it doesn’t interfere with default/example coercion.
description:
parameter.description
Free-form prose.
required:
parameter.required
Boolean.
allowempty: / allowemptyvalue:
parameter.allowEmptyValue
Boolean.
Validation fields: any other recognised
keyword β min, max, minLength, maxLength,
minItems, maxItems, pattern, unique, collectionFormat,
default, example, enum. These are looked up via
grammar.Lookup (which accepts canonical names + aliases) and
dispatched through the standard handlers seam.
Empty chunks and unknown keys
A bare + (or -) sigil with no follow-up content emits a
CodeInvalidAnnotation diagnostic and is dropped. The legacy
parser silently emitted an empty Parameter{} object β current
behaviour rejects it.
Unknown keys (typos like defualt:) emit
CodeInvalidAnnotation and drop. The legacy parser silently
discarded them.
Body parameters
When in: body, the orchestrator looks up type: as either:
A primitive (string, integer, number, boolean, array,
object) β emits a typed schema with the primitive on
parameter.schema.type.
A Go ident β emits a $ref to #/definitions/<Ident>. With []
prefixes, wraps the ref in nested array schemas.
Validation properties on a body chunk apply to the schema, gated by
the schema’s resolved type via checkShape. A min: 0 on a body
chunk with type: Pet (object) emits CodeShapeMismatch and drops;
a min: 0 with type: integer lands on the schema’s minimum.
Validation on SimpleSchema (non-body) parameters
For in: other than body, validation properties apply directly to
the parameter (not to a sub-schema). Type-gating still applies:
minLength on type: integer emits a diagnostic and drops.
Responses
The Responses: body in swagger:route carries one response
declaration per line. Each line has the shape:
<code>: <token>*
where <code> is default (case-insensitive) or a decimal HTTP
status code, and <token> is either a tag:value form or an
untagged token.
Recognised tags
Tag
Value shape
Lands on
body:
A scalar primitive (string / number / integer / boolean) OR a Go ident, each with optional [] prefixes (body:[]string, body:[]Pet)
A primitive emits a typed schema; a Go ident emits a $ref to #/definitions/<name> β array-wrapped per [] count. The reserved keywords array / object / file / null are rejected with a diagnostic (use []T or a model name)
response:
Go ident referring to a swagger:response-declared type
A $ref to #/responses/<name>
description:
Free-form prose (rest of line)
response.description
Untagged token rules
The first untagged token defaults to a response ref. The
orchestrator resolves it against the operation’s responses map
first, then falls back to definitions β if found in definitions
(not responses), it’s silently promoted to a body ref. An untagged
token is always read as a NAME, never a type: a bare 200: string
is a (dangling) response ref, not a primitive body β use the
unambiguous body:string form for a primitive body.
Subsequent untagged tokens accumulate into the description.
This block is a line-based sub-language, not YAML: each entry is a single
<code>: <token>* line. A description must sit on that same line β either via
the description: tag (403: description: Unauthorized) or as trailing
untagged tokens (200: listResponse all the users). A nesteddescription:
written on an indented continuation line under a bare 403: is not parsed
and yields an empty description; for that style, spell the operation out with a
swagger:operation YAML body instead.
Examples
Responses:
200: User the user as returned # untagged β response="User", desc="the user as returned"
200: body:string the version # primitive body (use the body: tag) + description
200: body:[]integer the id list # array-of-primitive body
200: body:User the user # body ref + description
200: response:userResponse the user # named response ref
201: body:Pet the created pet
404: description: not found
default: response:genericError
default: body:[]ErrorList the error list # array-wrapped body ref
Diagnostics
Unknown tag (200: weird:value) β emits
CodeInvalidAnnotation and drops the line.
Duplicate body/response tags on one line
(200: body:Pet response:errors) β emits
CodeInvalidAnnotation; the line drops.
Space-separated body Foo (instead of body:Foo) β detected
as a likely typo and dropped with diagnostic. The legacy parser
silently treated it as response="body" (a dangling ref to a
non-existent response).
Unresolvable response ref β when a response name appears in
neither responses nor definitions, the line drops with
diagnostic. The legacy parser emitted a dangling $ref. When the
unresolved name is a primitive type spelling (200: string), the
diagnostic points the author at the body: form (200: body:string).
Reserved body: type (200: body:object, body:file,
body:array, body:null) β these look like a type but are not valid
response body types; the line drops with a diagnostic suggesting a
scalar primitive, []T, or a model name.
Empty value lines
A line like 204: with nothing after the colon produces a Response
with the code and an empty description. This is intentional β some
authors want a 204 No Content with no body and no description.
YAML extensions
Several body keywords parse their body as YAML directly:
extensions: and infoExtensions: β a YAML map of x-* entries.
securityDefinitions: β a YAML map matching OAS v2’s
securityDefinitions shape.
externalDocs: β a YAML map with description and url keys.
Extension typing
Extension values are NOT coerced to strings β they preserve their
YAML-typed form: bool, float64, string, []any, or
map[string]any for nested structures.
Keys that don’t start with x- or X- emit a
CodeInvalidAnnotation diagnostic and drop. The build still
succeeds. Authors who relied on the legacy “hard error on non-x-*”
behaviour see a diagnostic + a clean spec missing the typo’d key.
The YAML extension bodies use indentation to delimit. A line
that returns to the indentation level of the keyword head β or
introduces a sibling keyword β terminates the body. The grammar
also recognises --- fence pairs around the body (matching the
swagger:operation YAML shape) and absorbs them silently.
Security requirements
The security: body (in swagger:meta, swagger:route, and
swagger:operation) carries OAuth-style security requirements
where each line is one requirement.
Shape
Each line: schemeName: scope1, scope2, β¦
schemeName matches a scheme declared in
securityDefinitions.
Scope list is comma-separated; trimmed; empties dropped.
An empty scope list (schemeName:) means “this scheme is
required, no scopes.” Common for apiKey and basic.
Each requirement is a single-key map; the array is an OR
relationship (the request satisfies security if it matches ANY
entry).
Contact / License
Inline single-line meta keywords with structured value
parsing.
Contact
The contact: value carries up to three components: name, email,
URL. Recognised forms:
Contact: Name <email@example.com> https://example.com
Contact: Name <email@example.com>
Contact: https://example.com
Contact: <email@example.com>
The grammar splits the value on the first URL prefix it finds
(https://, http://, ftps://, ftp://, wss://, ws://),
then parses the prefix portion as Name <email> via Go’s
net/mail.ParseAddress.
A malformed Name <email> head (e.g., unbalanced angle
brackets) surfaces as an error from Block.Contact(); the
meta builder propagates it as a build failure.
An empty contact line produces an empty Contact value (no
error, no diagnostic β equivalent to omitting the keyword).
Aliases: contact info, contact-info.
License
The license: value is split similarly:
License: Apache 2.0 https://www.apache.org/licenses/LICENSE-2.0
License: MIT
License: https://opensource.org/licenses/Custom
Same URL-prefix detection. Everything before the URL is the
license name; the URL (when present) is the license URL.
Either part may be empty.
License does NOT use mail.ParseAddress β the name is taken as
raw text up to the URL boundary.
Sub-language interactions
Two interaction points worth flagging:
Block-comment continuation lines and the
parameters/responses sub-languages. A
/* swagger:route β¦ */ block with Parameters: inside requires
the chunk-start sigils (+ / - ) to be at the start of the
trimmed line. Block-comment continuation noise (\t, *) is
stripped first; if your editor inserts a * continuation marker,
the lexer handles it transparently.
Flex-list and description: on a parameter chunk.
description: is a head field, not a list β it does NOT comma-split.
Authors who write description: foo, bar get a single description
"foo, bar", not two descriptions. (This was a real ambiguity
in older versions of go-swagger; the current grammar resolves
it cleanly.)
Grammar
The formal grammar of the codescan annotation surface. This document
specifies the language a Go comment must conform to so that the
scanner classifies it, dispatches it to the right builder, and
populates the OpenAPI spec deterministically.
Audience. Implementers β anyone porting, extending, or debugging
the parser. Annotation authors typically need
annotations.md and keywords.md
instead.
The grammar is layered:
Preprocess β comment-marker stripping (see
Β§preprocess).
Lex β terminal token emission, including multi-line body
accumulation (see Β§lexer).
Parse β block construction, family dispatch, keyword
classification (see Β§parser).
Walk β typed dispatch through grammar.Walker callbacks to
the builders (see Β§walker).
The productions below operate on the lexer’s terminal alphabet,
not raw text. Per-terminal lexical detail (how the lexer recognises
a number, a string, an annotation, β¦) is described in Β§lexer; the
EBNF that follows consumes pre-classified terminals.
The grammar is rigorous ISO-14977 EBNF. Required vs. optional
arguments, value typing, and family membership are
grammar-visible β every legality constraint expressible by token
sequencing is expressed that way.
Input is a *ast.CommentGroup from go/parser. Each *ast.Comment
in the group is one source-level comment node (either // β¦ or
/* β¦ */). The preprocessor produces a flat sequence of Line
structs, each one source line with:
Line.Text β content after comment-marker stripping and
leading content-prefix trim.
Line.Raw β content after comment-marker stripping only
(preserves leading whitespace).
Line.Pos β token.Position of the first content byte.
Stripping rules:
For // comments: drop the // marker. Line.Text runs
trimContentPrefix (strips leading \t*/|); Line.Raw keeps
the post-marker spacing.
For /* */ block comments: split body on newlines.
First line: drop the /* marker.
Continuation lines: run stripBlockContinuation (strips
leading whitespace + optional * continuation marker + one
following space), then trimContentPrefix.
Last line: drop the trailing */.
trimContentPrefix strips \t*/ and a single trailing | from
the line head. It does NOT strip - (so YAML list markers and
markdown dash items survive intact).
For synthetic per-line comments produced by upstream tooling
(notably parsers.ParseRoutePathAnnotation), a // prefix is
prepended before stripping so the // branch fires and the leading
whitespace gets shed correctly.
Lexer
The lexer turns a []Line into a []Token ending in TokenEOF.
Pipeline:
Line classifier β emit one preliminary token per line
(annotation / keyword / fence / blank / text).
Body accumulator β fold multi-line bodies (OPAQUE_YAML,
RAW_BLOCK_, RAW_VALUE_) into single body tokens.
Prose classifier β re-type surviving text tokens as
TokenTitle / TokenDesc.
Terminal vocabulary
Annotation name terminals (TokenAnnotation)
Each recognises an annotation name only β positional arguments
are emitted as separate terminals.
Terminal
Annotation
ANN_MODEL
swagger:model
ANN_RESPONSE
swagger:response
ANN_PARAMETERS
swagger:parameters
ANN_ROUTE
swagger:route
ANN_OPERATION
swagger:operation
ANN_META
swagger:meta
ANN_STRFMT
swagger:strfmt
ANN_ALIAS
swagger:alias
ANN_NAME
swagger:name
ANN_ALLOF
swagger:allOf
ANN_ENUM
swagger:enum
ANN_IGNORE
swagger:ignore
ANN_DEFAULT
swagger:default
ANN_TYPE
swagger:type
ANN_ADDITIONAL_PROPERTIES
swagger:additionalProperties
ANN_PATTERN_PROPERTIES
swagger:patternProperties
ANN_FILE
swagger:file
ANN_TITLE
swagger:title
ANN_DESCRIPTION
swagger:description
Argument terminals
Terminal
Recognises
IDENT_NAME
Identifier-shaped token. Used for every named arg and reference.
GET / POST / PUT / PATCH / HEAD / DELETE / OPTIONS / TRACE (case-insensitive).
URL_PATH
RFC-3986 URL path token (used as the second positional arg of OperationArgs).
Keyword head terminals (TokenKeyword)
Each recognises the keyword name only. See
keywords.md for the complete keyword surface.
Inline value terminals
The lexer types values per their lexical shape; semantic coercion
against the Go target happens in the analyzer.
Terminal
Recognises
NUMBER_VALUE
Signed decimal literal (integer or fractional).
INT_VALUE
Unsigned decimal integer.
BOOL_VALUE
true / false (case-insensitive).
STRING_VALUE
Verbatim non-LF text.
COMMA_LIST_VALUE
Comma-separated list of strings, trim-stripped.
ENUM_OPTION_VALUE
One of a closed token set declared per keyword (query/path/β¦ for in:, csv/ssv/β¦ for collectionFormat).
When the lexer fails to type a value against its keyword’s expected
shape, the property reaches the analyzer with Property.Typed.Type == ShapeNone and a CodeInvalidNumber / CodeInvalidInteger /
CodeInvalidBoolean diagnostic is emitted.
Multi-line body terminals
Single tokens spanning multiple source lines. The lexer absorbs the
head and the body lines.
A raw-block / raw-value keyword opens a body. The body terminates at
the next sibling structural token in the same family β either
another TokenAnnotation, another body-keyword head whose context
makes it a sibling, or TokenEOF.
Blank lines do NOT terminate the body. They are absorbed as visual
separators inside list-shaped bodies.
For raw-block heads, the inline post-colon value (when non-empty)
is prepended to the body as its first line. This means
Consumes: application/json (inline single value) and
Consumes:\n - application/json (multi-line body) both yield the
same body content; consumers don’t need to special-case the inline
form.
YAML fence handling
A line whose trimmed content is exactly --- opens (or closes) a
YAML fence. While the cursor sits between matching fences:
Annotation and keyword recognition is suspended; every line emits
as tokenRawLine carrying the verbatim source text.
The body accumulator captures the fenced region as a single
OPAQUE_YAML token attached to the surrounding annotation
(typically swagger:operation or a fenced extensions body).
A missing closing fence emits a CodeUnterminatedFence
diagnostic; the OPAQUE_YAML token is marked truncated and the
builder degrades gracefully.
Prose classification
Surviving tokenText tokens (not consumed by a body, not an
annotation or keyword head) re-type as either TokenTitle or
TokenDesc per three heuristics evaluated in order:
Blank-line split β a blank line inside the prose run ends
the title and starts the description.
Closing punctuation β if the first prose line ends with
Unicode punctuation, the title is just that one line.
Markdown ATX heading β if the first prose line matches
markdown’s # Heading shape, the # markers are stripped and
the line becomes the title.
When no heuristic fires, the entire prose run is title.
The parser consumes the lexer’s terminal stream and produces typed
Block values, one per *ast.CommentGroup. A single comment group
may produce MORE than one Block when multiple annotations appear
(each annotation closes the preceding Block and opens a fresh one).
The dispatcher reads the first ANN_* terminal; its identity
selects the family. If no annotation appears, the input is an
UnboundBlock β typically a Go struct field with description-only
documentation.
Block.AnnotationKind() returns the family discriminator.
Block.AnnotationArg() returns the leading IDENT argument (if any)
without requiring the caller to type-assert on the typed Block
kind.
Schema family
Bodies of swagger:model, swagger:parameters, swagger:response,
swagger:name.
swagger:title / swagger:description are schema-family overrides β they
replace the godoc-derived title / description on a model, field, response, or
header. They dispatch through the schema parser (not the classifier parser), so
validation keywords co-located on the same comment group still surface. The
RAW_VALUE is the rest of the head line; swagger:description additionally folds
a blank-terminated body (Option B) or, with a trailing |, a verbatim literal
markdown block. A blank override emits CodeEmptyOverride; swagger:title is
rejected with CodeContextInvalid on a non-body parameter or response header.
Operation family
swagger:route and swagger:operation are distinct block productions
because their bodies differ structurally β swagger:route accepts
the structured keyword surface; swagger:operation accepts an
OPAQUE_YAML body.
OperationFamilyBlock =RouteBlock | InlineOperationBlock ;
RouteBlock =ANN_ROUTE , OperationArgs , [ Title ]
, [ Description ]
, RouteBody ;
InlineOperationBlock =ANN_OPERATION , OperationArgs , [ Title ]
, [ Description ]
, InlineOperationBody ;
OperationArgs =HTTP_METHOD , URL_PATH , { IDENT_NAME } , IDENT_NAME ;
(* Trailing IDENT_NAME is the OperationID;
the run between URL_PATH and the OpID is
the tag list. *)RouteBody = { CommonOperationBodyItem | BLANK } ;
InlineOperationBody = { CommonOperationBodyItem | OPAQUE_YAML | BLANK } ;
CommonOperationBodyItem =OperationKeyword | OperationDecorator | OperationRawBlock | ExtensionsBlock | ExternalDocsBlock ;
OperationKeyword =KW_SCHEMES , COMMA_LIST_VALUE ;
OperationDecorator =DeprecatedLine ;
OperationRawBlock =RAW_BLOCK_CONSUMES | RAW_BLOCK_PRODUCES | RAW_BLOCK_SECURITY | RAW_BLOCK_RESPONSES | RAW_BLOCK_PARAMETERS ;
β¦where both share the header arguments:
The <GoIdent> swagger:route ... godoc-prefix exception (which
allows a leading Go identifier on the route annotation line) is
absorbed by the lexer; the EBNF sees a plain ANN_ROUTE.
Classifiers are stateless markers β they carry no validation body
of their own. The surrounding declaration’s other annotations (or
the absence thereof) determine where the classification lands.
Cross-cutting productions
These appear in multiple families and share a single production.
Vendor extensions (ExtensionsBlock, InfoExtensionsBlock) accept
YAML map bodies; non-x-* keys emit CodeInvalidAnnotation and
drop. The lexer additionally surfaces them via
Block.Extensions() with an Extension.Source discriminator
(KwExtensions vs KwInfoExtensions) so consumers can route to
the correct spec field (spec.extensions vs info.extensions).
Walker
Block.Walk(grammar.Walker{...}) dispatches Properties through
typed callbacks. The Walker maps a Property to a callback by
Keyword.Shape:
Shape
Callback
Payload
ShapeNumber
Number
(p, float64, exclusive bool)
ShapeInt
Integer
(p, int64)
ShapeBool
Bool
(p, bool)
ShapeString
String
(p, string) β value on p.Value
ShapeEnumOption
String
(p, string) β closed-vocab token on p.Typed.String
ShapeRawBlock
Raw
(p) β caller reads p.Body / p.Raw
ShapeRawValue
Raw
(p)
ShapeCommaList
Raw
(p) β caller splits via Property.AsList
ShapeNone (failed typing)
Raw
(p) β diagnostic fired separately
Additional callbacks fire outside the per-Property dispatch:
Title(s string) β once, before any property, if non-empty.
Description(s string) β once, before any property, if non-empty.
Extension(ext grammar.Extension) β once per typed extension.
Diagnostic(d grammar.Diagnostic) β block-level diagnostics fire
before Title; per-property diagnostics fire immediately before
the property’s main callback.
Walker.FilterDepth gates property callbacks by Property.ItemsDepth.
Pass 0 for level-0 properties (default); pass N for items-level
N; pass AllDepths (-1) for every depth.
Keyword applied to a schema type that doesn’t accept it (e.g. minLength on a number)
CodeContextInvalid
Warning
Keyword used outside its legal annotation context
CodeUnsupportedInSimpleSchema
Warning
Full-schema-only keyword used in SimpleSchema (non-body param, header)
CodeInvalidYAMLExtensions
Warning
YAML parse failed inside an extensions body
CodeUnterminatedFence
Warning
YAML fence opened but not closed before EOF
All diagnostics drop the offending property / annotation /
extension and continue the build. The accumulator on
common.Builder collects them in source order; the consumer’s
OnDiagnostic callback (if wired) fires inline.
What this grammar does not describe
The grammar’s job ends at producing typed Property and Block
values. The analyzer (builders / spec orchestrator) owns:
Type coercion β default: 1.5 against an integer schema is
a lexical success and an analyzer rejection. validations.CoerceValue
and validations.ParseDefault apply the schema-type-aware
coercion at write time.
Cross-reference resolution β $ref targets, alias-chain
resolution, post-decl discovery. The grammar emits the names; the
analyzer resolves them.
Schema-shape gating β validations.IsLegalForType decides
whether minLength applies to the resolved schema type. The
grammar always emits the property; the handler dispatch decides
whether to write it.
Ordering & merging across multiple comment groups β when
several swagger:parameters Foo Bar Baz declarations contribute
to the same operation, the spec builder merges them.
The grammar is also deliberately single-pass β it never
revisits a *ast.CommentGroup after Parse(cg) returns. The
common.Builder blockCache memoises results across the analyzer’s
recursive type descent (see
common README Β§blockcache).
The commands
Three commands ship from this repository β genspec,
genspec-tui and genspec-wasi β and they run
the same scan. This page is about how they are arranged, which matters to anyone
adding an option, adding a command, or wondering why the library stays as light
as it does.
The split is about dependencies, not about features.
Command
Module
Because
genspec-wasi
the main module
it must add nothing to what the library already needs β that is what lets it cross-compile to wasip1/wasm and run with no toolchain and no subprocess
genspec
its own module
it takes koanf, for the configuration sources it will be asked for next
genspec-tui
its own module
it takes bubbletea and the tree that comes with it
So installing the terminal UI pulls none of bubbletea into your project, and a
program importing the library gets neither. A command that needed a dependency
the library does not want is a reason to give it a module, not a reason to argue
about the dependency.
Note
The submodules are tagged and have no replace directive, so
go install github.com/go-openapi/codescan/cmd/genspec-tui@latest really works
from a clean environment.
One flag surface, declared once
Every knob the library takes is declared as a flag in cmd/internal/cliopts,
once, and each command registers the whole of it. That is what makes
-name-from-tags mean the same thing whichever one you reach for, and what makes
a knob added to the library reachable from all three at the same moment.
Written per command instead, the mapping would be partial in a different way each
time, and an option added to Options would reach whichever command somebody
remembered.
Three properties hold it together:
Entries are keyed by the field’s setter, never by a name derived from the
field. Naming the field in a string would let a rename pass the compiler and
leave the flag writing nowhere.
A guard in the package tests fails when a value-typed option lands with no
flag β or no recorded excuse. A caller cannot use an option that has no flag,
and would find out by meeting flag provided but not defined after writing
something against a surface that was never there. The pull request that adds
the option is a better place to find out.
A flag is the kebab-case of the field, without exception. No rule can derive
that JSONify is one word where HTTPServer is two, so coverage is decided by
writing through a setter and seeing what moved β never by mangling a name.
The package sits under cmd/ rather than under the root internal/, so the
commands that are modules of their own can import it.
Options that are not values β a filesystem, a document to merge into, the
callbacks β are deliberately absent from the table. Those are the command’s
business, and the tests carry the list with a reason for each. See
What has no flag.
One configuration contract, two readers
cmd/internal/cliconf owns where a .codescan.yaml is found, what may be in
it, and how it loses to anything typed β and nothing else. The values themselves
arrive as a plain flat map, so how they are read stays the command’s business:
genspec feeds the file through koanf, for the environment variables and
further formats it will be asked for next;
genspec-tui β which reads one file, once, to decide what a session starts
with β calls cliconf.Parse, which needs nothing this repository does not
already have.
That seam is why the package can be shared with a command that must
cross-compile to WebAssembly: cliconf.YAML satisfies koanf’s parser interface
structurally, so the package owes koanf no import.
Sections come from cliopts.ConfigSchema() merged with each command’s own, which
is why a section one command does not recognize is skipped rather than rejected, and a
key inside a section it does know must name one of its flags.
Everything a file sets lands through flag.FlagSet.Set β the same path the
command line takes β so a value is parsed and validated exactly once, and a file
cannot express anything an argument could not.
See also
Setting options β the same contract from
the reader’s side.
codescan was extracted from go-swagger
and has been reworked twice since, in ways that turned out to matter for what a
scan costs. This page is the short version of the evidence: three points in time,
then the loader options available today, measured on one corpus.
The full method, both corpora and the raw tables live in
internal/benchmarks.
The corpus
Everything below scans kubeapi: an API server stub that go-swagger generates
from the kubernetes API manifest β not kubernetes itself, and not code that ever
runs. It is here because of its shape: 2352 Go files, ~344k lines, and an
annotated surface of 222 model definitions and 260 route/operation blocks,
which is as much work as an annotation parser is ever asked to do.
Every figure on this page comes from a scan that emitted the same 222
definitions and 260 paths. A configuration that got faster by scanning less
would not be an improvement, so the emitted document is checked alongside the
cost.
Three points in time
Versions
v0.33.3 is roughly the state the code was extracted from go-swagger in.
v0.35.1 is the first debugged release of the grammar-based annotation parser.
current is master in its default configuration.
xychart-beta
title "Time for one scan of kubeapi (warm build cache, seconds)"
x-axis ["v0.33.3", "v0.35.1", "current"]
y-axis "seconds" 0 --> 8
bar [7.087, 1.758, 1.506]
xychart-beta
title "Memory: total allocated (bars) and peak RSS (line), MB"
x-axis ["v0.33.3", "v0.35.1", "current"]
y-axis "MB" 0 --> 4800
bar [4555, 1217, 1215]
line [1154, 807, 751]
End to end, out of the box: 4.7Γ faster and 3.7Γ less allocated, for the
identical document. Either of the loader options in the next section takes it
further β compiled dependencies reach 0.970 s and 447 MB, which is 7.3Γ faster
and 10Γ less allocated than v0.33.3.
All three bars are the same configuration, so the series measures six months of
work and nothing else. That matters because the largest single step available
today is not on this chart at all: it is a flag, and mixing it in here would
credit the calendar for it.
The two charts do not tell the same story, and the difference is the interesting
part. The step from v0.33.3 to v0.35.1 removes 3.3 GB of allocation but only
346 MB of peak β the old regexp-based parser allocated and discarded, so the
high-water mark stayed near the floor that the loaded package graph sets. Nothing
since has moved that floor by default (β56 MB). Asking for a different loader
moves it, and is worth β395 to β501 MB.
What each step changed
v0.33.3 β v0.35.1 β the annotation parser. The
grammar replaced a regexp engine. That was a
correctness, diagnostics and completeness project, not a performance one, and the
speed is a byproduct of dropping Go’s regexp from the hot path. It is worth
knowing how large the byproduct is on route-heavy code: scanning only this
corpus’s models β nothing to emit β the two versions allocate the same to within
0.5 MB, while scanning the routes goes from 4476 MB to 1216 MB. The whole gain is
in reading annotations, and it appears where route and operation bodies do.
v0.35.1 β current β the package loader. Resolving and type-checking the
package graph is the bulk of a scan, and it is now possible to ask for a loader
that reads less of it. In the default configuration this stretch bought speed
only β a quarter-second on both corpora, allocation-neutral to within 2 MB. The
memory is all in the asking. That is the next section.
How each loader option fares
Three ways to get the package graph, all producing the same document. They
differ in what they read and what they hold.
Note the axes: the two charts below are the same three configurations, timed on
a warm build cache and on an empty one. Only the scale differs β and it differs
by an order of magnitude.
xychart-beta
title "Time for one scan, WARM build cache (seconds)"
x-axis ["source deps", "pure-Go", "compiled deps"]
y-axis "seconds" 0 --> 2.5
bar [1.506, 1.331, 0.970]
xychart-beta
title "Time for one scan, COLD build cache (seconds)"
x-axis ["source deps", "pure-Go", "compiled deps"]
y-axis "seconds" 0 --> 15
bar [2.208, 1.359, 14.511]
xychart-beta
title "Memory: total allocated (bars) and peak RSS (line), MB"
x-axis ["source deps", "pure-Go", "compiled deps"]
y-axis "MB" 0 --> 1300
bar [1215, 643, 447]
line [751, 412, 306]
configuration
option
warm
cold
allocated
peak RSS
build cache it writes
source dependencies
the default
1.506 s
2.208 s
1215 MB
751 MB
7.7 MB
pure-Go loader
ToolchainFreeLoader
1.331 s
1.359 s
643 MB
412 MB
4 KB
compiled dependencies
CompiledDependencies
0.970 s
14.511 s
447 MB
306 MB
231 MB
No configuration wins both cache states, which is the whole reason there is a
choice to make:
Compiled dependencies take dependency types from the compiler instead of
reading their source β 31 packages read from source rather than 296. Fastest and
smallest by a wide margin on a warm cache. On a cold one it must compile the
closure before it can read it, so it is more than 6Γ slower than reading
source, and it writes 231 MB of build cache. Opt in where the cache is warm by
construction; it is off by default because a CI job regenerating a spec from a
clean checkout is not.
The pure-Go loader never invokes the go command, so there is no metadata to
populate and nothing to compile: it writes 4 KB of build cache and its cold
time equals its warm time. It is the only choice whose cost is predictable,
and it holds about 45% less memory than the standard loader.
Which to reach for, and what each one gives up, is on the
Options reference.
Reading these numbers
They are indicative, not a promise. Measured on one machine (Ryzen 7 5800X,
31 GB, go1.26.5, Linux), three warm rounds and a single cold one β cold being a
single-shot state by definition.
Two cautions carry over to any corpus of your own:
The two gains scale with different things. The loader acts on the
dependency closure, so its win is roughly a fixed amount per project; the
parser acts on the annotated surface, so its win is large on a route-heavy
server and small on a client carrying annotations only on its models. On a
smaller tree the loader’s share of the total looks much bigger than it does
here.
Wall clock needs an idle machine. Under load these timings move by 3Γ while
the allocation and peak-RSS figures reproduce to four digits. Distrust a timing
difference under 10%; nothing concluded above rests on one.
To measure your own tree, the harness takes it as an extra corpus alongside the
two it ships with β see its
README.