Skip to content

question: working with discriminators and inheritance on nested data #515

Closed
@schweizerm

Description

@schweizerm

Question

Hey guys :)
Currently I'm not sure if I'm not understanding things correctly or if there's indeed a missing/buggy functionality - kinda new to both openapi and swift.

In general I do have an Endpoint that returns some nested data.

Class structure look like this (pseudo):
Action(details: ActionDetails)
ActionDetails(type: String)
ActionDetailsFoo(foo: String): ActionDetails
ActionDetailsBar(bar: int): ActionDetails

GET /nextAction
returns Action

{
    "details": {
        "type": "FOO",
        "foo": "whatever"
    }
}

See (manually adjusted) file with notes:

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
  - url: http://localhost:8080
    description: Generated server url
paths:
  /nextAction:
    get:
      tags:
        - open-api-controller
      operationId: nextAction
      responses:
        default:
          description: OK
          content:
            'application/json':
              schema:
                $ref: '#/components/schemas/Action'
components:
  schemas:
    Action:
      type: object
      properties:
        details:
          $ref: '#/components/schemas/ActionDetails'
    ActionDetails:
      required:
        - type
      type: object
      properties:
        type:
          type: string
      discriminator:
        propertyName: type
        mapping:
          FOO: '#/components/schemas/ActionDetailsFoo'
          BAR: '#/components/schemas/ActionDetailsBar'
      anyOf: # manually added; removing this will always parse an object from ActionDetails without further info
        - $ref: '#/components/schemas/ActionDetailsFoo'
        - $ref: '#/components/schemas/ActionDetailsBar'
    ActionDetailsBar:
      type: object
      allOf:
#        - $ref: '#/components/schemas/ActionDetails' # keeping this ref to parent will end up in a loop
        - type: object
          properties:
            bar:
              type: integer
              format: int32
    ActionDetailsFoo:
      type: object
      allOf:
#        - $ref: '#/components/schemas/ActionDetails'
        - type: object
          properties:
            foo:
              type: string

I've been playing around a lot with the openapi spec but no matter how I put it, I can't really get it running as expected

A) removing the anyOf on the ActionDetails level will ignore any subtype when parsing

note: the anyOf is currently manually added after generation

        public struct ActionDetails: Codable, Hashable, Sendable {
            public var _type: Swift.String
            public init(_type: Swift.String) {
                self._type = _type
            }
            public enum CodingKeys: String, CodingKey {
                case _type = "type"
            }
        }

B) anyOf within the ActionDetails and allOf within the subclasses -> endless loop during runtime

ActionDetails.init() -> ActionDetailsFoo.init() -> ActionDetails.init() -> ActionDetailsFoo.init() -> ...

C) anyOf within the ActionDetails and removing allOf within subclasses:

kinda works, but I would have to manipulate the file manually and add all attributes that they should have from inheritance manually

From my understanding a) should be the correct way, as having a discriminator should take all schemas that have the parent schema with allOf included into consideration for alternate schemas.

Therefore I'm wondering if I'm doing it wrong or if this is indeed a bug / missing feature, can someone please enlighten me? :)

Also I'm generally new to swift and wonder if there's a more convenient way of getting the actual object then a switch on the cases? Maybe casting or anything? I usually have handlers and would like to just put the ActionDetails and they already know what kind of type they'd expect.

let result = client..

// is it possible to do something like
result as? ActionDetailsFoo 

// instead of
switch(result) {
    .case ActionDetailsFoo(let foo): 
    .case ActionDetailsBar(let bar): 
}

I actually do have a similar issue when sending responses of subtypes. I wonder if there's any elegant way in which I could send any subclass of ActionDetails instead of ActionDetails that lacks all the extra information to an endpoint that expects an object of type ActionDetails.

Metadata

Metadata

Assignees

No one assigned

    Labels

    kind/supportAdopter support requests.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions