Initial OpenAPI 3.1 support based on kin-openapi#2336
Conversation
|
@greptileai Please review this large change. It's a back-port from a different repo where this functionality is well tested. It's not ready to merge, due to kin-openapi not having a release ready, but I'd like feedback on the work to date to see if I missed anything major. Pay extra attention to places where OpenAPI 3.0 and OpenAPI 3.1 schemas provide alternative paths in the codegen. |
Greptile SummaryThis PR adds initial OpenAPI 3.1 support to the generator. The main changes are:
Confidence Score: 5/5This looks safe to merge.
Reviews (6): Last reviewed commit: "codegen: only map contentEncoding to []b..." | Re-trigger Greptile |
e4b5380 to
d33792a
Compare
|
@greptileai - all comments addressed, review again. |
|
(note to folks following this - we'll be releasing this as a 2.8.0 release, after 2.7.0) |
|
Do we think this will close any others from https://github.com/oapi-codegen/oapi-codegen/issues?q=is%3Aissue%20state%3Aopen%20openapi%203.1%20label%3A3.1 ? |
|
Worth checking if anything from 620da6b (and the related change to make the |
jamietanna
left a comment
There was a problem hiding this comment.
We'll also need a README change when this is closer to completion - to note that we do support 3.1 (but not 3.2!)
jamietanna
left a comment
There was a problem hiding this comment.
Will have a more in-depth review shortly, but so far this is looking reasonable 🥳
Yes. I will add them to PR description. |
d33792a to
38df03d
Compare
|
I rebased this onto the latest main, and downgraded the Go requirement to 1.25.9 |
kin-openapi's 3.1 work changed Schema.ExclusiveMin/Max from bool to
ExclusiveBound{Bool *bool; Value *float64}. Direct struct equality (!=)
still compiles but compares pointer addresses, not the wrapped values, so
two schemas with semantically identical bounds would incorrectly fail the
merge. Use reflect.DeepEqual for value-aware comparison.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This is Phase 1 of bringing 3.1 feature parity from oapi-codegen-exp into
mainline. It establishes the architectural patterns the rest of the work
will follow.
Version awareness:
* New globalState.is31 flag, populated once at Generate() entry from
swagger.IsOpenAPI31OrLater(). All version-aware logic lives in Go and
branches on this flag; templates remain version-blind and consume
pre-computed derived fields.
* Comment on the field explicitly forbids exposing it to TemplateFunctions
to prevent layering violations.
Nullable handling:
* New schemaIsNullable() helper that branches on globalState.is31:
3.0 reads s.Nullable; 3.1 reads s.Type.Includes("null").
* New schemaPrimaryType() helper strips "null" from the type slice in
3.1 mode so existing dispatch (`*Types.Is("string")` etc.) keeps
working for type:["string","null"].
* Four direct .Nullable read sites switched to the helper:
schema.go (property construction, array nullable, additionalProperties)
and operations.go (response header).
* merge_schemas.go's Nullable comparison routed through the helper; an
explanatory comment notes that type merging itself is NOT version
branched (equalTypes() handles single-element 3.0 and multi-element
3.1 type slices identically), and that result.Type already carries
any "null" entry forward in 3.1 mode.
Other:
* Adds OutputOptions.SkipEnumViaOneOf for Phase 2 (enum-via-oneOf
detection); declared now to keep the config schema stable across
phases.
* Removes the stale "OpenAPI 3.1.x is not yet supported" stderr
warning at cmd/oapi-codegen entry; partial 3.1 support now exists
and the message will become misleading as later phases land.
* Two path parameters in pkg/codegen/test_specs/x-go-type-import-pet.yaml
were missing the OpenAPI-required `required: true`. The omission was
incidental to what the test exercises (x-go-type-import); the spec is
now spec-conformant.
Tests:
* internal/test/openapi31_nullable/ -- two specs (3.0 nullable:true,
3.1 type:["string","null"]) generate into spec_3_0/ and spec_3_1/
subpackages. Instantiation tests (no string-matching) assert both
produce *string and round-trip JSON identically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 2 of the 3.1 backport. Detects the 3.1 enum-via-oneOf idiom and
routes it through the existing typed-enum codegen path so users see the
same Go shape they'd get from a plain `enum:` array.
The idiom (per OpenAPI 3.1 / JSON Schema 2020-12):
Severity:
type: integer # or "string"
oneOf:
- title: HIGH
const: 2
description: An urgent problem
- title: MEDIUM
const: 1
- title: LOW
const: 0
Generates:
type Severity int
const (
HIGH Severity = 2
LOW Severity = 0
MEDIUM Severity = 1
)
Implementation:
* New detectEnumViaOneOf() helper in pkg/codegen/schema.go. Trigger
conditions: globalState.is31 is true, !SkipEnumViaOneOf, schema's
primary type is "string" or "integer", every oneOf branch carries
non-empty Title AND non-nil Const, and no branch is itself a
composition or has Properties.
* GenerateGoSchema short-circuits between the AllOf branch and the
type dispatch: when the idiom matches, populate Schema.EnumValues
directly and register an AdditionalType for non-toplevel schemas
so the existing EnumDefinition collector picks them up. Negative
matches fall through to the standard oneOf union generator.
* No template changes -- the existing constants.tmpl renders the
typed enum + Valid() method via the standard EnumDefinition path.
* The OutputOptions.SkipEnumViaOneOf flag declared in Phase 1 is
now actually consulted here.
Tests:
* internal/test/enum_via_oneof/ -- ports the exp test fixture
(Severity int, Color string, MixedOneOf negative path). Five
instantiation tests (no string-matching) cover constant values,
JSON round-trips, and the negative path's compile-time alias
shape (a plain string is directly assignable to MixedOneOf,
proving it did NOT become a typed-enum newtype).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…iver
Phase 3 of the 3.1 backport. Generates client-side and server-side
support for `webhooks:` declared in OpenAPI 3.1 specs. The output
mirrors the existing Client / Server interfaces in shape but lives in
its own types, matching the conventions oapi-codegen-exp established.
Data model:
* OperationDefinition gains IsWebhook bool and WebhookName string.
* New WebhookOperationDefinitions() walks swagger.Webhooks and produces
OperationDefinitions in the same shape as path operations, minus the
path-alias and path-parameter logic (webhooks have no path template).
No version gate -- kin-openapi only populates the Webhooks field for
3.1+ documents, so 3.0 specs short-circuit on the empty map.
Generated client (WebhookInitiator):
* New pkg/codegen/templates/webhook-initiator.tmpl, modeled on
client.tmpl. Emits:
type WebhookInitiator struct{ Client; RequestEditors }
NewWebhookInitiator + WebhookInitiatorOption + WithWebhookHTTPClient
+ WithWebhookRequestEditorFn
WebhookInitiatorInterface
per-webhook methods (Op + OpWithBody) and request builders
(NewOpWebhookRequest + NewOpWebhookRequestWithBody)
* No stored Server -- the target URL is supplied per-call by the
caller (typically discovered from a subscription registration).
* Gated by Generate.Client (paired with the path Client).
Generated server (WebhookReceiver, stdhttp):
* New pkg/codegen/templates/stdhttp/std-http-webhook-receiver.tmpl.
Emits:
type WebhookReceiverInterface { HandleOpWebhook(w, r) for each }
type WebhookReceiverMiddlewareFunc func(http.Handler) http.Handler
OpWebhookHandler(si, middlewares...) http.Handler -- per-webhook
factory; caller mounts the returned http.Handler at the URL
path they advertise to subscribers.
* Methods take plain (w, r) -- the user reads/parses the body
themselves. Simpler than the full ServerInterfaceWrapper machinery;
parameter binding can be added later if the demand surfaces.
* Gated by Generate.StdHTTPServer (paired with the path stdhttp
ServerInterface). Other frameworks are left as follow-ups.
Wiring (codegen.go):
* allOps = ops + webhookOps is passed to OperationImports and
GenerateTypeDefinitions so webhook bodies and responses generate
their type definitions and imports normally.
* New GenerateWebhookInitiator and GenerateStdHTTPWebhookReceiver entry
points in operations.go, modeled on GenerateClient and
GenerateStdHTTPServer.
* Output ordering: webhook initiator follows ClientWithResponses;
webhook receiver follows the path StdHTTPServer block.
Tests (internal/test/webhooks/):
* spec.yaml declares a single petStatusChanged webhook with a JSON
PetStatusEvent body and a 204 response.
* TestWebhookRoundTrip mounts the generated factory against an
httptest.Server and fires a webhook via the initiator; asserts the
payload, method, and Content-Type round-trip intact.
* TestWebhookInitiatorRequestEditor verifies WithWebhookRequestEditorFn
applies on every outgoing request (parity with the path Client).
* TestWebhookReceiverMiddleware documents middleware composition order:
the factory wraps in `for _, mw := range middlewares { h = mw(h) }`,
so the LAST middleware passed becomes the outermost wrapper.
Example (examples/webhook/):
* Door badge reader scenario ported from oapi-codegen-exp/examples/webhook.
The server randomly generates enter/exit badge events every second
and fires the appropriate webhook to all registered subscribers; the
client subscribes to both event kinds via the path API, runs a local
HTTP receiver to handle inbound events, and deregisters cleanly on
exit. Demonstrates the full Client/Server + WebhookInitiator/Receiver
pairing in one example.
* Lives as a regular package under examples/go.mod -- no separate
module, no tools.go shim. `make generate` from examples/ regenerates
via the package's go:generate directive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 4 of the 3.1 backport. Generates client-side and server-side
support for `callbacks:` blocks nested under path operations. The output
mirrors WebhookInitiator / WebhookReceiver in shape -- separate types
named with the `Callback` prefix to match oapi-codegen-exp.
Callbacks have been part of the OpenAPI spec since 3.0, so this is NOT
version-gated: any spec that declares callbacks gets them generated.
Data model:
* OperationDefinition gains IsCallback bool and CallbackName string
(alongside the IsWebhook / WebhookName pair from Phase 3).
* New CallbackOperationDefinitions() walks
spec.Paths.<path>.<method>.Callbacks. Each leaf operation under the
callback's URL-expression key becomes one OperationDefinition with
IsCallback=true and CallbackName set to the outer map key (e.g.
"treePlanted"). The codegen does not interpret the URL expression
itself -- the caller of CallbackInitiator supplies the resolved
target URL at runtime, the same way it does for a webhook.
* Callback's internal map is private; iterate via cb.Keys() / cb.Value()
with sorted keys for deterministic output.
Generated client (CallbackInitiator):
* New pkg/codegen/templates/callback-initiator.tmpl. Same shape as the
Phase 3 webhook-initiator.tmpl with `Webhook` -> `Callback` rename:
type CallbackInitiator struct{ Client; RequestEditors }
NewCallbackInitiator + CallbackInitiatorOption
+ WithCallbackHTTPClient + WithCallbackRequestEditorFn
CallbackInitiatorInterface
per-callback methods (Op + OpWithBody) and request builders
(NewOpCallbackRequest + NewOpCallbackRequestWithBody)
* No stored Server -- targetURL is per-call.
* Gated only by Generate.Client + non-empty callbackOps.
Generated server (CallbackReceiver, stdhttp):
* New pkg/codegen/templates/stdhttp/std-http-callback-receiver.tmpl.
* WebhookReceiverInterface analog: Handle{Op}Callback(w, r) plus a
per-callback factory function {Op}CallbackHandler(si, middlewares...)
http.Handler.
* Gated only by Generate.StdHTTPServer + non-empty callbackOps.
Wiring (codegen.go):
* allOps is now `ops + webhookOps + callbackOps` so type-definition
and import passes see callback bodies / responses.
* Callback initiator output follows webhook initiator (so the order is
Client / ClientWithResponses / WebhookInitiator / CallbackInitiator).
* Callback receiver output follows webhook receiver under the
StdHTTPServer block.
* New GenerateCallbackInitiator and GenerateStdHTTPCallbackReceiver
template entry points in operations.go.
Tests (internal/test/callbacks/):
* spec.yaml deliberately uses `openapi: 3.0.3` to verify the codegen
does NOT gate callback emission on 3.1 (callbacks are 3.0+).
Models a tree-planting flow: parent operation PlantTree, callback
treePlanted with TreePlantingResult body.
* TestCallbackRoundTrip mounts the generated factory against an
httptest.Server and fires a callback via the initiator; asserts the
payload, method, and Content-Type round-trip intact.
* TestCallbackInitiatorRequestEditor verifies WithCallbackRequestEditorFn
applies on every outgoing request (parity with the path Client and
the WebhookInitiator).
Example (examples/callback/):
* Tree farm scenario ported from oapi-codegen-exp/examples/callback.
Server accepts PlantTree, returns 202, sleeps 1-5s, then fires the
treePlanted callback to the caller-supplied URL. Client opens an
ephemeral receiver, fires 10 plantings in sequence, and waits on a
channel until all 10 callbacks have arrived.
* Lives as a regular package under examples/go.mod -- no separate
module, no tools.go shim. Same flat layout as examples/webhook/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 5 of the 3.1 backport. Two small additive features that round out the 3.1 surface; no template changes. `examples:` (plural array) → Go doc comments: * New describeWithExamples() helper in pkg/codegen/schema.go folds example data into the description string used for generated doc comments. Version-aware: 3.0 reads schema.Example (singular), 3.1 reads schema.Examples (plural array). Cross-version misuse is documented as invalid input and the helper does not look at the off-version field. * Non-string examples are JSON-encoded so structured values render readably; strings round-trip as themselves. * Applied at the three sites where Description flows from the spec schema into the generated Schema/Property: GenerateGoSchema's $ref short-circuit, the main outSchema construction, and per-property population during object walk. `const:` → typed alias + singleton constant: * The existing enum branch in GenerateGoSchema now also fires on `(globalState.is31 && schema.Const != nil)`. A local enumSource abstracts over the two cases so a scalar `const: X` schema emits the same shape as `enum: [X]`: a typed alias plus a sanitized constant derived from the value. * Falls through the standard EnumDefinition pipeline -- existing constants.tmpl renders the typed enum + Valid() method without changes. Paths optional in 3.1: no-op. Every swagger.Paths access site (filter.go, prune.go, gather.go, operations.go, the new WebhookOperationDefinitions and CallbackOperationDefinitions) already nil-guards. Tests (internal/test/openapi31_polish/): * Pet.Name has a description plus two string examples; Pet.Lives has one integer example. Status is a top-level `const: active` schema. * TestStatusConstSchema (instantiation): `var s Status = Active` compiles only because Status is a distinct named type, and asserts the constant's value plus that Valid() returns true. * TestPetExampleComments parses the generated Go source and walks the AST to extract Pet's per-field doc comments, then asserts the expected description and "Examples: ..." fragments. Doc comments aren't runtime-introspectable, so AST inspection of the generated file is the right granularity here -- not a brittle string-match against the file as a whole. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…add param binding
Phase 6.1 of the 3.1 backport (kicking off the per-framework receiver
work). Sets up the prototype shape that the other 7 server-framework
receivers will follow.
Two changes that always have to land together:
1. Unify the two receiver templates into one parameterized template,
matching oapi-codegen-exp's structure:
* Replaces pkg/codegen/templates/stdhttp/std-http-webhook-receiver.tmpl
and pkg/codegen/templates/stdhttp/std-http-callback-receiver.tmpl
(deleted) with a single std-http-receiver.tmpl. The new template
takes a Prefix data field ("Webhook" or "Callback") and emits the
correct interface, middleware type, and per-op handler factory.
* New ReceiverTemplateData struct + NewReceiverTemplateData
constructor in operations.go feed the template.
* Single GenerateStdHTTPReceiver(t, prefix, ops) entry point;
callers in Generate() pass "Webhook" or "Callback".
2. Add query/header parameter binding to the receiver factory,
matching mainline's existing path-server middleware pattern.
The factory now takes an errHandler argument:
func PetStatusChangedWebhookHandler(
si WebhookReceiverInterface,
errHandler func(w http.ResponseWriter, r *http.Request, err error),
middlewares ...WebhookReceiverMiddlewareFunc,
) http.Handler
When operations have query/header params (rare for webhooks but
common for callbacks), the inline closure binds them into a typed
{Op}Params struct via runtime.BindQueryParameterWithOptions /
runtime.BindStyledParameterWithOptions and routes any error through
errHandler. errHandler may be nil; the default returns 400 with the
error message. When operations have no params, errHandler is
unused -- pass nil and the prior shape's behavior is preserved.
This is a breaking change to anyone using the Phase 3/4 generated
*Handler(si, middlewares...) shape. Acceptable since the Phase 3/4
surface is unreleased and only-this-branch.
New helper: OperationDefinition.SourceName() returns WebhookName when
IsWebhook, CallbackName when IsCallback. Templates use it to label
emitted handlers uniformly without branching on the source kind.
Existing call sites updated to pass nil for errHandler:
internal/test/webhooks/webhooks_test.go (3 places)
internal/test/callbacks/callbacks_test.go (2 places)
examples/webhook/client/main.go (2 places)
examples/callback/client/main.go (1 place)
The four affected gen files are regenerated. Other gen files in the
tree have unrelated drift from Phase 5's examples-in-doc-comments
feature (those don't touch the new receiver shape); a follow-up
`make generate` commit picks them up cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…omments Phase 5 (eedf057) added the describeWithExamples() helper that folds schema.Example / schema.Examples into the description string used for generated Go doc comments. Existing test specs that already had `example:` set on their schemas now produce richer doc comments. This commit is purely the result of running `make generate` -- no behavior change, only doc-comment additions like: // Code The underlying http status code +// +// Example: 500 Code int32 `json:"code"` Touched files: examples/minimal-server/stdhttp/api/ping.gen.go internal/test/externalref/petstore/externalref.gen.go internal/test/issues/issue-1087/deps/deps.gen.go internal/test/issues/issue-1168/api.gen.go internal/test/issues/issue1561/issue1561.gen.go Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 6.2 of the per-framework receiver work. Chi's path-handler signature is identical to stdhttp's (`(w http.ResponseWriter, r *http.Request)`), so the receiver template is structurally identical; only the file path and Go entry point are new. * New pkg/codegen/templates/chi/chi-receiver.tmpl -- a copy of the Phase 6.1 unified stdhttp receiver template. Single template handles both webhook and callback receivers via the Prefix data field. * New GenerateChiReceiver() entry point in operations.go. * In Generate(): two new blocks gated on Generate.ChiServer + non-empty webhook/callback ops, mirroring the stdhttp blocks. Generated output is written inside the existing chi-server WriteString block so all chi-related output stays grouped. * New internal/test/webhooks_chi/ -- compile-time assertion that the chi receiver template produces valid Go. The runtime round-trip behavior is already covered by internal/test/webhooks (stdhttp); since chi shares the signature, that coverage transfers. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…/<framework>/ Final Phase 6 cleanup. Reorganizes the seven flat per-framework webhook test directories under a single internal/test/webhooks/ parent so they group together and the layout reflects the framework-fan-out done in Phases 6.1-6.8. Layout change: internal/test/webhooks/ (the original stdhttp tests) internal/test/webhooks_chi/ } internal/test/webhooks_echo/ } internal/test/webhooks_fiber/ } flat per-framework dirs internal/test/webhooks_gin/ } internal/test/webhooks_gorilla/ } internal/test/webhooks_iris/ } becomes: internal/test/webhooks/ ├── spec.yaml (shared by all subdirs) ├── stdhttp/ (original round-trip tests) ├── chi/ ├── echo/ ├── fiber/ ├── gin/ ├── gorilla/ └── iris/ Per-subdir changes: * Package name simplified from `webhooks` / `webhooks_<framework>` to just `<framework>` (matches dir name; package-vs-import name overlap with framework packages like chi/v5 is harmless since Go's local package name is implicit). * Generated file uniformly named `webhooks.gen.go` (was `webhooks_<framework>.gen.go` in flat dirs). * config.yaml `package:` and `output:` directives updated; schema reference path bumped one level deeper. * doc.go `package` and prose updated; `go:generate` now points at the shared `../spec.yaml`. Spec deduplication: * The seven copies of spec.yaml (functionally identical, only title/description varied) collapse to one shared file at internal/test/webhooks/spec.yaml. Each subdir's go:generate references it via `../spec.yaml`. The round-trip test in stdhttp/webhooks_test.go has its package declaration updated to `package stdhttp`. No code or template changes outside the moves; same gen output up to package name. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two findings from automated review: P1 -- Non-deterministic map iteration in CallbackOperationDefinitions (pkg/codegen/operations.go). The inner loop walked `pathItem.Operations()` directly. When a path declares callbacks on multiple HTTP methods (e.g. both POST and PUT), the generated output would differ between runs because Go map iteration is randomized. Fix: copy keys into a slice and walk via SortedMapKeys, matching the pattern used by every other similar loop in the codebase (including WebhookOperationDefinitions immediately above). P2 -- Hidden global-state dependency in version-aware helpers (pkg/codegen/schema.go). schemaIsNullable, schemaPrimaryType, describeWithExamples, and detectEnumViaOneOf all read globalState.is31 implicitly, with no indication at the function signature that callers must invoke them only after Generate() has initialized the global. Fix: add an explicit "Precondition" line to each helper's doc comment so callers (including future refactors that move schema resolution earlier) are aware of the dependency. The signatures themselves are not changed -- threading the version flag through every call site would be an intrusive refactor for a problem better described in prose. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pulls in the official kin release which adds OpenAPI 3.1 support, and updates CI to run only on Go 1.26
Two distinct lint issues across four test files.
errcheck (5 sites): defer / direct calls to r.Body.Close() and
resp.Body.Close() ignored their error returns. Standard fix:
* `defer r.Body.Close()` -> `defer func() { _ = r.Body.Close() }()`
* `resp.Body.Close()` (non-deferred) -> `_ = resp.Body.Close()`
internal/test/callbacks/callbacks_test.go (1)
internal/test/webhooks/stdhttp/webhooks_test.go (4)
ST1023 (2 sites): "should omit type X from declaration; it will be
inferred from the right-hand side". Two different stories.
* internal/test/openapi31_polish/openapi31_polish_test.go: `var s
Status = Active` is genuinely redundant -- `Active` is declared
`const Active Status = "active"`, so type inference gives `s` the
type `Status` already. Switched to `s := Active`. Comment updated
to explain the still-meaningful compile-time check (if Active had
been emitted untyped, `s := Active` would not preserve the Status
type).
* internal/test/enum_via_oneof/enum_via_oneof_test.go: `var m
MixedOneOf = s` (where `s` is a plain string) IS the test. The
redundancy is intentional -- if MixedOneOf were a `type MixedOneOf
string` newtype rather than the alias `type MixedOneOf = string`,
this assignment would fail to compile. Letting inference give `m`
the type `string` would lose the property under test. Added
`//nolint:staticcheck` with the rationale inline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Now that kin doesn't require Go 1.26, downgrade to Go 1.25 and rebase changes from main.
Add OpenApi 3.1 top level schema sources to the hoisting test.
38df03d to
329414b
Compare
|
(note to me - we should double check everything in https://learn.openapis.org/upgrading/v3.0-to-v3.1.html is covered) |
|
I'm seeing the same Edit: |
|
No, it's not expected. I'll look at it in a little while. Very busy with other things right now. Thanks for letting us know. |
# Conflicts: # Makefile
Types.Is() is too strict for out needs. We want Is("object") to return
true for [object, "null"], but it's an exact match, replace this with
our own alternative that behaves as we want.
Resolved conflicts: - pkg/codegen/operations.go: kept new IsWebhook/WebhookName/IsCallback/CallbackName fields from feat branch; adopted upstream's tighter column alignment for the pre-existing IsAlias/AliasTarget/PathItemRef fields. - internal/test/anonymous_inner_hoisting/client.gen.go: kept ours; will be regenerated via make generate.
Resolved one conflict in pkg/codegen/codegen.go: upstream now uses the collectComponentTypes / collectOperationTypes / renderBoilerplate helper decomposition (from PR #2366). Kept that structure and changed the op- related helper calls to use allOps (paths + webhooks + callbacks) rather than ops, matching what the previous GenerateTypeDefinitions(t, spec, allOps, ...) call did, so webhook/callback-derived types continue to be emitted.
|
Want your agent to iterate on Greptile's feedback? Try greploops. |
…nymous-schemas
The new `output-options.generate-types-for-anonymous-schemas` flag is
supposed to name every inline schema that would otherwise become an
anonymous Go struct. For top-level array schemas whose items are an
inline object, this guarantee was not being met -- the generated type
was still `type Foo = []struct { ... }`.
The auto-hoist block inside `GenerateGoSchema` (schema.go:679) is
gated on `len(path) > 1` so top-level component schemas don't trigger
it (they already have their own names). The array-items recursive call
at schema.go:1027 passes `path` unchanged to `GenerateGoSchema`, so for
a top-level array schema the path length stays at 1 and the auto-hoist
declines to fire on the inline-object items. The existing array-item
hoist further down only fires for items with `additionalProperties` or
`oneOf`/`anyOf`, leaving plain inline objects to fall through.
Fix: extend the existing array-item hoist condition (schema.go:1032)
with a third clause matching the auto-hoist's predicate -- if the flag
is on and the items schema has `properties`, hoist it to a named
element type. The naming uses the same path + "Item" convention the
hoist has always used for unions and additionalProperties items, so a
top-level `Roles` array of inline-object items emits as
`type Roles_Item struct {...}` and `type Roles = []Roles_Item`.
For arrays nested inside other schemas (path length >= 2), the
auto-hoist already fires inside the items recursive call and sets
`RefType`, so the new clause's `RefType == ""` guard correctly skips
the second hoist -- no double-emission or naming conflict.
Regression coverage: the existing global anonymous_inner_hoisting
fixture gains a `Roles` component (array of inline objects) and a
`GET /roles` operation that returns it. The operation reference keeps
the default prune pass from stripping the component before codegen
sees it. A new test asserts that the element type is named
`Roles_Item` by constructing slice literals of the named type --
without the fix, the test does not compile because the generator
inlines the element struct.
Closes Greptile P1 review comment on pkg/codegen/schema.go:1027-1032.
…ity markers
OpenAPI 3.1 supports two equivalent idioms for expressing nullability
on a schema:
1. `type: ["string", "null"]` (the type-array idiom)
2. `anyOf: [{type: string}, {type: "null"}]` (the union idiom)
The first form has worked since the kin-openapi-3.1 branch was opened.
The second form crashed the code generator with:
error generating type for ...: unhandled Schema type: &[null]
`generateUnion` (schema.go) walks each `anyOf`/`oneOf` element and
calls `GenerateGoSchema` on it. For a bare `{"type": "null"}` branch,
the schema's Type slice is exactly `["null"]`. `schemaPrimaryType`
only strips "null" when the slice has more than one element, so the
single-element `["null"]` survives. Inside the primitive-type dispatch
in `oapiSchemaToGoType`, none of the type branches (string, integer,
number, boolean, array, object) handles "null" alone, so the function
falls through to the `unhandled Schema type` error.
Fix has three parts:
1. `isNullTypeSchema` helper: predicate for a bare `{"type": "null"}`
schema (type slice is exactly `["null"]`).
2. `schemaIsNullable` extension: in addition to checking whether the
outer type array includes "null", inspect anyOf/oneOf for null-only
branches. This lets the nullability flow through to call sites that
wrap the result in a pointer (or `nullable.Nullable[T]`) regardless
of which idiom the spec author used.
3. `generateUnion` collapse: after filtering null-only branches, if
exactly one effective branch remains and there's no discriminator,
treat the schema as that single branch rather than wrapping it in
a one-variant union type. Together with the schemaIsNullable
extension, this makes the two idioms produce identical Go shapes
-- `anyOf: [{type: string}, {type: "null"}]` and `type: ["string",
"null"]` both emit a `*string` field, not a `Pet_NicknameAnyOf`
wrapper struct with a `FromX`/`AsX` accessor API for a single
variant. The collapse is gated on the original anyOf/oneOf having
contained a null branch so behavior is unchanged for pre-existing
single-branch anyOf specs that may rely on the wrapper shape.
A small companion guard in `GenerateGoSchema` skips the
`GenStructFromSchema` overwrite when the collapse cleared the
struct-shaped fields (Properties, AdditionalProperties, UnionElements
all empty); without it the primitive GoType the collapse set would be
clobbered by an empty `struct {}` literal.
Regression coverage: two new properties on the OpenAPI 3.1 Pet schema
in internal/test/openapi31_nullable/ -- one using `anyOf: [{type:
string}, {type: "null"}]` and one using the matching `oneOf` form.
The test asserts the fields are `*string` (compile-time check via
`&nick` assignment where `nick` is a `string`) and that JSON
round-trip semantics match those of the existing type-array `nickname`
field.
…es, not raw element count The null-branch skip introduced in 2d6387e (`codegen: treat {"type": "null"} branches in anyOf/oneOf as nullability markers`) filters bare null-type branches out of the union loop, but the completeness check at the end still compared against `len(elements)` -- the raw input count, which includes the skipped null branches. A valid OpenAPI 3.1 nullable discriminated union like oneOf: - $ref: '#/components/schemas/Cat' - $ref: '#/components/schemas/Dog' - type: "null" discriminator: propertyName: kind would map the two real branches (Cat, Dog) but then fail generation with `discriminator: not all schemas were mapped` because the check saw `len(Mapping) == 2 < len(elements) == 3`. The null branch is a nullability marker, not a real variant that needs a discriminator mapping. Fix: compare against the `effectiveCount` already tracked for the collapse path -- the number of non-null branches actually processed by the loop. With this, nullable discriminated unions complete generation; non-nullable unions are unchanged (effectiveCount equals len(elements) when there are no null branches). Regression coverage: a `DiscriminatedPet` schema is added to the existing openapi31_nullable fixture as a Cat/Dog/null discriminated union, exposed via a new `favorite` property on Pet so it's not pruned. A new test asserts that JSON payloads for both Cat and Dog round-trip through the generated discriminated-union accessors; without the fix, codegen fails before the test can run. Closes Greptile P1 review comment on pkg/codegen/schema.go:1449.
…ords OpenAPI 3.1 replaced the 3.0 `format: binary` and `format: byte` idioms for declaring binary content with the JSON-Schema-aligned `contentMediaType` and `contentEncoding` keywords. The official 3.0-> 3.1 upgrade guide (https://learn.openapis.org/upgrading/v3.0-to-v3.1.html, "Update file upload descriptions") instructs users to migrate to the new keywords; without codegen support for them, a spec that follows the guide loses its file/binary typing -- fields that used to generate as `openapi_types.File` or `[]byte` silently fall through to bare `string`. Fix: in `oapiSchemaToGoType`'s string branch, synthesize a `format` value from `contentMediaType`/`contentEncoding` when the schema does not declare an explicit `format`. The mapping mirrors the 3.0 equivalents: - contentMediaType set (any value) -> format "binary" -> openapi_types.File - contentEncoding set (any value) -> format "byte" -> []byte - both set -> contentMediaType wins (file) An explicit `format` on the schema always wins over the synthesis, so a `format: date` field with a stray `contentMediaType` keeps its existing `openapi_types.Date` Go type and user `type-mapping` overlays continue to apply unchanged. Synthesis is gated on `globalState.is31` so 3.0 specs are untouched even if the parser happens to populate the fields. Regression coverage: new `internal/test/openapi31_content_keywords/` test exercises five cases via a single `FileUploadFields` component schema: contentMediaType -> File, contentMediaType: image/png -> File (non-octet-stream still routes to File), contentEncoding: base64 -> []byte, both keywords -> File (contentMediaType wins), explicit `format: date` overrides the synthesis. Assignments to typed zero values fail to compile if any case regresses, so the regression surfaces at `go build` time before any assertion runs. First of several follow-ups identified by the v3.0->v3.1 upgrade- guide audit; the remaining gap (empty-schema request bodies under binary content types like `application/octet-stream: {}`) is more invasive and will follow as a separate commit.
Go's encoding/json codec for `[]byte` uses standard padded base64
(RFC4648 section 4) unconditionally. The previous fix
(3ad1efd9, "handle 3.1 contentEncoding/contentMediaType file-upload
keywords") mapped *any* non-empty `contentEncoding` value to `[]byte`,
which silently corrupts every RFC4648 variant except standard base64:
- `contentEncoding: base64url`: URL-safe characters `-` and `_`
fail to decode, and re-marshal emits standard base64 instead of
the declared URL-safe form. The generated client/server stops
honoring the wire encoding declared in the spec.
- `contentEncoding: base32` / `base16`: the JSON []byte codec
expects/emits base64; spec-declared base32/base16 payloads round-
trip as garbage.
- `contentEncoding: quoted-printable`: same shape -- the JSON
[]byte codec doesn't speak QP at all.
Fix: narrow the synthesis to `contentEncoding == "base64"` only.
Other encodings fall through to the default `string` mapping,
preserving the raw declared wire form in user hands. Users who want
typed handling for the non-standard encodings can layer their own
codec at the application boundary or supply a `type-mapping`
override pointing at a wrapper type that implements an
encoding-aware MarshalJSON/UnmarshalJSON.
The `contentMediaType` mapping to `openapi_types.File` is unchanged;
that keyword expresses content semantics (raw binary of a media
type), not a wire encoding, and File is a generic blob type.
Regression coverage updates in
`internal/test/openapi31_content_keywords/`:
- `base64UrlField` moves out of `required` and the test asserts the
field type is `*string` (compile-time check via `&v`); a
regression that re-introduced the []byte synthesis would break
the build before any assertion ran.
- `base64Field` (with standard `contentEncoding: base64`) stays
`[]byte`, asserted by the dedicated TestContentEncodingBase64...
test.
Closes Greptile P1 review comment on pkg/codegen/schema.go:1149+.
* fix(codegen): use reflect.DeepEqual for ExclusiveMin/Max in MergeSchemas
kin-openapi's 3.1 work changed Schema.ExclusiveMin/Max from bool to
ExclusiveBound{Bool *bool; Value *float64}. Direct struct equality (!=)
still compiles but compares pointer addresses, not the wrapped values, so
two schemas with semantically identical bounds would incorrectly fail the
merge. Use reflect.DeepEqual for value-aware comparison.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): OpenAPI 3.1 foundation -- version awareness and nullable
This is Phase 1 of bringing 3.1 feature parity from oapi-codegen-exp into
mainline. It establishes the architectural patterns the rest of the work
will follow.
Version awareness:
* New globalState.is31 flag, populated once at Generate() entry from
swagger.IsOpenAPI31OrLater(). All version-aware logic lives in Go and
branches on this flag; templates remain version-blind and consume
pre-computed derived fields.
* Comment on the field explicitly forbids exposing it to TemplateFunctions
to prevent layering violations.
Nullable handling:
* New schemaIsNullable() helper that branches on globalState.is31:
3.0 reads s.Nullable; 3.1 reads s.Type.Includes("null").
* New schemaPrimaryType() helper strips "null" from the type slice in
3.1 mode so existing dispatch (`*Types.Is("string")` etc.) keeps
working for type:["string","null"].
* Four direct .Nullable read sites switched to the helper:
schema.go (property construction, array nullable, additionalProperties)
and operations.go (response header).
* merge_schemas.go's Nullable comparison routed through the helper; an
explanatory comment notes that type merging itself is NOT version
branched (equalTypes() handles single-element 3.0 and multi-element
3.1 type slices identically), and that result.Type already carries
any "null" entry forward in 3.1 mode.
Other:
* Adds OutputOptions.SkipEnumViaOneOf for Phase 2 (enum-via-oneOf
detection); declared now to keep the config schema stable across
phases.
* Removes the stale "OpenAPI 3.1.x is not yet supported" stderr
warning at cmd/oapi-codegen entry; partial 3.1 support now exists
and the message will become misleading as later phases land.
* Two path parameters in pkg/codegen/test_specs/x-go-type-import-pet.yaml
were missing the OpenAPI-required `required: true`. The omission was
incidental to what the test exercises (x-go-type-import); the spec is
now spec-conformant.
Tests:
* internal/test/openapi31_nullable/ -- two specs (3.0 nullable:true,
3.1 type:["string","null"]) generate into spec_3_0/ and spec_3_1/
subpackages. Instantiation tests (no string-matching) assert both
produce *string and round-trip JSON identically.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): OpenAPI 3.1 enum-via-oneOf detection
Phase 2 of the 3.1 backport. Detects the 3.1 enum-via-oneOf idiom and
routes it through the existing typed-enum codegen path so users see the
same Go shape they'd get from a plain `enum:` array.
The idiom (per OpenAPI 3.1 / JSON Schema 2020-12):
Severity:
type: integer # or "string"
oneOf:
- title: HIGH
const: 2
description: An urgent problem
- title: MEDIUM
const: 1
- title: LOW
const: 0
Generates:
type Severity int
const (
HIGH Severity = 2
LOW Severity = 0
MEDIUM Severity = 1
)
Implementation:
* New detectEnumViaOneOf() helper in pkg/codegen/schema.go. Trigger
conditions: globalState.is31 is true, !SkipEnumViaOneOf, schema's
primary type is "string" or "integer", every oneOf branch carries
non-empty Title AND non-nil Const, and no branch is itself a
composition or has Properties.
* GenerateGoSchema short-circuits between the AllOf branch and the
type dispatch: when the idiom matches, populate Schema.EnumValues
directly and register an AdditionalType for non-toplevel schemas
so the existing EnumDefinition collector picks them up. Negative
matches fall through to the standard oneOf union generator.
* No template changes -- the existing constants.tmpl renders the
typed enum + Valid() method via the standard EnumDefinition path.
* The OutputOptions.SkipEnumViaOneOf flag declared in Phase 1 is
now actually consulted here.
Tests:
* internal/test/enum_via_oneof/ -- ports the exp test fixture
(Severity int, Color string, MixedOneOf negative path). Five
instantiation tests (no string-matching) cover constant values,
JSON round-trips, and the negative path's compile-time alias
shape (a plain string is directly assignable to MixedOneOf,
proving it did NOT become a typed-enum newtype).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): OpenAPI 3.1 webhooks -- WebhookInitiator + WebhookReceiver
Phase 3 of the 3.1 backport. Generates client-side and server-side
support for `webhooks:` declared in OpenAPI 3.1 specs. The output
mirrors the existing Client / Server interfaces in shape but lives in
its own types, matching the conventions oapi-codegen-exp established.
Data model:
* OperationDefinition gains IsWebhook bool and WebhookName string.
* New WebhookOperationDefinitions() walks swagger.Webhooks and produces
OperationDefinitions in the same shape as path operations, minus the
path-alias and path-parameter logic (webhooks have no path template).
No version gate -- kin-openapi only populates the Webhooks field for
3.1+ documents, so 3.0 specs short-circuit on the empty map.
Generated client (WebhookInitiator):
* New pkg/codegen/templates/webhook-initiator.tmpl, modeled on
client.tmpl. Emits:
type WebhookInitiator struct{ Client; RequestEditors }
NewWebhookInitiator + WebhookInitiatorOption + WithWebhookHTTPClient
+ WithWebhookRequestEditorFn
WebhookInitiatorInterface
per-webhook methods (Op + OpWithBody) and request builders
(NewOpWebhookRequest + NewOpWebhookRequestWithBody)
* No stored Server -- the target URL is supplied per-call by the
caller (typically discovered from a subscription registration).
* Gated by Generate.Client (paired with the path Client).
Generated server (WebhookReceiver, stdhttp):
* New pkg/codegen/templates/stdhttp/std-http-webhook-receiver.tmpl.
Emits:
type WebhookReceiverInterface { HandleOpWebhook(w, r) for each }
type WebhookReceiverMiddlewareFunc func(http.Handler) http.Handler
OpWebhookHandler(si, middlewares...) http.Handler -- per-webhook
factory; caller mounts the returned http.Handler at the URL
path they advertise to subscribers.
* Methods take plain (w, r) -- the user reads/parses the body
themselves. Simpler than the full ServerInterfaceWrapper machinery;
parameter binding can be added later if the demand surfaces.
* Gated by Generate.StdHTTPServer (paired with the path stdhttp
ServerInterface). Other frameworks are left as follow-ups.
Wiring (codegen.go):
* allOps = ops + webhookOps is passed to OperationImports and
GenerateTypeDefinitions so webhook bodies and responses generate
their type definitions and imports normally.
* New GenerateWebhookInitiator and GenerateStdHTTPWebhookReceiver entry
points in operations.go, modeled on GenerateClient and
GenerateStdHTTPServer.
* Output ordering: webhook initiator follows ClientWithResponses;
webhook receiver follows the path StdHTTPServer block.
Tests (internal/test/webhooks/):
* spec.yaml declares a single petStatusChanged webhook with a JSON
PetStatusEvent body and a 204 response.
* TestWebhookRoundTrip mounts the generated factory against an
httptest.Server and fires a webhook via the initiator; asserts the
payload, method, and Content-Type round-trip intact.
* TestWebhookInitiatorRequestEditor verifies WithWebhookRequestEditorFn
applies on every outgoing request (parity with the path Client).
* TestWebhookReceiverMiddleware documents middleware composition order:
the factory wraps in `for _, mw := range middlewares { h = mw(h) }`,
so the LAST middleware passed becomes the outermost wrapper.
Example (examples/webhook/):
* Door badge reader scenario ported from oapi-codegen-exp/examples/webhook.
The server randomly generates enter/exit badge events every second
and fires the appropriate webhook to all registered subscribers; the
client subscribes to both event kinds via the path API, runs a local
HTTP receiver to handle inbound events, and deregisters cleanly on
exit. Demonstrates the full Client/Server + WebhookInitiator/Receiver
pairing in one example.
* Lives as a regular package under examples/go.mod -- no separate
module, no tools.go shim. `make generate` from examples/ regenerates
via the package's go:generate directive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): OpenAPI callbacks -- CallbackInitiator + CallbackReceiver
Phase 4 of the 3.1 backport. Generates client-side and server-side
support for `callbacks:` blocks nested under path operations. The output
mirrors WebhookInitiator / WebhookReceiver in shape -- separate types
named with the `Callback` prefix to match oapi-codegen-exp.
Callbacks have been part of the OpenAPI spec since 3.0, so this is NOT
version-gated: any spec that declares callbacks gets them generated.
Data model:
* OperationDefinition gains IsCallback bool and CallbackName string
(alongside the IsWebhook / WebhookName pair from Phase 3).
* New CallbackOperationDefinitions() walks
spec.Paths.<path>.<method>.Callbacks. Each leaf operation under the
callback's URL-expression key becomes one OperationDefinition with
IsCallback=true and CallbackName set to the outer map key (e.g.
"treePlanted"). The codegen does not interpret the URL expression
itself -- the caller of CallbackInitiator supplies the resolved
target URL at runtime, the same way it does for a webhook.
* Callback's internal map is private; iterate via cb.Keys() / cb.Value()
with sorted keys for deterministic output.
Generated client (CallbackInitiator):
* New pkg/codegen/templates/callback-initiator.tmpl. Same shape as the
Phase 3 webhook-initiator.tmpl with `Webhook` -> `Callback` rename:
type CallbackInitiator struct{ Client; RequestEditors }
NewCallbackInitiator + CallbackInitiatorOption
+ WithCallbackHTTPClient + WithCallbackRequestEditorFn
CallbackInitiatorInterface
per-callback methods (Op + OpWithBody) and request builders
(NewOpCallbackRequest + NewOpCallbackRequestWithBody)
* No stored Server -- targetURL is per-call.
* Gated only by Generate.Client + non-empty callbackOps.
Generated server (CallbackReceiver, stdhttp):
* New pkg/codegen/templates/stdhttp/std-http-callback-receiver.tmpl.
* WebhookReceiverInterface analog: Handle{Op}Callback(w, r) plus a
per-callback factory function {Op}CallbackHandler(si, middlewares...)
http.Handler.
* Gated only by Generate.StdHTTPServer + non-empty callbackOps.
Wiring (codegen.go):
* allOps is now `ops + webhookOps + callbackOps` so type-definition
and import passes see callback bodies / responses.
* Callback initiator output follows webhook initiator (so the order is
Client / ClientWithResponses / WebhookInitiator / CallbackInitiator).
* Callback receiver output follows webhook receiver under the
StdHTTPServer block.
* New GenerateCallbackInitiator and GenerateStdHTTPCallbackReceiver
template entry points in operations.go.
Tests (internal/test/callbacks/):
* spec.yaml deliberately uses `openapi: 3.0.3` to verify the codegen
does NOT gate callback emission on 3.1 (callbacks are 3.0+).
Models a tree-planting flow: parent operation PlantTree, callback
treePlanted with TreePlantingResult body.
* TestCallbackRoundTrip mounts the generated factory against an
httptest.Server and fires a callback via the initiator; asserts the
payload, method, and Content-Type round-trip intact.
* TestCallbackInitiatorRequestEditor verifies WithCallbackRequestEditorFn
applies on every outgoing request (parity with the path Client and
the WebhookInitiator).
Example (examples/callback/):
* Tree farm scenario ported from oapi-codegen-exp/examples/callback.
Server accepts PlantTree, returns 202, sleeps 1-5s, then fires the
treePlanted callback to the caller-supplied URL. Client opens an
ephemeral receiver, fires 10 plantings in sequence, and waits on a
channel until all 10 callbacks have arrived.
* Lives as a regular package under examples/go.mod -- no separate
module, no tools.go shim. Same flat layout as examples/webhook/.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): OpenAPI 3.1 polish -- examples in doc comments + const
Phase 5 of the 3.1 backport. Two small additive features that round
out the 3.1 surface; no template changes.
`examples:` (plural array) → Go doc comments:
* New describeWithExamples() helper in pkg/codegen/schema.go folds
example data into the description string used for generated doc
comments. Version-aware: 3.0 reads schema.Example (singular), 3.1
reads schema.Examples (plural array). Cross-version misuse is
documented as invalid input and the helper does not look at the
off-version field.
* Non-string examples are JSON-encoded so structured values render
readably; strings round-trip as themselves.
* Applied at the three sites where Description flows from the spec
schema into the generated Schema/Property: GenerateGoSchema's $ref
short-circuit, the main outSchema construction, and per-property
population during object walk.
`const:` → typed alias + singleton constant:
* The existing enum branch in GenerateGoSchema now also fires on
`(globalState.is31 && schema.Const != nil)`. A local enumSource
abstracts over the two cases so a scalar `const: X` schema emits the
same shape as `enum: [X]`: a typed alias plus a sanitized constant
derived from the value.
* Falls through the standard EnumDefinition pipeline -- existing
constants.tmpl renders the typed enum + Valid() method without
changes.
Paths optional in 3.1: no-op. Every swagger.Paths access site
(filter.go, prune.go, gather.go, operations.go, the new
WebhookOperationDefinitions and CallbackOperationDefinitions) already
nil-guards.
Tests (internal/test/openapi31_polish/):
* Pet.Name has a description plus two string examples; Pet.Lives has
one integer example. Status is a top-level `const: active` schema.
* TestStatusConstSchema (instantiation): `var s Status = Active`
compiles only because Status is a distinct named type, and asserts
the constant's value plus that Valid() returns true.
* TestPetExampleComments parses the generated Go source and walks the
AST to extract Pet's per-field doc comments, then asserts the
expected description and "Examples: ..." fragments. Doc comments
aren't runtime-introspectable, so AST inspection of the generated
file is the right granularity here -- not a brittle string-match
against the file as a whole.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(codegen): unify stdhttp webhook+callback receiver template, add param binding
Phase 6.1 of the 3.1 backport (kicking off the per-framework receiver
work). Sets up the prototype shape that the other 7 server-framework
receivers will follow.
Two changes that always have to land together:
1. Unify the two receiver templates into one parameterized template,
matching oapi-codegen-exp's structure:
* Replaces pkg/codegen/templates/stdhttp/std-http-webhook-receiver.tmpl
and pkg/codegen/templates/stdhttp/std-http-callback-receiver.tmpl
(deleted) with a single std-http-receiver.tmpl. The new template
takes a Prefix data field ("Webhook" or "Callback") and emits the
correct interface, middleware type, and per-op handler factory.
* New ReceiverTemplateData struct + NewReceiverTemplateData
constructor in operations.go feed the template.
* Single GenerateStdHTTPReceiver(t, prefix, ops) entry point;
callers in Generate() pass "Webhook" or "Callback".
2. Add query/header parameter binding to the receiver factory,
matching mainline's existing path-server middleware pattern.
The factory now takes an errHandler argument:
func PetStatusChangedWebhookHandler(
si WebhookReceiverInterface,
errHandler func(w http.ResponseWriter, r *http.Request, err error),
middlewares ...WebhookReceiverMiddlewareFunc,
) http.Handler
When operations have query/header params (rare for webhooks but
common for callbacks), the inline closure binds them into a typed
{Op}Params struct via runtime.BindQueryParameterWithOptions /
runtime.BindStyledParameterWithOptions and routes any error through
errHandler. errHandler may be nil; the default returns 400 with the
error message. When operations have no params, errHandler is
unused -- pass nil and the prior shape's behavior is preserved.
This is a breaking change to anyone using the Phase 3/4 generated
*Handler(si, middlewares...) shape. Acceptable since the Phase 3/4
surface is unreleased and only-this-branch.
New helper: OperationDefinition.SourceName() returns WebhookName when
IsWebhook, CallbackName when IsCallback. Templates use it to label
emitted handlers uniformly without branching on the source kind.
Existing call sites updated to pass nil for errHandler:
internal/test/webhooks/webhooks_test.go (3 places)
internal/test/callbacks/callbacks_test.go (2 places)
examples/webhook/client/main.go (2 places)
examples/callback/client/main.go (1 place)
The four affected gen files are regenerated. Other gen files in the
tree have unrelated drift from Phase 5's examples-in-doc-comments
feature (those don't touch the new receiver shape); a follow-up
`make generate` commit picks them up cleanly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* chore(gen): regenerate gen files to pick up Phase 5 examples-in-doc-comments
Phase 5 (eedf057) added the describeWithExamples() helper that folds
schema.Example / schema.Examples into the description string used for
generated Go doc comments. Existing test specs that already had
`example:` set on their schemas now produce richer doc comments.
This commit is purely the result of running `make generate` -- no
behavior change, only doc-comment additions like:
// Code The underlying http status code
+//
+// Example: 500
Code int32 `json:"code"`
Touched files:
examples/minimal-server/stdhttp/api/ping.gen.go
internal/test/externalref/petstore/externalref.gen.go
internal/test/issues/issue-1087/deps/deps.gen.go
internal/test/issues/issue-1168/api.gen.go
internal/test/issues/issue1561/issue1561.gen.go
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): chi webhook+callback receiver template
Phase 6.2 of the per-framework receiver work. Chi's path-handler
signature is identical to stdhttp's (`(w http.ResponseWriter, r
*http.Request)`), so the receiver template is structurally identical;
only the file path and Go entry point are new.
* New pkg/codegen/templates/chi/chi-receiver.tmpl -- a copy of the
Phase 6.1 unified stdhttp receiver template. Single template handles
both webhook and callback receivers via the Prefix data field.
* New GenerateChiReceiver() entry point in operations.go.
* In Generate(): two new blocks gated on Generate.ChiServer + non-empty
webhook/callback ops, mirroring the stdhttp blocks. Generated output
is written inside the existing chi-server WriteString block so all
chi-related output stays grouped.
* New internal/test/webhooks_chi/ -- compile-time assertion that the
chi receiver template produces valid Go. The runtime round-trip
behavior is already covered by internal/test/webhooks (stdhttp);
since chi shares the signature, that coverage transfers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): gorilla/mux webhook+callback receiver template
Phase 6.3. Gorilla shares stdhttp's `(w, r)` handler signature, so the
receiver template is structurally identical -- same approach as the
Phase 6.2 chi addition.
* New pkg/codegen/templates/gorilla/gorilla-receiver.tmpl
* New GenerateGorillaReceiver() entry point
* Wiring in Generate() gated on Generate.GorillaServer; output written
inside the existing gorilla-server WriteString block
* New internal/test/webhooks_gorilla/ as a compile-time assertion;
runtime round-trip behavior is covered by internal/test/webhooks
(signature is identical, so coverage transfers)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): echo (v4) webhook+callback receiver template
Phase 6.4. First framework with a non-stdhttp signature, requiring its
own bespoke template.
Echo's idioms:
* Method signature `Handle{Op}{Prefix}(ctx echo.Context, params...) error`
(echo v4 uses the non-pointer Context).
* Factory returns `echo.HandlerFunc`; middlewares are echo's native
`echo.MiddlewareFunc` (`func(echo.HandlerFunc) echo.HandlerFunc`).
* Parameter-binding errors are returned via echo.NewHTTPError so
echo's framework error chain reports them as 400. There's no
errHandler argument like the stdhttp factory has.
* Query params bind via ctx.QueryParam / ctx.QueryParams() (and
runtime.BindQueryParameterWithOptions for styled). Header params
read ctx.Request().Header.
* New pkg/codegen/templates/echo/echo-receiver.tmpl
* New GenerateEchoReceiver() entry point
* Wiring in Generate() gated on Generate.EchoServer; output written
inside the existing echo-server WriteString block
* New internal/test/webhooks_echo/ -- compile-time assertion that the
echo receiver template produces valid Go
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): echo (v5) webhook+callback receiver template
Phase 6.5. Echo v5 differs from v4 only in using `*echo.Context`
(pointer) -- otherwise the API surface (ctx.QueryParam, ctx.Request(),
echo.NewHTTPError, echo.HandlerFunc, echo.MiddlewareFunc) is identical.
The template is a `echo.Context` -> `*echo.Context` substitution from
the Phase 6.4 v4 template.
* New pkg/codegen/templates/echo/v5/echo-receiver.tmpl
* New GenerateEcho5Receiver() entry point
* Wiring in Generate() gated on Generate.Echo5Server; output written
inside the existing echo5-server WriteString block
No internal/test/webhooks_echo5/ directory: echo v5 is not in
internal/test/go.mod (the existing echov5 fixture lives in its own
sub-module under internal/test/parameters/echov5/), so adding a
webhook test would require either invasive dep changes or another
sub-module. The Phase 6.4 echo v4 test exercises the same template
shape end-to-end -- inspection of the v5 codegen output confirms only
the Context pointer-ness changes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): gin webhook+callback receiver template
Phase 6.6. Gin's idioms:
* Method signature `Handle{Op}{Prefix}(c *gin.Context, params...)`
with no error return -- gin reports errors via the context.
* Factory returns `gin.HandlerFunc`. Per-handler middleware is NOT
generated (gin's idiom prefers route-group / engine .Use()
composition); users mount middleware at the engine level.
* Parameter-binding errors abort the request with
c.JSON(http.StatusBadRequest, gin.H{"error": "..."}).
* Query params bind via c.Query / c.Request.URL.Query() (and
runtime.BindQueryParameterWithOptions for styled). Header params
read c.Request.Header.
* New pkg/codegen/templates/gin/gin-receiver.tmpl
* New GenerateGinReceiver() entry point
* Wiring in Generate() gated on Generate.GinServer
* New internal/test/webhooks_gin/ -- compile-time assertion
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): fiber (v2) webhook+callback receiver template
Phase 6.7. Fiber's idioms:
* Method signature `Handle{Op}{Prefix}(c *fiber.Ctx, params...) error`
(mainline pins fiber v2 -> *fiber.Ctx pointer; v3 changes to a
non-pointer interface but is not pinned in mainline).
* Factory returns `fiber.Handler` (alias for `func(*fiber.Ctx) error`).
* Parameter-binding errors are returned via `fiber.NewError(
fiber.StatusBadRequest, ...)` so fiber's error chain reports them
as 400.
* Query-param binding via `url.ParseQuery(string(c.Request().URI().
QueryString()))` to get url.Values for runtime.BindQueryParameter
WithOptions, plus `c.Query("name")` for direct passthrough/JSON.
* Header params read `c.GetReqHeaders()` which returns
map[string][]string.
* Per-handler middleware is NOT generated; users compose middleware
via fiber.App.Use().
* New pkg/codegen/templates/fiber/fiber-receiver.tmpl
* New GenerateFiberReceiver() entry point
* Wiring in Generate() gated on Generate.FiberServer
* New internal/test/webhooks_fiber/ -- compile-time assertion
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(codegen): iris webhook+callback receiver template
Phase 6.8, completing the per-framework receiver fan-out. Iris's
idioms:
* Method signature `Handle{Op}{Prefix}(ctx iris.Context, params...)`
with no error return -- iris reports errors via the context.
* Factory returns `iris.Handler`. Per-handler middleware is NOT
generated; iris's idiom prefers app.Use() / Party-level composition.
* Parameter-binding errors set ctx.StatusCode(400) plus
ctx.WriteString(reason) and return.
* Query params bind via ctx.URLParam("name") for direct values, and
runtime.BindQueryParameterWithOptions with ctx.Request().URL.Query()
for styled. Header params read ctx.Request().Header.
* New pkg/codegen/templates/iris/iris-receiver.tmpl
* New GenerateIrisReceiver() entry point
* Wiring in Generate() gated on Generate.IrisServer
* New internal/test/webhooks_iris/ -- compile-time assertion
All seven server frameworks (stdhttp/chi/gorilla/echo/echo5/gin/
fiber/iris) now emit framework-native webhook+callback receiver
interfaces alongside their path-server interfaces, gated by the
existing per-framework Generate.* flags.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* refactor(test): consolidate webhook tests into internal/test/webhooks/<framework>/
Final Phase 6 cleanup. Reorganizes the seven flat per-framework
webhook test directories under a single internal/test/webhooks/
parent so they group together and the layout reflects the
framework-fan-out done in Phases 6.1-6.8.
Layout change:
internal/test/webhooks/ (the original stdhttp tests)
internal/test/webhooks_chi/ }
internal/test/webhooks_echo/ }
internal/test/webhooks_fiber/ } flat per-framework dirs
internal/test/webhooks_gin/ }
internal/test/webhooks_gorilla/ }
internal/test/webhooks_iris/ }
becomes:
internal/test/webhooks/
├── spec.yaml (shared by all subdirs)
├── stdhttp/ (original round-trip tests)
├── chi/
├── echo/
├── fiber/
├── gin/
├── gorilla/
└── iris/
Per-subdir changes:
* Package name simplified from `webhooks` / `webhooks_<framework>` to
just `<framework>` (matches dir name; package-vs-import name overlap
with framework packages like chi/v5 is harmless since Go's local
package name is implicit).
* Generated file uniformly named `webhooks.gen.go` (was
`webhooks_<framework>.gen.go` in flat dirs).
* config.yaml `package:` and `output:` directives updated; schema
reference path bumped one level deeper.
* doc.go `package` and prose updated; `go:generate` now points at the
shared `../spec.yaml`.
Spec deduplication:
* The seven copies of spec.yaml (functionally identical, only
title/description varied) collapse to one shared file at
internal/test/webhooks/spec.yaml. Each subdir's go:generate
references it via `../spec.yaml`.
The round-trip test in stdhttp/webhooks_test.go has its package
declaration updated to `package stdhttp`.
No code or template changes outside the moves; same gen output up to
package name.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(codegen): address greptile review findings on Phase 6 work
Two findings from automated review:
P1 -- Non-deterministic map iteration in CallbackOperationDefinitions
(pkg/codegen/operations.go).
The inner loop walked `pathItem.Operations()` directly. When a path
declares callbacks on multiple HTTP methods (e.g. both POST and PUT),
the generated output would differ between runs because Go map
iteration is randomized. Fix: copy keys into a slice and walk via
SortedMapKeys, matching the pattern used by every other similar loop
in the codebase (including WebhookOperationDefinitions immediately
above).
P2 -- Hidden global-state dependency in version-aware helpers
(pkg/codegen/schema.go).
schemaIsNullable, schemaPrimaryType, describeWithExamples, and
detectEnumViaOneOf all read globalState.is31 implicitly, with no
indication at the function signature that callers must invoke them
only after Generate() has initialized the global. Fix: add an
explicit "Precondition" line to each helper's doc comment so callers
(including future refactors that move schema resolution earlier) are
aware of the dependency. The signatures themselves are not changed --
threading the version flag through every call site would be an
intrusive refactor for a problem better described in prose.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Pull in kin-openapi v0.136.0
Pulls in the official kin release which adds OpenAPI 3.1 support, and
updates CI to run only on Go 1.26
* fix(test): address golangci-lint errcheck + ST1023 findings
Two distinct lint issues across four test files.
errcheck (5 sites): defer / direct calls to r.Body.Close() and
resp.Body.Close() ignored their error returns. Standard fix:
* `defer r.Body.Close()` -> `defer func() { _ = r.Body.Close() }()`
* `resp.Body.Close()` (non-deferred) -> `_ = resp.Body.Close()`
internal/test/callbacks/callbacks_test.go (1)
internal/test/webhooks/stdhttp/webhooks_test.go (4)
ST1023 (2 sites): "should omit type X from declaration; it will be
inferred from the right-hand side". Two different stories.
* internal/test/openapi31_polish/openapi31_polish_test.go: `var s
Status = Active` is genuinely redundant -- `Active` is declared
`const Active Status = "active"`, so type inference gives `s` the
type `Status` already. Switched to `s := Active`. Comment updated
to explain the still-meaningful compile-time check (if Active had
been emitted untyped, `s := Active` would not preserve the Status
type).
* internal/test/enum_via_oneof/enum_via_oneof_test.go: `var m
MixedOneOf = s` (where `s` is a plain string) IS the test. The
redundancy is intentional -- if MixedOneOf were a `type MixedOneOf
string` newtype rather than the alias `type MixedOneOf = string`,
this assignment would fail to compile. Letting inference give `m`
the type `string` would lose the property under test. Added
`//nolint:staticcheck` with the rationale inline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* Fix lint issues and CI versioning
* Rebase and downgrade Go
Now that kin doesn't require Go 1.26, downgrade to Go 1.25 and
rebase changes from main.
* Extend hoisting tests
Add OpenApi 3.1 top level schema sources to the hoisting test.
* Fix issues due to rebasing onto main
* Replace kin Types.Is() usage with our own
Types.Is() is too strict for out needs. We want Is("object") to return
true for [object, "null"], but it's an exact match, replace this with
our own alternative that behaves as we want.
* Add test coverage for nullables
* codegen: hoist inline-object array items under generate-types-for-anonymous-schemas
The new `output-options.generate-types-for-anonymous-schemas` flag is
supposed to name every inline schema that would otherwise become an
anonymous Go struct. For top-level array schemas whose items are an
inline object, this guarantee was not being met -- the generated type
was still `type Foo = []struct { ... }`.
The auto-hoist block inside `GenerateGoSchema` (schema.go:679) is
gated on `len(path) > 1` so top-level component schemas don't trigger
it (they already have their own names). The array-items recursive call
at schema.go:1027 passes `path` unchanged to `GenerateGoSchema`, so for
a top-level array schema the path length stays at 1 and the auto-hoist
declines to fire on the inline-object items. The existing array-item
hoist further down only fires for items with `additionalProperties` or
`oneOf`/`anyOf`, leaving plain inline objects to fall through.
Fix: extend the existing array-item hoist condition (schema.go:1032)
with a third clause matching the auto-hoist's predicate -- if the flag
is on and the items schema has `properties`, hoist it to a named
element type. The naming uses the same path + "Item" convention the
hoist has always used for unions and additionalProperties items, so a
top-level `Roles` array of inline-object items emits as
`type Roles_Item struct {...}` and `type Roles = []Roles_Item`.
For arrays nested inside other schemas (path length >= 2), the
auto-hoist already fires inside the items recursive call and sets
`RefType`, so the new clause's `RefType == ""` guard correctly skips
the second hoist -- no double-emission or naming conflict.
Regression coverage: the existing global anonymous_inner_hoisting
fixture gains a `Roles` component (array of inline objects) and a
`GET /roles` operation that returns it. The operation reference keeps
the default prune pass from stripping the component before codegen
sees it. A new test asserts that the element type is named
`Roles_Item` by constructing slice literals of the named type --
without the fix, the test does not compile because the generator
inlines the element struct.
Closes Greptile P1 review comment on pkg/codegen/schema.go:1027-1032.
* codegen: treat `{"type": "null"}` branches in anyOf/oneOf as nullability markers
OpenAPI 3.1 supports two equivalent idioms for expressing nullability
on a schema:
1. `type: ["string", "null"]` (the type-array idiom)
2. `anyOf: [{type: string}, {type: "null"}]` (the union idiom)
The first form has worked since the kin-openapi-3.1 branch was opened.
The second form crashed the code generator with:
error generating type for ...: unhandled Schema type: &[null]
`generateUnion` (schema.go) walks each `anyOf`/`oneOf` element and
calls `GenerateGoSchema` on it. For a bare `{"type": "null"}` branch,
the schema's Type slice is exactly `["null"]`. `schemaPrimaryType`
only strips "null" when the slice has more than one element, so the
single-element `["null"]` survives. Inside the primitive-type dispatch
in `oapiSchemaToGoType`, none of the type branches (string, integer,
number, boolean, array, object) handles "null" alone, so the function
falls through to the `unhandled Schema type` error.
Fix has three parts:
1. `isNullTypeSchema` helper: predicate for a bare `{"type": "null"}`
schema (type slice is exactly `["null"]`).
2. `schemaIsNullable` extension: in addition to checking whether the
outer type array includes "null", inspect anyOf/oneOf for null-only
branches. This lets the nullability flow through to call sites that
wrap the result in a pointer (or `nullable.Nullable[T]`) regardless
of which idiom the spec author used.
3. `generateUnion` collapse: after filtering null-only branches, if
exactly one effective branch remains and there's no discriminator,
treat the schema as that single branch rather than wrapping it in
a one-variant union type. Together with the schemaIsNullable
extension, this makes the two idioms produce identical Go shapes
-- `anyOf: [{type: string}, {type: "null"}]` and `type: ["string",
"null"]` both emit a `*string` field, not a `Pet_NicknameAnyOf`
wrapper struct with a `FromX`/`AsX` accessor API for a single
variant. The collapse is gated on the original anyOf/oneOf having
contained a null branch so behavior is unchanged for pre-existing
single-branch anyOf specs that may rely on the wrapper shape.
A small companion guard in `GenerateGoSchema` skips the
`GenStructFromSchema` overwrite when the collapse cleared the
struct-shaped fields (Properties, AdditionalProperties, UnionElements
all empty); without it the primitive GoType the collapse set would be
clobbered by an empty `struct {}` literal.
Regression coverage: two new properties on the OpenAPI 3.1 Pet schema
in internal/test/openapi31_nullable/ -- one using `anyOf: [{type:
string}, {type: "null"}]` and one using the matching `oneOf` form.
The test asserts the fields are `*string` (compile-time check via
`&nick` assignment where `nick` is a `string`) and that JSON
round-trip semantics match those of the existing type-array `nickname`
field.
* codegen: compare discriminator mapping count against effective branches, not raw element count
The null-branch skip introduced in 2d6387e (`codegen: treat
{"type": "null"} branches in anyOf/oneOf as nullability markers`)
filters bare null-type branches out of the union loop, but the
completeness check at the end still compared against `len(elements)`
-- the raw input count, which includes the skipped null branches.
A valid OpenAPI 3.1 nullable discriminated union like
oneOf:
- $ref: '#/components/schemas/Cat'
- $ref: '#/components/schemas/Dog'
- type: "null"
discriminator:
propertyName: kind
would map the two real branches (Cat, Dog) but then fail generation
with `discriminator: not all schemas were mapped` because the check
saw `len(Mapping) == 2 < len(elements) == 3`. The null branch is a
nullability marker, not a real variant that needs a discriminator
mapping.
Fix: compare against the `effectiveCount` already tracked for the
collapse path -- the number of non-null branches actually processed
by the loop. With this, nullable discriminated unions complete
generation; non-nullable unions are unchanged (effectiveCount equals
len(elements) when there are no null branches).
Regression coverage: a `DiscriminatedPet` schema is added to the
existing openapi31_nullable fixture as a Cat/Dog/null discriminated
union, exposed via a new `favorite` property on Pet so it's not
pruned. A new test asserts that JSON payloads for both Cat and Dog
round-trip through the generated discriminated-union accessors;
without the fix, codegen fails before the test can run.
Closes Greptile P1 review comment on pkg/codegen/schema.go:1449.
* codegen: handle 3.1 contentEncoding/contentMediaType file-upload keywords
OpenAPI 3.1 replaced the 3.0 `format: binary` and `format: byte`
idioms for declaring binary content with the JSON-Schema-aligned
`contentMediaType` and `contentEncoding` keywords. The official 3.0->
3.1 upgrade guide
(https://learn.openapis.org/upgrading/v3.0-to-v3.1.html, "Update
file upload descriptions") instructs users to migrate to the new
keywords; without codegen support for them, a spec that follows the
guide loses its file/binary typing -- fields that used to generate as
`openapi_types.File` or `[]byte` silently fall through to bare
`string`.
Fix: in `oapiSchemaToGoType`'s string branch, synthesize a `format`
value from `contentMediaType`/`contentEncoding` when the schema does
not declare an explicit `format`. The mapping mirrors the 3.0
equivalents:
- contentMediaType set (any value) -> format "binary" -> openapi_types.File
- contentEncoding set (any value) -> format "byte" -> []byte
- both set -> contentMediaType wins (file)
An explicit `format` on the schema always wins over the synthesis, so
a `format: date` field with a stray `contentMediaType` keeps its
existing `openapi_types.Date` Go type and user `type-mapping`
overlays continue to apply unchanged. Synthesis is gated on
`globalState.is31` so 3.0 specs are untouched even if the parser
happens to populate the fields.
Regression coverage: new `internal/test/openapi31_content_keywords/`
test exercises five cases via a single `FileUploadFields` component
schema: contentMediaType -> File, contentMediaType: image/png -> File
(non-octet-stream still routes to File), contentEncoding: base64 ->
[]byte, both keywords -> File (contentMediaType wins), explicit
`format: date` overrides the synthesis. Assignments to typed zero
values fail to compile if any case regresses, so the regression
surfaces at `go build` time before any assertion runs.
First of several follow-ups identified by the v3.0->v3.1 upgrade-
guide audit; the remaining gap (empty-schema request bodies under
binary content types like `application/octet-stream: {}`) is more
invasive and will follow as a separate commit.
* codegen: only map contentEncoding to []byte for standard base64
Go's encoding/json codec for `[]byte` uses standard padded base64
(RFC4648 section 4) unconditionally. The previous fix
(3ad1efd9, "handle 3.1 contentEncoding/contentMediaType file-upload
keywords") mapped *any* non-empty `contentEncoding` value to `[]byte`,
which silently corrupts every RFC4648 variant except standard base64:
- `contentEncoding: base64url`: URL-safe characters `-` and `_`
fail to decode, and re-marshal emits standard base64 instead of
the declared URL-safe form. The generated client/server stops
honoring the wire encoding declared in the spec.
- `contentEncoding: base32` / `base16`: the JSON []byte codec
expects/emits base64; spec-declared base32/base16 payloads round-
trip as garbage.
- `contentEncoding: quoted-printable`: same shape -- the JSON
[]byte codec doesn't speak QP at all.
Fix: narrow the synthesis to `contentEncoding == "base64"` only.
Other encodings fall through to the default `string` mapping,
preserving the raw declared wire form in user hands. Users who want
typed handling for the non-standard encodings can layer their own
codec at the application boundary or supply a `type-mapping`
override pointing at a wrapper type that implements an
encoding-aware MarshalJSON/UnmarshalJSON.
The `contentMediaType` mapping to `openapi_types.File` is unchanged;
that keyword expresses content semantics (raw binary of a media
type), not a wire encoding, and File is a generic blob type.
Regression coverage updates in
`internal/test/openapi31_content_keywords/`:
- `base64UrlField` moves out of `required` and the test asserts the
field type is `*string` (compile-time check via `&v`); a
regression that re-introduced the []byte synthesis would break
the build before any assertion ran.
- `base64Field` (with standard `contentEncoding: base64`) stays
`[]byte`, asserted by the dedicated TestContentEncodingBase64...
test.
Closes Greptile P1 review comment on pkg/codegen/schema.go:1149+.
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fixes: #373
This is a back-port of my OpenAPI 3.1 work in the oapi-codegen-exp repo.
Since kin-openapi now requires Go 1.26.2, merging this requires some updates to our build, and also increasing our Go version dependency. That will require a bit of cleanup in our sub-modules and lots of them can be simplified. For now, I've done the minimal set of work in this branch to get it to compile.
OpenAPI 3.1 idioms suported: