Map OpenAPI 3.1 multi-type unions to any - #2522
Conversation
OpenAPI 3.1 allows `type` to be a list, so a value may be any one of several types. The primitive-type dispatch uses `Types.Is(...)`, which only matches a single-element list, so every multi-type union fell through to `unhandled Schema type` and failed generation. Go has no type matching that constraint, so map a union to `any`, the same permissive mapping a bare `type: "null"` already gets (oapi-codegen#2430), rather than rejecting an otherwise valid spec. The mapping is narrow on purpose, so the existing error still catches malformed input. A list-valued `type` is 3.1-only syntax, so a 3.0 document carrying one keeps failing. Every entry must name a JSON Schema type, so a misspelled one (`type: [strng, number]`) keeps failing too. A union carrying an `enum` is excluded from enum codegen for the reason `type: array` already is: `const X any = ...` is not a valid Go constant. It generates the plain `any` the union maps to.
Greptile SummaryThis PR allows valid OpenAPI 3.1 schemas with multiple declared JSON types to generate as permissive Go
Confidence Score: 5/5The PR appears safe to merge, with no unacknowledged actionable defects identified in the changed behavior. The new dispatch is restricted to valid OpenAPI 3.1 multi-type lists, preserves existing malformed-input errors, and prevents enum metadata from reaching invalid constant generation.
|
| Filename | Overview |
|---|---|
| pkg/codegen/schema.go | Adds narrowly scoped OpenAPI 3.1 multi-type detection, maps recognized unions to any, and bypasses scalar enum generation for those unions. |
| pkg/codegen/schema_test.go | Covers scalar and array-containing unions, null stripping, malformed type names, and the OpenAPI 3.1 version gate. |
| internal/test/openapi31/spec.yaml | Extends the existing OpenAPI 3.1 fixture with union-valued maps, properties, named schemas, nullable unions, and enum-bearing unions. |
| internal/test/openapi31/openapi31.gen.go | Representative generated output matches the intended any, map[string]any, and alias mappings without unrelated drift. |
| internal/test/openapi31/openapi31_test.go | Verifies generated union values compile, accept multiple runtime types, and round-trip through JSON where applicable. |
| README.md | Documents that OpenAPI 3.1 multi-type schemas map to Go any. |
Reviews (1): Last reviewed commit: "Map OpenAPI 3.1 multi-type unions to `an..." | Re-trigger Greptile
jamietanna
left a comment
There was a problem hiding this comment.
@mromaszewicz I'd expect that we'd treat this as a oneOf in this case?
You know, I'm not sure. This isn't like |
|
Thanks for the PR — the core mapping is well-guarded and the writeup is unusually thorough. We ran a deep analysis with Claude on this change (probing generated output for the interaction cases against a compiled runtime), and it surfaced a few issues that make the change incomplete as-is. Findings below, roughly in order of severity. 1. Parameter positions generate code that always fails at request timeA multi-type union in a parameter now generates successfully, but the endpoint is permanently broken: parameters:
- name: id
in: path
required: true
schema:
type: [string, integer]The generated handler binds into an So a path param typed this way 400s on every request, and a query param 400s whenever it's supplied. Before this PR the spec failed loudly at generation time; after it, the failure moves to runtime. Unlike Relatedly, the PR body says generation is exercised across "property, additionalProperties, parameter, request body, response body, array items, and allOf positions", but the committed suite only covers property, additionalProperties, a named component, and the enum interaction. If you tested the other positions locally, could you commit those tests? The parameter case above is exactly the kind of thing that coverage would have caught. 2.
|
OpenAPI 3.1 allows
typeto be a list, so a value may be any one of several types. The primitive-type dispatch inpkg/codegen/schema.gousesTypes.Is(...), which only matches a single-element list, so every multi-type union fell through tounhandled Schema typeand failed generation. Closes #2521.Go has no type meaning "one of these", so a union maps to
any. That follows the baretype: "null"branch directly above it, added for #2430, on the same reasoning: prefer the permissive mapping over rejecting an otherwise valid spec.The mapping is deliberately narrow so the existing error keeps catching malformed input instead of quietly widening it. #1977 asks for a better error here, and swallowing more cases would be the wrong direction. A list-valued
typeis 3.1-only syntax, so a 3.0 document carrying one still fails. Every entry must name a JSON Schema type, sotype: [strng, number]still fails on the typo.One interaction needed handling. A union carrying an
enumreached the enum branch, which emitsconst X T = ...against a type that is nowany. That is not a valid Go constant, so generation succeeded but the output did not compile. Unions are now excluded from enum codegen for the reasontype: arrayalready is, and generate the plainanyinstead.Nullable objects are unaffected.
schemaPrimaryTypestrips"null"before the dispatch, sotype: [object, "null"]still generates its struct.type: [object, string]does collapse toanyand drops its declared properties. That is inherent to the mapping, since no Go type is either a struct or a string.Generation is exercised across property,
additionalProperties, parameter, request body, response body, array items, andallOfpositions. One rough edge worth naming: a response body typedanyunderstrict-serveremits a method on a named interface type, which does not compile. That reproduces onmaintoday for bothschema: {}andtype: "null", so this PR routes one more spec shape into it rather than causing it. Happy to fix that separately if you'd like it tracked.For motivation, Honeycomb's published 3.1 spec carries two of these for genuinely polymorphic event and query-result values. The only workaround today is an overlay that strips the
typekeyword before generation.