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.
Learn codescan by spec concept — model definitions, routes and operations,
validations, examples, and document metadata — each shown as annotated Go
next to the Swagger it produces.
How-to guides for the knobs that change how the same Go source renders into
the spec — grouped by what they shape: scope & discovery, names & $refs,
titles & descriptions, field types & formats, and response bodies.
The complete, normative reference for the codescan annotation language —
every annotation, every keyword, the embedded sub-languages, and the formal
grammar the parser implements.
Subsections of go-openapi codescan
Usage
This section covers how to use codescan in practice.
The annotation vocabulary, keyword reference and the formal grammar the
scanner parses.
Subsections of Usage
Examples
The code shown on these pages lives under
docs/examples
as a separate Go module. Snippets are injected via the code Hugo shortcode,
so what you read here is exactly what CI compiles and tests.
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.
Reference
codescan parses a small annotation language layered on top of Go doc comments.
The reference material currently lives alongside the source in
docs/:
Annotations
— the full annotation vocabulary (swagger:meta, swagger:route,
swagger:model, swagger:parameters, swagger:response, …).
Keywords
— every keyword recognized inside annotation blocks and where it applies.
Grammar
— the formal grammar the parser implements.
Sub-languages
— the embedded YAML / simple-schema surfaces.
TODO (scaffold): these documents are good candidates to migrate into the
doc-site as first-class pages (one section per file), so they render with
navigation and search rather than linking out to GitHub.
Project
This section holds material specific to this repository:
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.
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, 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 ./...
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).
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).
A community 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. Fork, reuse, and adapt what you find useful. See the
go-swagger project’s “About” page for the wider toolkit
story.
Import codescan, annotate a package, and produce a Swagger 2.0 specification
from your Go program.
Today, codescan is used as a Go library (below). Additional usage modes will
appear here as siblings as the toolkit grows.
Subsections of Getting started
Usage as a library
The most direct way to use codescan is to import it and call Run from your
own Go program — a generator, a go:generate step, or a test that keeps your
spec in sync with the source.
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.
Options worth knowing
Field
Effect
Packages
Relative go list patterns to scan (e.g. ./...).
WorkDir
Directory the patterns resolve against.
ScanModels
Also emit definitions for swagger:model types.
InputSpec
Overlay: merge discoveries on top of an existing spec.
BuildTags, Include/Exclude
Scope control over what gets scanned.
RefAliases, TransparentAliases
Alias-handling knobs.
EmitRefSiblings
Emit a $ref’d field’s description and extensions as direct $ref siblings instead of an allOf wrap — see Descriptions beside a $ref.
SkipAllOfCompounding
Never wrap a $ref’d field in an allOf; emit a bare $ref and drop the decorations that need a compound — see Descriptions beside a $ref.
DescWithRef
Deprecated — preserve a description-only $ref field via a single-arm allOf; prefer EmitRefSiblings.
SkipExtensions
Suppress x-go-* vendor extensions.
SkipEnumDescriptions
Keep the swagger:enum const→value mapping out of property/parameter descriptions (it still rides x-go-enum-desc).
EmitXGoType
Stamp x-go-type (the fully-qualified Go type) on every definition — see Vendor extensions.
SingleLineCommentAsDescription
Route single-line comments to description instead of title/summary — see Single-line comments.
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 the
examples cannot drift from what the scanner actually emits.
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
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}.
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.
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 is a narrow, value-only classifier hint placed on a var or
const. It does not publish a spec entity of its own — it has no standalone
output — so most spec defaults are carried by the default: keyword above
rather than this annotation.
// DefaultPort is the fallback port used wherever Port is not supplied. The// swagger:default annotation is a narrow value-only discovery hint.//
// swagger:defaultvarDefaultPort = 8080
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`json:"sortKey" form:"sort_key"`// PageSize bounds the page length.PageSizeint`json:"pageSize" form:"page_size"`}
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 fixtures/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.
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.
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.
Nullable pointers
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.
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 the options that drive it. It is
written for people who want the full contract — annotation authors looking up an
exact rule, library callers looking up an option, and contributors porting,
extending, or debugging the parser.
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.
The formal ISO-14977 EBNF the parser implements, from comment preprocessing through the typed walker.
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.
Options — every field of codescan.Options:
its type, default, and effect, cross-linked to the how-to that shows it in
action. The library-caller reference.
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.
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
: dedicated CLI, independent from go-swagger
⬜ v0.37.0 (September 2026) : decouple from `Spec`
: go1.26+
: Internal model
: More go-swagger backlog fixes & tunable knobs
section Q4 2026
🔍 v0.38.x (Oct 2026) : LSP & IDE integration, playground UI (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.
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, swagger:default — bare
annotation, the surrounding decl supplies the entity name.
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
—
—
—
—
—
—
—
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.fixtures/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.
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.fixtures/enhancements/allof-edges/types.go.
swagger:default
Usage
// swagger:default
What it does
Marks the surrounding declaration as the spec’s default value for the
corresponding shape.
Used in narrow contexts where the scanner expects an explicit anchor for a
default. This annotation is value-only — there’s no exported entity it
publishes; it’s a classifier hint the scanner consumes during discovery.
Where it goes
On a value declaration (var, const) or a struct field.
Takes no argument — an optional title/description may follow on the
doc comment.
Supported keywords
None of its own. Most spec defaults are instead carried by the
default: keyword on the relevant
field; this annotation has a narrow surface and is not commonly authored
directly.
Example
swagger:default is value-only: it produces no definition, so there is no
emitted spec to render. The source below shows the narrow classifier-hint
form — in practice most defaults come from the
default: keyword on a field.
// DefaultPort is the fallback port used wherever Port is not supplied. The// swagger:default annotation is a narrow value-only discovery hint.//
// swagger:defaultvarDefaultPort = 8080
// 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"}
Marks a named type over a string, integer, number or boolean as an enum
and collects the type’s const declarations.
Values come from the Go type-checker, so any constant expression is
collected — iota (including the implicit specs, which carry neither a
type nor a value), computed members (1 << 3), references to earlier
members, negative values, every integer base, values above MaxInt64 in
an unsigned enum, rune literals (as code points), true / false, and
both string forms. The emitted type / format come from the declared
Go type, never from the members, so an int8 enum is
{integer, int8} and reordering the const block cannot change the type.
A type declared over another named type keeps that type’s format
(type Kind strfmt.UUID stays format: uuid).
Two shapes do not work: an alias to a basic type cannot host an enum (the
type-checker erases the alias, leaving nothing to collect), and a rune
or byte enum emits integers, which is what those types are on the wire.
See Enumerations.
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.fixtures/enhancements/enum-overrides/types.go.
swagger:file
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.fixtures/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.fixtures/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.fixtures/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. That is what makes it
read 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 is what stops it rotting silently when a field is renamed
upstream, 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.fixtures/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.fixtures/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"` }
}
Routes can then reference it via response:genericError in their
Responses: body.
Full example.fixtures/enhancements/routes-full-petstore-shape/handlers.go.
swagger:route
Usage
// swagger:route METHOD PATH [tag …] OPERATION_ID
What it does
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.fixtures/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.fixtures/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.fixtures/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 as an argument is rejected with a diagnostic — use
swagger:file.
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.)
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.
Options reference
codescan.Options
is the single configuration struct passed to
codescan.Run. 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. 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
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
Effect
Packages
[]string
nil
Package patterns to scan (e.g. ./...), resolved relative to WorkDir.
WorkDir
string
"" (cwd)
Working directory the package patterns and module resolution are rooted at.
BuildTags
string
""
Go build tags to activate while loading, so tag-guarded source is scanned. See Build tags.
Include
[]string
nil
Allow-list of package path patterns; when non-empty only matching packages are scanned. See Scoping the scan.
Exclude
[]string
nil
Deny-list of package path patterns, applied after Include. See Scoping the scan.
IncludeTags
[]string
nil
Allow-list filtering routes/operations by their swagger tags.
ExcludeTags
[]string
nil
Deny-list filtering routes/operations by their swagger tags.
ExcludeDeps
bool
false
Skip types reached through module dependencies, keeping the scan to first-party packages.
ScanModels
bool
false
Also emit a definition for every swagger:model type, not just route-reachable ones. See When the scanner emits a type.
PruneUnusedModels
bool
false
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
Base document to overlay scanned discoveries onto; its definitions are pinned and seed pruning roots. See Overlaying a spec.
Names & references
How definitions are named and how $refs render. See
Names & $refs.
Option
Type
Default
Effect
NameFromTags
[]string
nil (⇒ ["json"])
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
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
Render Go type aliases as a first-class $ref (via swagger:model) instead of expanding them inline. See Alias rendering.
TransparentAliases
bool
false
Make aliases fully transparent — never creating a definition. See Alias rendering.
DefaultAllOfForEmbeds
bool
false
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)
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
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 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
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
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
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
Suppress all x-go-* vendor extensions in the output. See Vendor extensions.
EmitXGoType
bool
false
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
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
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.
See also
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.
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).