swagger-api / swagger-core

Examples and server integrations for generating the Swagger API Specification, which enables easy access to your REST API
http://swagger.io
Apache License 2.0
7.38k stars 2.18k forks source link

Question: Is there a way to have a ComposedSchema with a discriminator part in a contract generated with springdoc-openapi-maven-plugin? #3604

Open nicolasgras opened 4 years ago

nicolasgras commented 4 years ago

I have a sample SpringBoot API with the following features:

I try to generate an OpenApi contract from this API with springdoc-openapi-maven-plugin.

In my pom.xml, I have the following elements:

By transitivity, I have a dependency to io.swagger.core.v3:swagger-core:2.1.2. I have the same issue with io.swagger.core.v3:swagger-core:2.1.3

Here are my classes I generate schema from.

import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;

public class ContainerClass {

    @ArraySchema(
        arraySchema = @Schema(discriminatorProperty = "classType"), 
        schema = @Schema(implementation = ParentClass.class)
    )
    public List<ParentClass> elements;

    // + Getter/Setter
}
@JsonTypeInfo(
        use = JsonTypeInfo.Id.NAME,
        include = JsonTypeInfo.As.EXISTING_PROPERTY,
        property = "classType",
        defaultImpl = ParentClass.class,
        visible = true)
@JsonSubTypes({
        @JsonSubTypes.Type(value = ChildA.class, name = "CHILD_A"),
        @JsonSubTypes.Type(value = ChildB.class, name = "CHILD_B")})
@Schema(
        description = "Parent description",
        discriminatorProperty = "classType",
        discriminatorMapping = {
                @DiscriminatorMapping(value = "CHILD_A", schema = ChildA.class),
                @DiscriminatorMapping(value = "CHILD_B", schema = ChildB.class)
        }
)
public abstract class ParentClass {

    public String classType;

    // + Getter/Setter
}
@io.swagger.v3.oas.annotations.media.Schema(description = " Child A", allOf = ParentClass.class)
public class ChildA extends ParentClass{
}
@io.swagger.v3.oas.annotations.media.Schema(description = " Child B", allOf = ParentClass.class)
public class ChildB extends ParentClass{
}

When I run springdoc-openapi-maven-plugin, I get the following contract file.

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost:8080
  description: Generated server url
paths:
  /container:
    get:
      tags:
      - hello-controller
      operationId: listElements
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/ContainerClass'
components:
  schemas:
    ChildA:
      type: object
      description: ' Child A'
      allOf:
      - $ref: '#/components/schemas/ParentClass'
    ChildB:
      type: object
      description: ' Child B'
      allOf:
      - $ref: '#/components/schemas/ParentClass'
    ContainerClass:
      type: object
      properties:
        elements:
          type: array
          description: array schema description
          items:
            oneOf:
            - $ref: '#/components/schemas/ChildA'
            - $ref: '#/components/schemas/ChildB'
    ParentClass:
      type: object
      properties:
        classType:
          type: string
      description: Parent description
      discriminator:
        propertyName: classType
        mapping:
          CHILD_A: '#/components/schemas/ChildA'
          CHILD_B: '#/components/schemas/ChildB'

Actually, in my context, in order to have not any breaking change with existing consumers, I need items property in ContainerClass schema to contain the discriminator part that is contained in ParentClass schema, like this:

ContainerClass:
  type: object
  properties:
    elements:
      type: array
      description: array schema description
      items:
        discriminator:
          propertyName: classType
          mapping:
            CHILD_A: '#/components/schemas/ChildA'
            CHILD_B: '#/components/schemas/ChildB'
        oneOf:
        - $ref: '#/components/schemas/ChildA'
        - $ref: '#/components/schemas/ChildB'

When I try to set properties in annotation, I don't manage to do that. And when I debug code of io.swagger.v3.core.jackson.ModelResolver, I don't manage to find a way to do that. And so far I have not found an example of code that help me.

I need that because when I generate a client application from this contract with openapi-generator-maven-plugin, an interface ContainerClassElementsOneOf is generated but with some information missing in its @JSonTypeInfo annotation and with not any @JSonSubTypes annotation.

I get the following interface

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "", visible = true)
public interface ContainerClassElementsOneOf  {
}

whereas I need

@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "classType", visible = true)
@JsonSubTypes({
  @JsonSubTypes.Type(value = ChildA.class, name = "CHILD_A"),
  @JsonSubTypes.Type(value = ChildB.class, name = "CHILD_B"),
})
public interface ContainerClassElementsOneOf  {
    public String getClassType();
}

Is there a way so that a ComposedSchema (array contained in ContainerClass in my case) has a disciminator part generated by springdoc-openapi-maven-plugin execution?

ernestaskardzys commented 4 years ago

I'm seeing exactly the same issue with my code. It looks like that openapi-generator-maven-plugin generates an incorrect interface ContainerClassElementsOneOf out of OpenAPI specification.

miroslavvojtus commented 3 years ago

Hello, we have very same issue.

From the @NicolasGras's example it should generate ref to parent rather than oneOf. Like:

...
    ContainerClass:
      type: object
      properties:
        elements:
          type: array
          description: array schema description
          items:
            $ref: '#/components/schemas/ParentClass'
    ParentClass:
      type: object
      properties:
        classType:
          type: string
      description: Parent description
      discriminator:
        propertyName: classType
        mapping:
          CHILD_A: '#/components/schemas/ChildA'
          CHILD_B: '#/components/schemas/ChildB'

We have the problem on non-array attribute which has generated schema as oneOf of children rather than ref to parent with proper discriminator details. That is for us necessary in order to properly generate SDK on front end.

I would consider this rather a bug than question. If you do not consider this a bug can you at least argue why it is ok?

We attempted to fix it by annotating referencing attribute like:

import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;

public class ContainerClass {

    @ArraySchema(
        arraySchema = @Schema(discriminatorProperty = "classType"), 
        schema = @Schema(ref= "#/components/schemas/ParetnClass")
    )
    public List<ParentClass> elements;

    // + Getter/Setter
}

It resulted in even worse result (cyclic dependency):

openapi: 3.0.1
info:
  title: OpenAPI definition
  version: v0
servers:
- url: http://localhost:8080
  description: Generated server url
paths:
  /container:
    get:
      tags:
      - hello-controller
      operationId: listElements
      responses:
        "200":
          description: OK
          content:
            '*/*':
              schema:
                $ref: '#/components/schemas/ContainerClass'
components:
  schemas:
    ChildA:
      type: object
      description: ' Child A'
      allOf:
      - $ref: '#/components/schemas/ParentClass'
    ChildB:
      type: object
      description: ' Child B'
      allOf:
      - $ref: '#/components/schemas/ParentClass'
    ContainerClass:
      type: object
      properties:
        elements:
          type: array
          description: array schema description
          items:
            $ref: '#/components/schemas/ParentClass'
    ParentClass:
      $ref: '#/components/schemas/ParentClass'