Skip to content

# Less verbose initialization of discriminated union types with associated values #748

Open
@Mrteller

Description

@Mrteller

Motivation

While working with generated code for OpenAPI schemas that use discriminators (representing enums with associated values in Swift), I noticed that the current implementation requires redundant specification of the discriminator value when instantiating such objects.

Using the example from issue #515:

openapi: "3.1.0"
info:
  title: Action API
  version: "1.0.0"
  description: API for handling actions
servers:
  - url: https://api.example.com/v1
    description: Production server
paths:
  /actions:
    post:
      summary: Create a new action
      operationId: createAction
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/Action'
      responses:
        '201':
          description: Action created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Action'
        '400':
          description: Invalid request payload
        '500':
          description: Internal server error
components:
  schemas:
    Action:
      type: object
      properties:
        details:
          $ref: '#/components/schemas/ActionDetails'
    ActionCommon:
      type: object
      properties:
        type:
          type: string
      required:
        - type
    ActionDetails:
      oneOf:
        - $ref: '#/components/schemas/ActionDetailsFoo'
        - $ref: '#/components/schemas/ActionDetailsBar'
      discriminator:
        propertyName: type
        mapping:
          FOO: '#/components/schemas/ActionDetailsFoo'
          BAR: '#/components/schemas/ActionDetailsBar'
    ActionDetailsBar:
      type: object
      allOf:
        - $ref: '#/components/schemas/ActionCommon'
        - type: object
          properties:
            bar:
              type: integer
              format: int32
          required:
            - bar
    ActionDetailsFoo:
      type: object
      allOf:
        - $ref: '#/components/schemas/ActionCommon'
        - type: object
          properties:
            foo:
              type: string
          required:
            - foo

The generated initializers require specifying the discriminator value twice:

        let response = try await client.createAction(body: .json(.init(details: .bar(.init(value1: .init(_type: "bar"), value2: .init(bar: 123))))))

What is even worse is that second time you are able to pass something unrelated. While it is possible to limit that at OpenAPI spec with the help of enum and const that will require additional work.

Expected Behavior

Since the discriminator value is already known at the call site (we're using the .foo or .bar static function), it would be more ergonomic if we didn't need to specify it again in the internal initialization. Something like:

        let response = try await client.createAction(body: .json(.init(details: .bar(.init(bar: 123)))))

Steps to Reproduce

  1. Create a new Swift package
  2. Add the OpenAPI spec above as openapi.yaml
  3. Add swift-openapi-generator plugin to Package.swift
  4. Generate code using swift package generate-code-from-openapi
  5. Observe the generated initializers require redundant specification of the discriminator value

Swift Version

Swift version 5.9.2

Operating System

macOS 14.3

Proposed solution

It hard for me to figure if invasive changes are require to implement simplified `init() for such types, but that is how I see the solution at its best.

Alternatives considered

If for some reasons existing excessive init is needed, the generated convenience laconic init or init with default value for type param is probably OK too.

Additional information

This issue is related to how the generator handles discriminated unions that represent what would naturally be Swift enums with associated values. While the current implementation works, it introduces unnecessary verbosity that could be improved.
It seems the issue is related to boxing and wrapping approaches that are used in the project.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/featureNew feature.status/triageCollecting information required to triage the issue.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions