Skip to content

discriminator helpers generate invalid assignments for pointer-typed Code fields #2297

@4nd3r5on

Description

@4nd3r5on

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:

  1. A plain untyped string constant is assigned to a pointer type (*ResourceConflictErrorCode), which the compiler rejects.
  2. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions