Skip to content

openapi3: add an OpenAPI 3.0 output package - #117

Merged
seanogdev merged 5 commits into
masterfrom
openapi3-output
Aug 11, 2026
Merged

openapi3: add an OpenAPI 3.0 output package#117
seanogdev merged 5 commits into
masterfrom
openapi3-output

Conversation

@seanogdev

@seanogdev seanogdev commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What

Adds an openapi3 output package, so Kommentaar can emit OpenAPI 3.0.3
alongside the existing Swagger 2.0 output.

New output formats: openapi3-yaml, openapi3-json, openapi3-jsonindent.
The default output is unchanged (openapi2-yaml), so nothing moves until a
caller asks for v3.

docparse is untouched. The IR is already format-neutral JSON Schema, so both
writers share the same parser and the same Schema type.

Mapping

OpenAPI 2 OpenAPI 3
in: body parameter requestBody with a content map
form parameters requestBody, application/x-www-form-urlencoded
consumes / produces per-media-type content entries
definitions components.schemas
#/definitions/X #/components/schemas/X
flat parameter fields (type, format, enum, …) nested under parameter.schema
basepath servers

Two OpenAPI 2 workarounds are dropped rather than ported, because v3 gives
parameters a full schema:

  • query array items keep their $ref instead of being downgraded to string
    (openapi2.go had // in swagger 2.0, arrays in the query can only contain basic type)
  • a parameter typed by a named Go type keeps that reference instead of falling
    back to string

Two things v3 requires and v2 did not:

  • response codes are string keys ("200", not 200)
  • description is mandatory on a response, so an empty one falls back to the
    HTTP status text

Verification

Beyond the unit tests and 13 golden files, I generated the full
Teamwork Projects API v3 spec with both writers and compared them
structurally (620 paths, 793 operations, 1010 schemas):

  • identical path / operation / schema name sets — nothing added or dropped
  • schema bodies byte-identical modulo the $ref prefix — 0 of 1010 differ
  • operationIds, summaries, tags, response codes and parameter name+in sets:
    0 differences
  • 249 in: body became 249 requestBody, with 0 presence mismatches
  • no v2 leftovers: zero occurrences of swagger:, definitions:,
    #/definitions/, in: body, in: formData, consumes, produces

Validated with spectral (spectral:oas) against that real spec, the v3
output is strictly more valid than the Swagger 2.0 we ship today:

rule v2 v3
parser 1392 0
*-valid-schema-example 1826 1826
path-params 13 13
*-schema 447 50
total errors 3678 1889

The 1392 parser errors v3 eliminates are all "Mapping key must be a string
scalar rather than number"
— the unquoted integer response codes.

The errors that remain are pre-existing and reproduce identically under both
writers:

  • 1826 *-valid-schema-example: Schema.Default is a Go string and
    Enum is []string, so a boolean parameter emits default: "false" and an
    integer enum emits ["0","1","2","3"]. Fixing this belongs in docparse and
    would improve both writers.
  • 13 path-params: duplicate endpoints documented under two placeholder
    names. A caller-side doc-comment bug.
  • 47 occurrences of type: "any": existing docparse output.

Fidelity audit against a real spec

I diffed the two writers field by field over the Teamwork Projects API v3 spec —
every parameter's type, format, enum, default, minimum, maximum,
readOnly, items and description; every response's description, schema
reference and media type; every request body; every schema body; and the
operation metadata.

That audit found one real fidelity loss, now fixed in this PR:

A response documented as a media type with no body —
Response 200 (text/csv): {empty} — recorded text/csv in the OpenAPI 2
operation-level produces. OpenAPI 3 has no produces, and the writer only
emitted content when a schema existed, so the media type vanished and the
response looked empty. 42 endpoints were affected (CSV, HTML, PDF, XLSX, plain
text, octet-stream). They now emit a schema-less content entry:

"200":
  description: 200 OK (no data)
  content:
    text/csv: {}

Only explicit non-default media types qualify, so the 158 204s and the
other ~580 schema-less JSON responses stay bare rather than gaining a
meaningless application/json: {}.

After the fix the audit reports zero differences in every other category:
no parameter field lost, no enum/default/format dropped, no schema body
changed, no operation or response missing, no description lost. spectral
error counts are unchanged by the fix (1889), so the new entries introduce no
validation problems.

Note for callers

Extend: stays a verbatim passthrough. An extend file written for v2 (e.g.
containing consumes:) will emit a key that is not valid in OpenAPI 3;
x--prefixed extensions are unaffected.

Testing

go test ./... passes, including the new openapi3 package. TestOpenAPI3
reuses the existing testdata packages and only runs cases that have a
want3.yaml, so it adds no fixture duplication. Refresh goldens with
UPDATE_GOLDEN=1 go test -run TestOpenAPI3 .

Ports the openapi2 writer to OpenAPI 3.0.3. The docparse IR is
format-neutral, so the parser is untouched and both writers share it.

Mappings:
  - body parameter        -> requestBody with a content map
  - form parameters       -> requestBody, application/x-www-form-urlencoded
  - consumes/produces     -> per-media-type content entries
  - definitions           -> components.schemas
  - #/definitions/X       -> #/components/schemas/X
  - flat parameter fields -> parameter.schema
  - basepath              -> servers

Two OpenAPI 2 workarounds are dropped rather than ported: query array
items keep their $ref instead of being downgraded to string, and a
parameter typed by a named Go type keeps that reference. Response codes
are string keys, and a response with no description falls back to the
HTTP status text, both of which OpenAPI 3 requires.
Covers the example package end to end and asserts no OpenAPI 2
constructs survive, plus the schema, status-text and id helpers.
Adds openapi3-yaml, openapi3-json and openapi3-jsonindent. The default
output stays openapi2-yaml.
TestOpenAPI3 reuses the existing testdata packages and compares against
want3.yaml, so only curated cases run. Set UPDATE_GOLDEN=1 to refresh
them.
A response documented as a content type with no body, e.g.

    Response 200 (text/csv): {empty}

recorded that type in the OpenAPI 2 operation-level `produces`. OpenAPI 3
has no `produces`, and the writer only emitted `content` when there was a
schema, so the media type was dropped and the response looked empty.

Emit a schema-less content entry for it instead. Only explicit non-default
types qualify, so responses that merely default to JSON without a body,
such as 204s, stay bare.
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 31419661334

Coverage increased (+1.5%) to 56.68%

Details

  • Coverage increased (+1.5%) from the base build.
  • Patch coverage: 142 uncovered changes across 2 files (265 of 407 lines covered, 65.11%).
  • No coverage regressions found.

Uncovered Changes

File Changed Covered %
openapi3/openapi3.go 398 262 65.83%
kconfig/kconfig.go 6 0 0.0%
Total (3 files) 407 265 65.11%

Coverage Regressions

No coverage regressions found.


Coverage Stats

Coverage Status
Relevant Lines: 2747
Covered Lines: 1557
Line Coverage: 56.68%
Coverage Strength: 35.46 hits per line

💛 - Coveralls

@seanogdev
seanogdev merged commit 749d535 into master Aug 11, 2026
3 checks passed
@seanogdev
seanogdev deleted the openapi3-output branch August 11, 2026 12:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants