-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Description
Description
oapi-codegen version: v2.6.0
When a oneOf schema uses a discriminator and the variant schemas are defined via allOf (inheriting from a base schema with an optional field that gets narrowed to an enum in the variant), the generator correctly produces a pointer type for the discriminator field — but then generates From* / Merge* helper functions that assign a raw string literal to that pointer field. This produces code that does not compile.
Two mistakes occur in the same generated output:
- A plain untyped string constant is assigned to a pointer type (
*ResourceConflictErrorCode), which the compiler rejects. - The already-generated typed constants (
ResourceExists,IdempotencyConflict) are not used — even if the field were non-pointer, a direct string literal assignment would still fail due to the named type.
Schema (minimal reproduction)
# errs.yml
components:
schemas:
ErrorResponse:
type: object
required: [error]
properties:
code:
type: string
error:
type: string
ResourceConflictError:
type: object
required: [code, error]
allOf:
- $ref: '#/components/schemas/ErrorResponse'
- properties:
code:
type: string
enum: [resource_exists]
example: resource_exists
IdempotencyConflictError:
type: object
required: [code, error]
allOf:
- $ref: '#/components/schemas/ErrorResponse'
- properties:
code:
type: string
enum: [idempotency_conflict]
example: idempotency_conflict
responses:
Conflict:
description: Conflict
content:
application/json:
schema:
oneOf:
- $ref: '#/components/schemas/ResourceConflictError'
- $ref: '#/components/schemas/IdempotencyConflictError'
discriminator:
propertyName: code
mapping:
resource_exists: '#/components/schemas/ResourceConflictError'
idempotency_conflict: '#/components/schemas/IdempotencyConflictError'Generated code (broken)
The generator emits typed constants and pointer-typed fields correctly:
const (
IdempotencyConflict IdempotencyConflictErrorCode = "idempotency_conflict"
ResourceExists ResourceConflictErrorCode = "resource_exists"
)
type ResourceConflictError struct {
Code *ResourceConflictErrorCode `json:"code,omitempty"`
Error string `json:"error"`
}
type IdempotencyConflictError struct {
Code *IdempotencyConflictErrorCode `json:"code,omitempty"`
Error string `json:"error"`
}But the From* / Merge* helpers assign raw string literals to those pointer fields:
func (t *Conflict) FromResourceConflictError(v ResourceConflictError) error {
v.Code = "resource_exists" // ERROR: cannot use untyped string as *ResourceConflictErrorCode
...
}
func (t *Conflict) MergeResourceConflictError(v ResourceConflictError) error {
v.Code = "resource_exists" // ERROR: same
...
}
func (t *Conflict) FromIdempotencyConflictError(v IdempotencyConflictError) error {
v.Code = "idempotency_conflict" // ERROR: cannot use untyped string as *IdempotencyConflictErrorCode
...
}
func (t *Conflict) MergeIdempotencyConflictError(v IdempotencyConflictError) error {
v.Code = "idempotency_conflict" // ERROR: same
...
}Compiler errors
cannot use "resource_exists" (untyped string constant) as *ResourceConflictErrorCode value in assignment
cannot use "idempotency_conflict" (untyped string constant) as *IdempotencyConflictErrorCode value in assignment
Root cause (hypothesis)
The generator does not honour the required: [code, error] constraint declared at the top level alongside allOf — it sees code as optional in the base ErrorResponse and generates a pointer field. The From*/Merge* template then generates a plain string literal assignment without accounting for either the pointer indirection or the named type, and without reusing the typed constants it already emitted earlier in the same file.