Skip to content

[Go] Support polymorphic substitution ("subclasses") #4559

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
sebastien-rosset opened this issue Nov 20, 2019 · 5 comments
Open

[Go] Support polymorphic substitution ("subclasses") #4559

sebastien-rosset opened this issue Nov 20, 2019 · 5 comments

Comments

@sebastien-rosset
Copy link
Contributor

sebastien-rosset commented Nov 20, 2019

Description

This is a suggestion to generate go interfaces in the go code, or some other mechanism to support sub-classes.

The OAS spec supports polymorphism and inheritance. One area that seems to be problematic in the generated go code is when a schema refers to a type that has multiple sub-types, and the actual sub-type is determined at run-time by the api service. The go code is generated with a pointer to a struct (not an interface), which means there is no possibility of dynamically determining the proper go struct based on the discriminator value.

For example, suppose an OAS API specifies a path that returns a heterogenous list of animals. The returned list may include dogs, cats, and any "subclass" of animal. The api service may return a list of animals, and each object in the array may have a different discriminator value (e.g. Dog, Cat). This scenario can also apply to a component: for example, the Animal type may have a property which specifies a list of visited places, and each "Place" may have sub-types. A place could be a city, country or other types of places.

With the OpenAPITools generated code, the go client will unmarshal the list of Animals into the "Animal" go struct, and it will lose all the properties that were specified in the sub-types (such as Dog and Cat).

To address this problem, we have created a custom go implementation, and we generate a golang interface for each specified type in a OAS schema. The go type of the generated property is the (generated) go interface, not a pointer to a go struct. This makes it a bit harder to unmarshal because the default go unmarshaler does not know which type to instantiate. But this can be handled with a custom, generated JSON unmarhshaler; the unmarshaler reads the value of the discriminator property to determine the actual type, then it instantiates the proper go struct (e.g. Dog or Cat).

When there is a small and fixed number of sub-types, one could argue "oneOf" should be used to provide an explicit list of all sub-types. Besides the fact that currently the generators do not handle oneOf, it would ugly when the number of sub-types grows to a large value.

Is there a strong reason to generate pointers to go structs as opposed to go interfaces? Are there other approaches that work for polymorphic substitution? It seems the OAS spec is not very clear on that topic.

openapi-generator version

4.2.1

OpenAPI declaration file content or url
paths:
 /Animals:
    get:
      responses:
        '200':
          description: a heterogeneous (dogs, cats...) list of animals.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Animals'

components:
  schemas:
    Resource:
      discriminator:
        propertyName: objectType
      properties:
        objectType:
          type: string
    Place:
      allOf:
        - $ref: '#/components/schemas/Resource'
        - type: object
          properties:
            name:
              type: string
    City:
      type: object
      allOf:
        - $ref: '#/components/schemas/Place'
        - type: object
          properties:
            zipCode:
              type: string
    Country:
      type: object
      allOf:
        - $ref: '#/components/schemas/Place'
        - type: object
          properties:
            countryCode:
              type: string
    Animal:
      type: object
      allOf:
        - $ref: '#/components/schemas/Resource'
        - type: object
          properties:
            name:
              type: string
            visitedPlaces:
              type: array
              description: the list of places (cities and countries) that this animal has visited.
              items:
                $ref: '#/components/schemas/Place'
   Dog:
      type: object
      allOf:
        - $ref: '#/components/schemas/Animal'
        - type: object
          properties:
            breed:
              type: string
    Cat:
      type: object
      allOf:
        - $ref: '#/components/schemas/Animal'
        - type: object
          properties:
            color:
              type: string
    Animals:
      type: array
      items:
        $ref: '#/components/schemas/Animal'
Command line used for generation
Steps to reproduce
Related issues/PRs

OAI/OpenAPI-Specification#403

Suggest a fix
@wing328
Copy link
Member

wing328 commented Nov 21, 2019

@sebastien-rosset thanks for starting a discussion on this.

cc @antihax (2017/11) @bvwells (2017/12) @grokify (2018/07) @kemokemo (2018/09) @bkabrda (2019/07)
(technical committee members)

@sebastien-rosset
Copy link
Contributor Author

I've noticed the same problem occurs in the generated Python client.

@bvwells
Copy link
Contributor

bvwells commented Nov 24, 2019

Another possibility to solve this is to use struct embedding. I've seen this used in other go based swagger generators such go-swagger.

Could be a simple enough approach to solve the issue...

@sebastien-rosset
Copy link
Contributor Author

sebastien-rosset commented Nov 24, 2019

Another possibility to solve this is to use struct embedding. I've seen this used in other go based swagger generators such go-swagger.

Could be a simple enough approach to solve the issue...

Are you referring to the following: if type B has "allOf" type A (and potentially other properties), then go struct B embeds go struct A? If so, yes, this would be great to have better modeling of inheritance, as opposed to flattening all structs. But I'm not sure how this will address the use case I have raised here.

I'll open a separate issue to support inheritance with embedded structs.

@sebastien-rosset sebastien-rosset changed the title [Go] Generate go interfaces to handle polymorphic substitution [Go] Support polymorphic substitution ("subclasses") Feb 25, 2020
dlnilsson added a commit to appgate/terraform-provider-appgatesdp that referenced this issue Sep 16, 2020
openapi.generator does not play well with discriminator(s) from the openapi spec.

related issues:
OpenAPITools/openapi-generator#4559
OpenAPITools/openapi-generator#417

the  api_ldap_identity_providers.go is a  copy/paste search and replace of api_identity_providers.go
to return LdapProviders instead of sub stub IdentityProvider

we need to do the same for the identity provider types.

Signed-off-by: Daniel Nilsson <[email protected]>
@karaatanassov
Copy link

For polymorphism in golang interfaces are required. Embedding of interfaces and/or data structures is optimization to not repeat the code in multiple places. Struct types in golang regardless of the use of embedding do not have polymorphic behavior i.e. you cannot use a struct embedding a "base" struct as parameter or result of function declared to accept/return the "Base" struct.

The topic of go and json polymorphism is not a simple one and I have only found partial solutions. To build a holistic solution required putting all these together and then solving couple of riddles. I came up with a write up on the topic as I think it will be really helpful for openapi-generator and other tools to support polymorphism. I cannot help with the implementation as I do not understand well enough the project but I hope this write up is inspiring.

https://github.com/karaatanassov/go_polymorphic_json/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants