OpenAPI 3.1, webhooks, callbacks, and a lot of polish
This is a big one. After a long stretch of internal refactoring across the last couple of releases, we've been able to land some features that have been on the wishlist for years, most notably initial OpenAPI 3.1 support. As always, the full, automatically-generated changelog is at the bottom, and the sections below call out the things you'll actually want to read before upgrading.
When this project was originally released, it supported a narrow set of OpenAPI features, and over time, it has grown from generating code for hand crafted, structurally tight specifications, to very complex, and often messy specifications. I don't think we can ever handle every specification, however, over time, we're going to give users control over as many behaviors as possible, so that our assumptions, which can't be right for everyone, are configurable. More changes of this form will continue to come in the upcoming releases.
Before you upgrade
A couple of housekeeping notes up front:
- Go 1.25 is now required. If you're not there yet, you'll need to bump your toolchain before pulling this in. We had to update to 1.25 to pull in a kin-openapi that supports OpenAPI 3.1
- Use generated code with
runtimev1.6.0 or newer. Several features in this release (the newDurationtype, escaped-path-parameter handling, typed response headers) rely on functionality that landed in the runtime, so make sure you're ongithub.com/oapi-codegen/runtimev1.6.0+ when you regenerate.
☢️ Breaking changes
We try hard to avoid breaking changes, and when we can't, to make them narrow or configurable.
Trailing-slash routes on the net/http server no longer act as catch-alls (#2460)
This one only affects the standard-library net/http server (the one built on ServeMux). It's a correctness fix, which is why we decided to just do it rather than hide it behind a flag.
Here's the problem: a ServeMux pattern ending in / matches the entire subtree beneath it, but an OpenAPI path ending in / means exactly that path and nothing deeper. So we were both mismatching the spec's semantics and, worse, panicking at registration time when two such patterns overlapped ambiguously. We now anchor any trailing-slash path with {$} so it matches only what the spec says it should.
The result: if your spec has trailing-slash paths, requests to deeper URLs that used to get swallowed by the catch-all will now correctly return 404. (ServeMux still issues its usual 307 redirect from the un-slashed path to the canonical one.) If you were relying on the old subtree behavior, that was never what your spec actually declared.
Security scopes are no longer emitted by default (#2440)
Generated servers used to emit per-scheme context key types (bearerAuthContextKey and friends), scope constants (BearerAuthScopes), and per-operation context stores that flattened your spec's security requirements into the request context. We've stopped emitting these by default.
The reason is that this machinery is fundamentally broken: it can't represent alternative (OR), combined (AND), or anonymous ({}) security requirements, so it quietly encourages people to build authorization logic on top of a representation that doesn't actually capture what the spec says. Authentication and authorization belong in the request validation middleware, which evaluates the real security requirements directly.
If you genuinely need the old emission back, there's an opt-in flag:
compatibility:
enable-auth-scopes-on-context: trueWe'd encourage you to migrate to the validation middleware instead, but the flag is there if you need a bridge.
🎉 Notable changes
OpenAPI 3.1 support (#2336)
Finally, after several years of requests, we've been able to close #373, adding OpenAPI 3.1 support. We've added support for callbacks and webhooks, and we support several OpenAPI 3.1 idioms, such as flexible enums via oneOf, as well as type: [T, "null"]-style nullability. This is initial support, so we'd love to hear about the specs it doesn't yet handle well.
While oapi-codegen would love to see an increase in sponsorship to make the project more sustainable, if we had to choose, we'd prefer to see that money go upstream to kin-openapi, which is the OpenAPI library that powers us and a large part of the Go ecosystem.
We're working to sponsor Pierre, the solo kin-openapi maintainer, with a significant portion of our own funds, and really hope that y'all consider sponsorship to support the important work that he does.
The OpenAPI 3.1 support in kin-openapi wouldn't have been possible without Pierre's maintainership, and a number of external contributors.
Optionally hoist anonymous schemas into named types (#2366)
You can now opt in to having every anonymous, inline schema hoisted into a top-level named type, with a name derived from its location (path) in the spec. If you've ever wanted a real, referenceable Go type for some deeply-nested inline object instead of an anonymous struct, this is for you.
We now validate the spec before generating (#2435)
oapi-codegen has always been extremely permissive about the specs it accepts, which is great until a malformed spec leads to non-compiling generated code and a confusing debugging session. We now run a validation pass over the spec before code generation, which lets us catch a class of errors that are hard to detect during codegen and abort early with a meaningful error message instead of emitting broken Go. As a bonus, this makes us considerably more resilient to malicious or garbage input.
Handlers are now registered in spec order (#2465, #2477)
Some routers make matching decisions based on the order in which handlers are registered. To make behavior predictable and match your intent, we now emit route registrations in the same order the paths appear in your spec, so you have control of the order by controlling the order in which you declare them. To go back to the previous behavior, add this to your configuration:
compatibility:
sort-handler-registrations: trueformat: duration now maps to a real Duration type (#2458)
A type: string, format: duration schema used to fall through to a plain Go string, leaving all the parsing and validation up to you. The runtime now ships an RFC 3339 Duration type (added in runtime v1.5.0), and the default type mapping resolves format: duration to it, right alongside the other formats like date, uuid, email, and binary.
This does change the generated type for specs already using format: duration. If you'd rather keep the old plain-string behavior, map the format back explicitly in your config:
output-options:
type-mapping:
string:
formats:
duration:
type: stringThe rest of the notables and new features
A quick rundown of the other behavior-affecting changes worth knowing about:
- Fiber v3 support (#2431, thanks @dillonbuchanan-maia-tech) — codegen now targets the stable Fiber v3, in addition to v2.
- Anonymous security alternatives are allowed (#2432, thanks @kriptoburak) — an empty
{}entry in asecuritylist (i.e. "auth optional here") is now handled correctly. - Response models expose schema-extraction helpers (#2379, thanks @rd-andreas-tollkoetter) — added functions on response models to make it easier to pull schemas back out.
deprecatedGodoc is emitted correctly (#2405, thanks @jamietanna) — deprecation markers now land on the right declarations so your tooling actually flags them.- Better generated client Godoc (#2407, thanks @jamietanna) — improved documentation comments on generated client code.
- Escaped-path-parameter handling (#2457) — generated code now tells the runtime whether the router delivers already-escaped path parameter values, fixing a class of decoding bugs (this is part of why you want runtime v1.6.0+).
- Typed response headers (#2462, #2463) — response headers are now parsed into typed fields on the client response wrappers.
There's a great deal more in the way of bug fixes below — this release fixes a large batch of long-standing issues in the aggregate-type (allOf/anyOf/oneOf), discriminator, and external-$ref code paths. Thank you to everyone who reported issues and sent PRs.
☢️ Breaking changes
- Anchor trailing-slash stdhttp routes with {$} to match OpenAPI semantics (#2460) @mromaszewicz
- Deprecate ctx based security scopes (#2440) @mromaszewicz
🎉 Notable changes
- Register handlers in spec order (#2465) @mromaszewicz
- Map format: duration to the runtime's RFC 3339 Duration type (#2458) @mromaszewicz
- Codegen: add fiber v3 support (rebased on main, fiber v3 stable) (#2431) @dillonbuchanan-maia-tech
- fix: allow anonymous security alternatives (#2432) @kriptoburak
- Validate OpenAPI spec before codegen (#2435) @mromaszewicz
- Initial OpenAPI 3.1 support based on kin-openapi (#2336) @mromaszewicz
- Add optional type generation for inline schemas (#2366) @mromaszewicz
- fix: correctly set deprecated Godoc where necessary (#2405) @jamietanna
- Add functions to response models for schema-extraction (#2379) @rd-andreas-tollkoetter
- Update Go to 1.25.9, clean up modules, and update dependencies (#2355) @mromaszewicz
🚀 New features and improvements
- Map format: duration to the runtime's RFC 3339 Duration type (#2458) @mromaszewicz
- Codegen: add fiber v3 support (rebased on main, fiber v3 stable) (#2431) @dillonbuchanan-maia-tech
- Validate OpenAPI spec before codegen (#2435) @mromaszewicz
- fix: qualify response refs from externally-ref'd path items (#2421) @mromaszewicz
- Initial OpenAPI 3.1 support based on kin-openapi (#2336) @mromaszewicz
- Add optional type generation for inline schemas (#2366) @mromaszewicz
- fix: correctly set deprecated Godoc where necessary (#2405) @jamietanna
- Add functions to response models for schema-extraction (#2379) @rd-andreas-tollkoetter
- feat(client): improve generated Godoc (#2407) @jamietanna
🐛 Bug fixes
- Add sort-handler-registrations flag to opt out of spec-order registra… (#2477) @mromaszewicz
- Name shared path parameter helper types once, hashing cross-path collisions (#2476) @mromaszewicz
- feat(#1726): add literal colon support for gin, echo, fiber (#1879) @cosban
- Fix ValueByDiscriminator() with external refs (#2474) @mromaszewicz
- Fix allOf/nullable detection and recursion (#2473) @mromaszewicz
- Fix nested allOf/anyOf merging (#2471) @mromaszewicz
- Name path-level parameter helper types per operation (#2466) @mromaszewicz
- Register handlers in spec order (#2465) @mromaszewicz
- Stringify non-string text/plain responses in strict servers (#2464) @mromaszewicz
- Qualify external header schema refs in generated response headers (#2463) @mromaszewicz
- Parse response headers into typed fields on client response wrappers (#2462) @mromaszewicz
- Anchor trailing-slash stdhttp routes with {$} to match OpenAPI semantics (#2460) @mromaszewicz
- Tell the runtime whether routers deliver escaped path parameter values (#2457) @mromaszewicz
- Add regression test for enum values containing backslashes (#2454) @mromaszewicz
- Fix discriminator type on unions (#2453) @mromaszewicz
- Share security scheme context keys across import-mapped packages (#2452) @mromaszewicz
- fix: render Godoc correctly for renamed + union types (#2451) @jamietanna
- Bring all routers to parity with each other (#2445) @mromaszewicz
- fix: bind only the request body in echo v5 strict handlers (#2444) @mromaszewicz
- fix: bind to body (#2063) @SeaRoll
- fix: emit case clauses for bodyless responses to prevent default catch-all unmarshal error (#2427) @lwc
- fiber: fix extra brace in optional pass-through cookie binding (#2443) @mromaszewicz
- fix: allow anonymous security alternatives (#2432) @kriptoburak
- Handle pure nullable types (#2437) @mromaszewicz
- Make sure to escape user strings (#2433) @mromaszewicz
- Don't emit enum constants for array-typed schemas (#2424) @mromaszewicz
- fix: honour external package's Go name for direct response refs (#2423) @mromaszewicz
- Unify emitter/consumer mediatype filter (#2420) @mromaszewicz
- Bump runtime to v1.4.2 (#2419) @mromaszewicz
- fix: use raw enum values for cross-enum conflict detection (#2390) @natalie-o-perret
- fix: correctly generate consistent media type for type aliases (#2411) @jamietanna
- fix: correctly set deprecated Godoc where necessary (#2405) @jamietanna
- Improve string escaping and add tests (#2396) @mromaszewicz
- Extend deepObject testing to include unicode (#2381) @mromaszewicz
- Don't generate constants for undefined scopes (#2372) @mromaszewicz
- fix: properly handle quotes in content-type when generating server code (#1929) @viktorasm
- Fix: Respect parent properties in case of merged schemas by allOf (#1415) @musaprg
📝 Documentation updates
- Reject import-mapping keys that are JSON pointers (#2461) @mromaszewicz
- Refactor documentation (#2272) @mromaszewicz
- refactor petstore example (#2277) @mromaszewicz
👻 Maintenance
- Update echo V5 petstore example (#2442) @mromaszewicz
- chore: refactor all tests by category (#2260) @mromaszewicz
- Add Greptile logo to local logo cache (#2425) @mromaszewicz
- chore: downgrade Go requirement to 1.25.0 (#2413) @jamietanna
- test: use Petstore spec from GitHub (#2400) @jamietanna
- Echo V5 now uses the generic test framework (#2382) @mromaszewicz
- lint: migrate to
golangci-lint's install script onmain(#2376) @jamietanna - chore(renovate): correctly run
make tidy(#2374) @jamietanna - Update Greptile directives (#2361) @mromaszewicz
📦 Dependency updates
33 changes
- chore(deps): update actions/setup-go action to v7 (.github/workflows) (#2469) @renovate[bot]
- fix(deps): update module github.com/getkin/kin-openapi to v0.142.0 (go.mod) (#2450) @renovate[bot]
- chore(deps): update golang/govulncheck-action action to v1.1.0 (.github/workflows) (#2447) @renovate[bot]
- fix(deps): update module github.com/getkin/kin-openapi to v0.141.0 (go.mod) (#2446) @renovate[bot]
- fix(deps): update module golang.org/x/tools to v0.48.0 (go.mod) (#2404) @renovate[bot]
- fix(deps): update module golang.org/x/text to v0.40.0 (go.mod) (#2402) @renovate[bot]
- fix(deps): update module golang.org/x/mod to v0.38.0 (go.mod) (#2401) @renovate[bot]
- chore(deps): update release-drafter/release-drafter action to v7.5.1 (.github/workflows) (#2388) @renovate[bot]
- fix(deps): update module github.com/speakeasy-api/openapi to v1.24.0 (go.mod) (#2416) @renovate[bot]
- chore(deps): update github/codeql-action action to v4.37.0 (.github/workflows) (#2384) @renovate[bot]
- chore(deps): update actions/labeler action to v6.2.0 (.github/workflows) (#2441) @renovate[bot]
- chore(deps): update actions/setup-go action to v6.5.0 (.github/workflows) (#2429) @renovate[bot]
- docs(deps): update module github.com/oapi-codegen/oapi-codegen/v2 to v2.7.2 (readme.md) (#2439) @renovate[bot]
- fix(deps): update module github.com/speakeasy-api/openapi to v1.23.1 (go.mod) (#2392) @renovate[bot]
- chore(deps): update actions/checkout action to v7 (.github/workflows) (#2415) @renovate[bot]
- docs(deps): update module github.com/oapi-codegen/oapi-codegen/v2 to v2.7.1 (readme.md) (#2399) @renovate[bot]
- fix(deps): update module github.com/getkin/kin-openapi to v0.140.0 (go.mod) (#2395) @renovate[bot]
- chore(deps): update dessant/label-actions action to v5.0.3 (.github/workflows) (#2386) @renovate[bot]
- fix(deps): update module github.com/getkin/kin-openapi to v0.139.0 (go.mod) (#2387) @renovate[bot]
- chore(deps): update dessant/label-actions action to v5.0.2 (.github/workflows) (#2377) @renovate[bot]
- fix(deps): update module golang.org/x/text to v0.37.0 (go.mod) (#2321) @renovate[bot]
- chore(deps): update release-drafter/release-drafter action to v7.3.0 (.github/workflows) (#2346) @renovate[bot]
- chore(deps): update module github.com/golangci/golangci-lint to v2.12.2 (makefile) (#2283) @renovate[bot]
- chore(deps): update github/codeql-action action to v4.35.5 (.github/workflows) (#2268) @renovate[bot]
- chore(deps): update actions/setup-go action to v6.4.0 (.github/workflows) (#2309) @renovate[bot]
- chore(deps): update actions/upload-artifact action to v7.0.1 (.github/workflows) (#2325) @renovate[bot]
- chore(deps): update actions/labeler action to v6.1.0 (.github/workflows) (#2369) @renovate[bot]
- fix(deps): update module golang.org/x/tools to v0.45.0 (go.mod) (#2375) @renovate[bot]
- chore(deps): update dessant/label-actions action to v5.0.1 (.github/workflows) (#2373) @renovate[bot]
- fix(deps): update module github.com/getkin/kin-openapi to v0.138.0 (go.mod) (#2337) @renovate[bot]
- chore(deps): update oapi-codegen/actions action to v0.8.0 (.github/workflows) (#2365) @renovate[bot]
- Update Go to 1.25.9, clean up modules, and update dependencies (#2355) @mromaszewicz
- Update module github.com/oapi-codegen/oapi-codegen/v2 to v2.7.0 (README.md) (#2362) @renovate[bot]
Sponsors
We would like to thank our sponsors for their support during this release.
We'd also like to thank Greptile for allowing our project to use their code review system.