All notable changes to stdocs are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Nothing yet.
0.7.1 - 2026-06-24
- Bumped the bundled UI versions: Scalar (
@scalar/api-reference) to 1.61.0, Swagger UI (swagger-ui-dist) to 5.32.8, and Stoplight Elements (@stoplight/elements) to 9.0.23 — across the CDN pins, the embedded bundles, and thepackage.jsontracker. All three are additive/patch upstream releases that preserve the configuration and CSP contracts stdocs relies on; the CDN SRI hashes and vendored bytes are re-pinned (Scalar's bundle and Swagger UI's JS changed; Swagger UI's CSS and Stoplight's assets are byte-identical to the prior pin).
0.7.0 - 2026-06-19
- Each bundled UI sub-package's
WithUInow accepts options, with aWithConfiguration(map[string]any)that forwards UI-native configuration to the docs page without forking the template or its CSP — Scalar'sconfiguration(viadata-configuration), Swagger UI'sSwaggerUIBundleoptions and Redoc'sRedoc.initoptions (via a non-executable JSON block read by a hash-pinned initializer), and Stoplight's<elements-api>attributes. It is carried by a new exportedConfig.UIConfigfield. - The OpenAPI spec endpoint sends
Content-Disposition: inlinewith a filename (openapi.json/openapi.yaml), so a direct download gets a sensible name while the document still opens in the browser.
- The bundled Scalar and Swagger UI pages now disable, by default, the
features that cannot work under the strict docs CSP, so the page has no
dead chrome: Scalar's "Ask AI" and "Generate MCP" (they call
scalar.com) and its external web fonts, and Swagger UI's spec-validator
badge (it loads from validator.swagger.io). Their no-config output
therefore differs from v0.6.x. Re-enable any of them with
WithConfiguration, and relax the policy withWithDocsSecurityHeaders(false)orWithCSPso the feature can reach its service. Redoc now boots through an inlineRedoc.initinitializer instead of the<redoc>web component.
- The Swagger UI inline initializer is re-pinned and Redoc's new inline
initializer is pinned by sha256 in its Content-Security-Policy; UI
configuration travels in a non-executable data block, so it never
affects those hashes or relaxes
script-src.
0.6.8 - 2026-06-17
- Updated the bundled Scalar UI (
ui/scalarandui/scalaremb) to@scalar/api-reference1.60.0 — an additive minor (acontent.startplugin slot and an opt-in sidebar for plugin views) plus a handful of rendering fixes. The standalone build and thedata-urlbootstrap the UIs rely on are unchanged, so the docs page and its CSP are untouched; the CDN sha384 SRI and the vendored air-gapped bundle are re-pinned to the new bytes, verified byte-identical between the npm tarball and the pinned jsDelivr URL.
- The README described the built-in docs page as ~1.6 KB — accurate at v0.1.0, but it has grown to ~10 KB across the v0.6.x additions (the security headers, the dismissable notice, method badges, and the expandable operations). Corrected the figure and aligned it with the source. Also tidied a couple of stale doc comments left over from older "Tier" terminology.
0.6.7 - 2026-06-14
- Operations on the built-in docs page are now expandable: clicking one
drops down its description, parameters, and a generated JSON example of
the request body and each response (schemas resolved through their
$refs, recursion-capped). It stays dependency-free and under the same strict CSP — the chevron is a cloned inline<svg>, examples render viatextContent, and the inline script/style hashes are re-pinned. The page still opens collapsed and remains a flat, skim-first list.
0.6.6 - 2026-06-14
- The built-in docs page got a light polish while staying tiny and
dependency-free. Operations now have coloured method badges (custom
methods included), a padlock marks the ones that require auth,
deprecated operations are dimmed and flagged, and the header carries
the API version, description, and links to
openapi.json/openapi.yaml. It is still a flat, no-build, no-CDN route list under the same strict CSP — the padlock is an inline<svg>, not an emoji, and the inline script/style hashes are re-pinned.
0.6.5 - 2026-06-14
- The built-in docs page now shows a small dismissable notice that it is
the minimal default UI, with a one-line pointer to the richer ones
(import a UI sub-package and pass its
WithUI()option). Dismissing it is remembered inlocalStorage. The notice respects the page's strict CSP — it is shown and hidden through thehiddenattribute, with no inline style, and its script/style hashes are re-pinned.
0.6.4 - 2026-06-13
Security headers on the served docs page.
- The docs handler now sends hardening headers by default: a
Content-Security-Policy on the HTML page, and
X-Content-Type-Options,Referrer-Policy,X-Frame-Options, andPermissions-Policyon every docs response. The CSP is scoped to the active UI — the built-in page gets a strictdefault-src 'none'with its inline script and style pinned by hash, and each rich UI ships the policy its bundle needs. Scripts are pinned by source and hash with nounsafe-inline, only same-origin framing is allowed, and no third-party connection is permitted, so the page makes no off-origin calls (the embedded UIs are fully self-contained, fonts and logos included).WithDocsSecurityHeaders(false)turns the set off;WithCSPreplaces just the policy. Every UI is rendered under its enforced policy in the ui-smoke browser test. There is no Strict-Transport-Security header: HSTS governs the whole origin over TLS, which is the server's or the edge's job.
0.6.3 - 2026-06-13
A consistency pass before going public: stale references and comments cleaned up, and the linter now covers both modules.
- Removed a stale
CONTRIBUTING.mdcross-reference from the README that pointed at a section which is not there. golangci-lintnow also runs against the nested YAML round-trip module in CI; the root invocation never reached it, so a regression in that module could have slipped through.
- The
swagger-uiandredocpackage docs described the hash-recompute step as living inCONTRIBUTING.md; they now point at the inline recipe and the*embair-gapped option, matching thescalarandstoplightpackages. - The CI version-matrix comment no longer claims a "two most recent
releases" policy the 1.24 floor outgrew; it describes the
go.mod-floor behavior the job actually implements. - Corrected an internal file path in the Dependabot maintainer notes.
0.6.2 - 2026-06-13
- A declared
defaultthat violates its own constraints — a value outside itsenum, past a numeric bound, or failing aminLength/maxLength/pattern— now panics at build time instead of emitting a self-contradictory document. (The promise that invalid input fails fast rather than publishing a wrong contract had a hole: adefaultoutside itsenumshipped both.)
- The README scope note now states the line vs typed-handler frameworks: stdocs documents handlers but does not validate or enforce the contract at runtime.
package-lock.json— a 468 KB lockfile in the root of a zero-dependency library; the dev-only npm manifest Dependabot tracks needs onlypackage.json.
0.6.1 - 2026-06-13
Pre-public polish: wider Go support, a cleaner default document, and honesty-claim repairs.
- The minimum Go version is now 1.24 (was 1.25). The code needs
nothing newer than the
omitzerojson tag; the CI version matrix derives from thego.modfloor, so it covers every stable 1.24+ patch automatically. - Clean output is now the default. The published document no
longer carries the
Generated from Go type ...fallback descriptions or thex-stdocs-type/x-stdocs-warningannotation extensions, which leak Go package layout into a contract consumed by client generators and portals. (x-stdocs-additionalOperationsis still emitted — on 3.0/3.1 it is the only representation of custom-method routes.)WithCleanOutput(false)keeps the annotations for debugging which Go types produced which schemas.
no-request-bodyLint advisory: a POST/PUT/PATCH operation with no documented request body (a forgottenWithBody) now gets a finding instead of silently documenting the route as taking nothing.SECURITY.mdwith a private vulnerability disclosure policy.
- A bad route pattern panicked with a doubled prefix
(
stdocs: stdocs: ...) becauseHandleFunc/Handlere-wrapped an already-prefixed error.
0.6.0 - 2026-06-13
The contract reaches the frontend: TypeScript declarations generated natively, from the same model as the document.
- The
tsgensubpackage:tsgen.Generate(mux)returns the API as one self-contained TypeScript module — an exported interface or alias per component schema, acomponentsinterface gluing them, anoperationsinterface keyed by the rebuild-stable operationIds (parameters by location, request bodies, responses per status; body-less entries type asundefined, raw bodies asstring), andwebhookswhen declared. Doc and constraint tags become JSDoc; nullability becomes| null— generation reads the version-agnostic model, so the 3.0-vs-3.1 dialect question never arises. Pure Go: no node toolchain at generation time. Types only, permanently — no runtime client, no fetch wrapper, no npm package; the transport belongs to the application. Output is deterministic and meant to be committed like the spec bytes: regenerating on an stdocs upgrade is a contract change, review the diff. CI compiles the generated corpus with the pinnedtypescriptrelease under--strict; current TypeScript releases under default compiler settings plus--strictare the supported surface. Webhooks appear for 3.1 and 3.2 muxes only, matching the served document, and a component named after a TypeScript reserved word (or the module's owncomponents/operations/webhooksinterfaces — reachable only through unexported Go types) fails generation with theSchemaNameremedy instead of declaration-merging into a type no wire value satisfies.
- The scope statement now says what it always meant, with the line drawn precisely: stdocs uses no code generation, annotations, or dependencies to understand your Go code — and what it emits (the document, the UI, now TypeScript declarations) sits on the other side of that line. The previous wording read as a promise to never emit generated artifacts; this release would have quietly contradicted it, so the sentence changed in the open instead — in the reference and in the README.
- Invalid
jsontag names (control characters, emoji — anythingencoding/json'sisValidTagrejects) now fall back to the Go field name in the document, the way they always did on the wire; the schema no longer requires a keyjson.Marshalnever emits. - A 3.0 mux with registered webhooks no longer leaks their payload schemas into the document as orphan components — 3.0 has no webhooks field, and now no residue of one.
0.5.1 - 2026-06-12
- Embedded-field flattening now follows
encoding/json's dominance rules exactly: a shallower field hides deeper ones, a lone tagged field beats untagged same-depth rivals, any other same-depth name collision drops the field entirely (including diamond embedding — while a field below a shared join point survives, sinceencoding/jsonloses embed multiplicity there), unexported pointer embeds promote their exported fields, and a shadowed embed's required-ness no longer leaks onto the winning field. Documents change only where they disagreed with whatjson.Marshalactually serves — schemas with colliding embeds stop claiming fields that never reach the wire, so golden files over such shapes will show a diff worth reading. - Flattening is decided by struct kind, as
encoding/jsondecides it, which retires a family of phantom properties: an embedded struct with no JSON-visible fields (sync.Mutex, marker structs) no longer documents a required property named after the type, recursive pointer embeds no longer document a self-referential phantom, and marshaler embeds whose method promotion is blocked flatten their exported fields like the wire does. Tag-named unexported struct embeds — whichjson.Marshalserves as nested objects — are documented now,json:"-,"names a key-instead of dropping the field, anopenapi:"-"field keeps participating in name dominance (hiding a field cannot resurface a rival the wire drops), and anopenapioverride on an embedded named scalar applies instead of panicking about a flattening that never happens. - The DriftWarn reference told users to place the wrapper around their middleware, which its signature makes impossible; it now states the real limitation — responses written by surrounding middleware are invisible to it.
0.5.0 - 2026-06-12
DriftWarn graduates from log lines to a CI-gateable contract checker, and raw responses get the same default/fallback treatment JSON bodies have.
DriftNotify(fn): every drift warning is also delivered as a structuredDriftFindingwith a stableCode(build-failed,undeclared-status,content-type-mismatch,body-kind-mismatch,missing-required-field,undocumented-fields) — allow-list by Code in a test that replays traffic and drift becomes a CI gate, the same discipline asWarning.Code.DriftSampleBodiesnow looks one level into rows: elements of array-of-object properties (orders[].fee_cents) and of array bodies ([].id) have their keys compared against the documented row schema, accumulated per field so warn volume stays bounded by the schema, never the row count.WithFallbackRawResponse(status, contentType)andWithDefaultRawResponse(status, contentType): raw string-typed default responses at both scopes, completing the default/fallback x JSON/raw grid — a plain-text error era bundles the way a JSON one does. Precedence is unchanged, and aWithResponseContentTypeon the route survives a raw fallback.- A documented pattern for list-row subsets: share the canonical
model's common fields through an embedded core (reflection
flattens embedding the way
encoding/jsonpromotes fields). There is deliberately no doc-only subset helper — a document trimmed below what the handler writes is whatDriftWarnexists to catch.
- Drift logs get more accurate on upgrade: statuses covered only by
a
defaultentry now have its declared media type checked (a CSV default served as plain text warns like an explicit status would), and new row-level warnings surface divergence that was already being served. A sampled body of the wrong top-level JSON kind — the classic literal-null 200 against an object schema — warns once asbody-kind-mismatchinstead of counting every required field missing. One class of warnings disappears: the ServeMux's own canonicalization redirects (/subhitting aGET /sub/registration, path cleaning) are no longer attributed to the route — its handler never ran, so there is no contract to compare. - The generator notes were re-verified against current releases:
ogen v1.17.0 fixed the nullable anyOf-with-facets rejection, and
the
nullable-facet-generatorsadvisory now says so precisely (the Code is unchanged; exclusive bounds, the webhook-security bug, and the oapi-codegen nullable-enum constant still stand). CI generates with ogen v1.20.3.
DriftWarn's route snapshot records its registration generation before building, closing a window where a route registered mid-build could be snapshotted unfinalized and never revisited.- Bodies streamed via
ReadFrom(sendfile) flow through the sampling capture buffer instead of bypassing the body check.
0.4.2 - 2026-06-12
WithFallbackResponse(status, body): route-scoped default responses, built forOptsbundles — one fallback per error-shape era. Explicit declarations win, then route fallbacks, then the mux-levelWithDefaultResponse.WithRawResponse(status, contentType): raw and file responses (CSV, plain text, downloads) in one opt, replacing the inferredWithResponse(status, "") + WithResponseContentTypeidiom (which keeps working).- The
openapitag accepts a composablenullableentry: bareopenapi:"nullable"stacks with reflection (constraints and doc tags keep composing), decoupling wire-level null from Go pointers; combined withrequired:"true"it expresses required-but-nullable without changing the Go type. Also composes inside type overrides. DriftSampleBodies(): opt-in DriftWarn body sampling that compares response bodies' top-level keys against the documented object schema — each missing required key warns once per route, status, and field; undocumented extras once per route and status (64 KB cap, development aid).
0.4.1 - 2026-06-12
A bugs-polish-and-deep-testing release: every known defect from the adversarial verification backlog and a five-persona user-simulation study, fixed and pinned.
- Embedded UIs no longer render a silent blank page when mounted the
documented way:
Mountregisters the*embpackages' asset route automatically through the newConfig.Assetsfield, tolerating a pre-existing manual registration — upgrading code that followed the old two-line example keeps working, and the manual line can simply be deleted. ManualDocs()mounting keeps the explicitAssetHandlerregistration. - Routes registered after a build now appear on the next read — the
spec cache tracks a registration generation, so
JSON,YAML, the served endpoints,Lint, andDriftWarnstop serving stale documents and the "register everything first" caveats are gone. Registration is synchronized with serving (matching the embeddedServeMux's own guarantees), late registrations after the first build validate their schemas eagerly atHandleFunc, and the published operation ids depend only on the current route set — never on when intermediate builds happened. Mountbuilds the document eagerly: fail-fast tag panics fire at startup instead of inside the first docs request.DriftWarnis snapshot-based: no more race againstRefresh/finalize, late registrations are picked up, and a JSON-documenteddefaultresponse served with a non-JSON Content-Type now warns (the text/plain straggler that previously slipped through).- Webhook operations no longer inherit document-level security —
they emit an explicit
security: []override (or the newWebhook.Security), so generated clients compile again. - Host-scoped patterns are handled honestly: a deterministic
survivor per (method, path) — hostless wins — with an
x-stdocs-warningon hosted survivors, no dangling operationId suffixes, host-free tag/summary inference, and ashadowed-routeLint finding for the registrations the document cannot express. required:"true"now works on body/response structs (previously a silent no-op outsideWithParams); with a pointer field it documents required-but-nullable, andrequired:"false"opts out.- Unsigned integer fields document
minimum: 0, yielding to explicit bound tags. Spec-affecting. - Default operationIds normalize hyphenated path segments
(
get_internal_reconcile_status). Spec-affecting. Optional()is order-independent withWithBody.- Tag inference skips version segments (
/v1/tasksgroups underTasks, notV1);WithTagFuncoverrides the inference for other conventions. Spec-affecting for version-prefixed APIs.
Lintfindings carry a stableWarning.Codefor CI allow-lists, plus new advisories:required-with-default,auto-descriptions,dangling-id-suffix, andshadowed-route;exclusive-boundswarns that current Go generators reject the numeric 3.1/3.2 form.- The
ParamOptvocabulary is complete:ParamFormat,ParamExclusiveMinimum/Maximum,ParamMinItems/MaxItems/UniqueItems, andParamItemsfor typed array elements. - Reflector fuzzing (
reflect.StructOfover arbitrary shapes and tag mixes) and a 500-case emitter property test pinning the per-version dialect rules. - Headless rendering smoke tests for all nine bundled UIs (build tag
uismoke; manual-dispatch CI job), through a shadow-DOM-piercing harness. - CI gates: Spectral style errors on the 3.0.4 corpus document and an 80% statement-coverage floor.
MIGRATING.mdgains a retrofit guide (mirror types, docs behind auth middleware, raw responses, generic envelopes, error-shape eras); the reference explains the default response plainly, documents the required-tag override, map reflection, the raw download idiom, embedded-asset auto-registration, and extends the generator notes (oapi-codegen nullable-enum caveat, TypeScript generators).
0.4.0 - 2026-06-11
WithCleanOutput(true)strips the stdocs annotation extensions (x-stdocs-type,x-stdocs-warning) and the auto-generated "Generated from Go type ..." descriptions for documents published as contracts.x-stdocs-additionalOperationssurvives — it is the only 3.0/3.1 representation of custom-method operations.- Component-name control: a
SchemaName() stringmethod names a type's component (value or pointer receiver), and generic instantiations simplify to readable identifiers (Page[main.Task]→Page_Task). - The
openapifield tag:openapi:"-"excludes a field from the document;openapi:"type=string,format=date-time"replaces the reflected schema when reflection cannot infer the wire format. Constraint and doc tags compose on top. WithResponseContentType(status, ct)— the response-side counterpart ofWithBodyContentType;DriftWarntreats declared non-JSON content types as the contract.WithMultipartBody+FilePart/FieldPartdocument multipart/form-data file uploads.- Spec richness:
WithExternalDocs(document),WithTagExternalDocs(tags, order-independent withWithTag), theExternalDocsroute opt (operations),WithSPDXLicense(3.1+identifier, degrading to name-only on 3.0), andWithOperationIDFuncfor operationId style control. Mux.Lint()reports advisory consumability findings: operations without error responses or summaries, untyped schema fields, collision-suffixed component names, custom-method extension carriers, and vendor extensions in non-clean output.MIGRATING.md: migration guides from swaggo/swag, FastAPI, and typed-handler frameworks, with mapping tables, linked from the READMEs.
- Nullable scalars on 3.1/3.2 emit the
anyOfform instead of atypearray: both are valid JSON Schema 2020-12, but real-world generators digestanyOfmore reliably (ogen rejects the array form), and nullable$refuse sites already emitted it. Type-gated facets (bounds, lengths, pattern, array facets, the enum with a null member) hoist onto the wrapper so doc UIs render them. Verified: ogen generates typed clients from stdocs documents, and CI gains that consumability gate (3.0.4 full-corpus plus a 3.1 document; numeric 3.1 exclusive bounds are rejected by current Go generators —Lint()warns). - 3.1/3.2 schema objects emit the
examplesarray instead of the dialect-deprecated singularexample(3.0 unchanged). WithTagmerges into an existing declaration instead of appending a duplicate, makingWithTagExternalDocsorder-independence true in both directions.- Component-name reservation accounts for types reflected during
another type's build, and a
SchemaNamemethod promoted from an embedded field no longer renames the embedding type — both previously produced silently shared (clobbered) components. - Generic component names changed from fully-qualified sanitizations
(
main_Page_main_Task) to the simplified form (Page_Task). Spec-affecting for documents containing generic types.
0.3.0 - 2026-06-11
- Schema constraint tags on struct fields:
minimum,maximum,exclusiveMinimum,exclusiveMaximum,minLength,maxLength,pattern,minItems,maxItems,uniqueItems,enum,default, andformat. Values are parsed per the field type and validated against it; misapplied or unparseable constraints panic at document-build time. Exclusive bounds emit the boolean form on 3.0 and the numeric 2020-12 keywords on 3.1/3.2. - Typed parameter declaration, two surfaces:
ParamOptmodifiers onWithParam/QueryParam/HeaderParam/CookieParam(ParamRequired,ParamDefault,ParamExample,ParamEnum,ParamMinimum,ParamMaximum,ParamMinLength,ParamMaxLength,ParamPattern— values validated against the declared type), andWithParams(struct), which reflects a struct withquery:/header:/cookie:location tags, the body fields' tag vocabulary, andrequired:"true". Opts(...)combines route opts into reusable bundles.WithDefaultResponse(status, body)documents a response on every operation that does not declare the status itself — the shared error envelope declared once. Status 0 means the OpenAPIdefaultresponse.- Secured operations (per-route
WithSecurityor inheritedWithGlobalSecurity) automatically document a 401; a per-route 401 or aWithDefaultResponse(401, body)wins, andWithAutoUnauthorized(false)suppresses it. Spec-affecting. WithPathPrefix(prefix)prepends a documentation-only prefix to every emitted path, for muxes mounted behindhttp.StripPrefixor a stripping reverse proxy.DriftWarn(mux, logf), a development aid that warns once per route and finding when a handler returns an undocumented status or writes a non-JSON Content-Type for a JSON-documented response.
WithParamand its shorthands fail fast: an unknown type string (previously a silent empty schema), an unknowninlocation, or an empty name now panics at registration. Duplicate (name, location) parameter pairs on one operation panic at document build, numeric constraint values must satisfy the JSON number grammar, enum tag members are trimmed (empty members panic), and nullable fields with an enum listnullso the published contract matchesencoding/json.- YAML output reformats exponent-form numbers (
1e3→1.0e+3) so YAML 1.1 parsers type them as numbers. - CI gains a spec-validation job: generated 3.0.4/3.1.2 documents run through openapi-spec-validator and 3.2.0 validates against the official OpenAPI 3.2 JSON Schema on every push.
- The package documentation on pkg.go.dev is now the canonical reference, organized by topic with runnable examples; the README is slimmed to hero, features, one worked example, the UI table, and per-topic links into the reference.
0.2.0 - 2026-06-11
- README documentation for the spec-as-artifact workflow (golden-file
test, PR contract diffing, linting, client generation), the
doc:/description:/example:field tags, theWithResponse(0, ...)default-response convention, and an explicit scope-and-non-goals section. FromDocs(r, docsPrefix)andMux.FromDocs(r)report whether a request appears to originate from the docs UI's try-it consoles (best-effort, Referer-based), so teams can apply their own policy — block writes, divert to scratch storage, tag for observability. Documented with a guard-middleware example and an explicit not-a-security-control caveat: the signal is client-controlled and must only ever gate restrictions.
- Go
int,uint, anduint32now reflect asformat: int64(previouslyint32). Goint/uintare 64-bit on every supported platform anduint32exceeds the int32 range — clients generated from the old mapping mis-typed these fields. Spec-affecting. - The
examplestruct tag is parsed according to the field type:example:"42"on an integer field now emits the number 42 instead of a string that violated its own schema. Unparseable values panic at document-build time.
0.1.1 - 2026-06-10
Mux.Mountaccepts the same optional bool asMux.Docs, with the same rule: an explicit per-call value wins overWithDisabledin both directions (mux.Mount(env != "prod")).- Per-route visibility: the
Hidden()route opt excludes a route from the generated document everywhere;Internal()excludes it unless the mux is configured with the newWithInternal(true)option (default false — internal routes never leak by accident). Shown internal operations carry the conventionalx-internal: trueextension. Excluded routes leave no trace in the document (no paths, schemas, or operation-id effects) and still serve traffic — visibility is documentation shaping, not access control.
- Go support now follows the Go project's release policy: the two
most recent Go releases (currently 1.25 and 1.26;
godirective 1.25.0, down from 1.26.4). CI computes every supported patch release from the go.mod floor and runs build, vet, the race-enabled test suite, and the YAML round-trip module on each.
0.1.0 - 2026-06-10
Initial release.
- OpenAPI document generation for routes registered on a wrapped
net/http.ServeMux(stdocs.New+HandleFunc/Handlewith the Go 1.22+ method+path pattern syntax). Specs are served at<prefix>/openapi.jsonand<prefix>/openapi.yamltogether with a docs UI page; the prefix defaults to/docs(WithDocsPrefix). - Three OpenAPI versions — 3.0.4 (default), 3.1.2, and 3.2.0: the
latest patch of each 3.x minor, selected with
WithVersion. All three outputs validate against external validators (openapi-spec-validator for 3.0/3.1, the official 2025-09-17 JSON Schema for 3.2). 3.2 extras:$selfviaWithSelfURL(validated as a fragment-free URI reference), first-classqueryoperations, custom HTTP methods underadditionalOperations(anx-stdocs-additionalOperationsextension carries them on 3.0/3.1), and thedeviceAuthorizationOAuth flow. - Type-to-schema reflection that follows the
encoding/jsoncontract: pointers (nullable), slices, maps, arrays, generics, recursive types via$ref, embedded structs (tagged ones nest, unexported ones promote),json.RawMessage/json.Marshaler/encoding.TextMarshalerawareness, and theomitempty,omitzero,,string, and-tag options. Component names are unique document-wide; same-named types from different packages get numeric suffixes with consistent$refs. - Smart defaults: summaries inferred from handler function names
(closures and method values excluded), tags from the first path
segment (matching the casing of
WithTagdeclarations), wildcard path parameters at the path-item level, auto-200 responses, and document-unique operation ids that stay stable across rebuilds. - Route opts:
Summary,Description,Tags,Deprecated,OperationID,Optional,WithBody,WithBodyContentType,WithResponse,WithResponseDescription,WithResponseHeader,WithResponseExample,WithExample,WithParam(+QueryParam,HeaderParam,CookieParam),WithSecurity,WithNoSecurity. Response/body decoration opts are order-independent. - Security schemes:
WithBearerAuth,WithBasicAuth,WithAPIKeyAuth,WithOAuth2Auth,WithSecurityScheme, andWithGlobalSecurity. Requirements referencing unregistered scheme names are reported as errors fromJSON()/YAML(). - Webhooks for 3.1/3.2 via
WithWebhooks, with payload schemas reflected fromBodyValuelike route bodies. - Five UIs: a dependency-free default page (~1.6 KB, inline JS only), plus Scalar, Swagger UI, Redoc, and Stoplight Elements — each as a CDN sub-package (exact versions, sha384 SRI on every script and stylesheet) and an air-gapped embedded sub-package (vendored npm bundle bytes, integrity-tested, immutable cache headers).
- Docs toggling:
Mux.Docs(enabled)per call site andWithDisabled(bool)per mux; an explicit per-call value wins. Routes under the docs prefix never appear in the generated spec. - Tier 1:
DocsHandler+WithSpecserve a hand-written OpenAPI document behind any of the bundled UIs without wrapping a mux. - Escape hatch:
WithOpenAPI(func(map[string]any))mutates the built document before caching;Refresh()forces a rebuild. - XSS-safe docs page rendered through
html/template; relative spec and asset URLs work under any prefix or reverse proxy. - Tooling: GitHub Actions CI (build, gofmt, race tests, fuzz, vet,
golangci-lint, YAML round-trip against gopkg.in/yaml.v3, coverage),
Dependabot for gomod/actions/npm with per-package version-parity
tests, and a runnable demo (
cmd/demo).