OpenAPITools / openapi-generator

OpenAPI Generator allows generation of API client libraries (SDK generation), server stubs, documentation and configuration automatically given an OpenAPI Spec (v2, v3)
https://openapi-generator.tech
Apache License 2.0
21.52k stars 6.51k forks source link

[BUG][JavaSpring] using "oneof" does not generate "OneOf*.java" models (openapi-generator-maven-plugin 4.3.0/4.3.1) #6816

Open iekdosha opened 4 years ago

iekdosha commented 4 years ago

Bug Report Checklist

Description

While generating code with oneOf two models as a response (or request body) with Spring generator (openapi-generator-maven-plugin) code for the oneOf model is not generated. For the given spec below the only generated models are Model1 and Model2 but the interface uses a model named OneOfModel1Model2 and a file named OneOfModel1Model2.java is not generated which cause a compilation error:

import api.generated.models.OneOfModel1Model2;

. . .

@Validated
@Api(value = "Default", description = "the Default API")
public interface DefaultApi {

    /**
     * GET /test : Your GET endpoint
     *
     * @return OK (status code 200)
     */
    @ApiOperation(value = "Your GET endpoint", nickname = "getTest", notes = "", response = OneOfModel1Model2.class, tags={  })
    @ApiResponses(value = { 
        @ApiResponse(code = 200, message = "OK", response = OneOfModel1Model2.class) })
    @RequestMapping(value = "/test",
        produces = { "application/json" }, 
        method = RequestMethod.GET)
    ResponseEntity<OneOfModel1Model2> getTest();

}
openapi-generator version

Tried with openapi-generator-maven-plugin 4.3.0 and 4.3.1.

OpenAPI declaration file content or url
{
  "openapi": "3.0.0",
  "info": {
    "title": "testAPI",
    "version": "1.0"
  },
  "servers": [
    {
      "url": "http://localhost:3000"
    }
  ],
  "paths": {
    "/test": {
      "get": {
        "summary": "Your GET endpoint",
        "tags": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Model1"
                    },
                    {
                      "$ref": "#/components/schemas/Model2"
                    }
                  ]
                }
              }
            }
          }
        },
        "operationId": "get-test"
      }
    }
  },
  "components": {
    "schemas": {
      "Model1": {
        "title": "Model1",
        "type": "object",
        "properties": {
          "id1": {
            "type": "string"
          }
        }
      },
      "Model2": {
        "title": "Model2",
        "type": "object",
        "properties": {
          "id2": {
            "type": "integer"
          }
        }
      }
    }
  }
}
Steps to reproduce

Plugin configuration

    <properties>
        <kotlin.vesion>1.3.41</kotlin.vesion>
        <pact.artifactory.folder />
        <!--openapi generator-->
        <openapi-generator.version>4.3.1</openapi-generator.version>
        <openapi-generator.resources>/src/main/resources</openapi-generator.resources>
        <openapi-generator.specfile>api.json</openapi-generator.specfile>
        <openapi-generator.language>spring</openapi-generator.language>
        <openapi-generator.datelibrary>java8</openapi-generator.datelibrary>
        <openapi-generator.basepackage>api</openapi-generator.basepackage>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.openapitools</groupId>
                <artifactId>openapi-generator-maven-plugin</artifactId>
                <version>${openapi-generator.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>generate</goal>
                        </goals>
                        <configuration>
                            <inputSpec>
                                ${project.basedir}${openapi-generator.resources}/${openapi-generator.specfile}
                            </inputSpec>
                            <output>${project.build.directory}/generated-sources</output>
                            <generatorName>${openapi-generator.language}</generatorName>
                            <skipValidateSpec>true</skipValidateSpec>
                            <configOptions>
                                <sourceFolder>.</sourceFolder>
                                <dateLibrary>java8</dateLibrary>
                                <interfaceOnly>true</interfaceOnly>
                                <skipDefaultInterface>true</skipDefaultInterface>
                                <useTags>true</useTags>
                            </configOptions>
                            <modelPackage>${openapi-generator.basepackage}.generated.models</modelPackage>
                            <apiPackage>${openapi-generator.basepackage}.generated.api</apiPackage>
                            <generateModelDocumentation>false</generateModelDocumentation>
                            <generateApiDocumentation>false</generateApiDocumentation>
                            <generateModelDocumentation>false</generateModelDocumentation>
                            <generateApiDocumentation>false</generateApiDocumentation>
                            <generateSupportingFiles>false</generateSupportingFiles>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

        </plugins>
    </build>

Thanks :)

roigamliel commented 4 years ago

Seeing it as well

j3den commented 4 years ago

Hello. I am also getting the same. I can only guess that the implementation for Spring should be a Response Entity containing an "Object" type as opposed to the generated model.

I could of course for now just have it return "Object" but then the other aspects like documentation and client library generation will not be as accurate.

Thanks.

Screenshot from 2020-06-30 15-29-25

Screenshot from 2020-06-30 15-30-04

    parameters:
      - schema:
          type: string
          format: uuid
        name: purchaseID
        in: path
        required: true
        description: The ProductPurchaseID - UUID
    post:
      summary: Create a Payment Intent for a Product Purchase
      operationId: createPaymentIntentForPurchase
      responses:
        '201':
          description: Created
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: '#/components/schemas/StripePaymentIntent'
      description: |-
        Create a payment intent for the product purcahse for the UX to handle payment.
        For example, passing the psp=STRIPE parameter will create a Stripe-Payment Intent for the product purchase.
      parameters:
        - schema:
            type: string
            enum:
              - STRIPE
              - PAYPAL
              - SAGE
          in: query
          name: psp
          description: The payment service provider to use.
          required: true
      tags:
        - Web

Screenshot from 2020-06-30 15-30-38

hrkristian commented 4 years ago

In order to get a OneOf* class you have to make it a named component, then reference that in schema with a $ref.

This seems to be a universal case for the plugin, an "inline" definition does not result in a generated class (with some few exceptions.)

ericamerchant commented 4 years ago

@hrkristian Can you give an example of what the named component would look like in the oneOf case?

ericamerchant commented 4 years ago

I tried the following and as a result I got 4 classes. ObjectA, ObjectC, and ObjectD are all as defined. ObjectB is a combination of ObjectA and ObjectB, containing properties from both, but without any "oneof" type validation nor any inheritance with ObjectC and ObjectD.

      "ObjectA": {
        "type": "object",
        "properties": {
          "FancyProperty": {
            "$ref": "#/components/schemas/ObjectB"
          }
        }
      },
      "ObjectB": {
        "oneOf": [
          {
            "type": "string"
          },
          {
            "$ref": "#/components/schemas/ObjectC"
          },
          {
            "$ref": "#/components/schemas/ObjectD"
          }
        ]
      },
      "ObjectC": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string"
          },
          "value": {
            "type": "object"
          },
          "message": {
            "type": "string"
          }
        }
      },
      "ObjectD": {
        "type": "object",
        "properties": {
          "something": {
            "type": "string"
          }
        }
      }
akashlee commented 3 years ago

Would suggest to make use of discriminator to achieve oneOf functionality.

paths:
  /cars:
    post:
      description: Am I lucky?
      requestBody:
        content:
          application/json:
            schema:
             type: object
             properties:
               lotteryNumber:
                 type: string
      responses:
        '201':
          description: A car for sure
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Car'

components:
    schemas:
      Car:
        type: object
        properties:
          carName:
            type: string
        discriminator:
          propertyName: carName

      FERRARI:
        type: object
        allOf:
          - $ref: '#/components/schemas/Car'
          - type : object
            properties:
              howFastDoIGo:
                type: string

      HONDACIVIC:
        allOf:
          - $ref: '#/components/schemas/Car'
          - type : object
            properties:
              howMuchMileageIGive:
                type: string

image

image

image

image

helloworld121 commented 3 years ago

I am also looking forward to this support. I like the suggestion of @akashlee Best Regards

fkreis commented 2 years ago

Any news on this? I am also struggling with the generation when using oneOf functionality with <artifactId>openapi-generator-maven-plugin</artifactId>, <version>5.3.1</version> and <generatorName>spring</generatorName>. True, the situation improves when I put my oneOf block into a separate named component as suggested by @hrkristian, like

SuperType:
      oneOf:
        - $ref: "#/components/schemas/SubType1"
        - $ref: "#/components/schemas/SubType2"

This way, at least the SuperType class is generated. However, SubType1 and SubType2 do not inherit from SuperType. It looks more like SuperType becomes an allOf SubType1 and SubType2. Still, even if that would work as a workaround it would be nice if also the inline definition of oneOf would be generated properly, because very often you are not the owner of the openapi specification and hence, you don't want to edit it.

ivadoncheva commented 2 years ago

Hi, I also experience issues with oneOf generated classes. Example:

 application/json:
              schema:
                oneOf:
                - $ref: "#/components/schemas/RequestType1"
                - $ref: "#/components/schemas/RequestType2"

As a result I have 1 interface -> OneOfSomeBody and 3 classes generated: RequestType1, RequestType2 and a third class -> SomeBody

@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.JavaClientCodegen", date = "2022-02-22T14:55:43.512127+02:00[Europe/Kiev]")
public class SomeBody implements Serializable, OneOfSomeBody {
  private static final long serialVersionUID = 1L;

  @Override
  public boolean equals(java.lang.Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    return true;
  }

  @Override
  public int hashCode() {
    return Objects.hash();
  }

  @Override
  public String toString() {
    StringBuilder sb = new StringBuilder();
    sb.append("class SomeBody {\n");

    sb.append("}");
    return sb.toString();
  }
  /**
  * Convert the given object to string with each line indented by 4 spaces
  * (except the first line).
  */
  private String toIndentedString(java.lang.Object o, boolean mask) {
    if (o == null) {
      return "null";
    }
    if (mask) {
      return "****";
    }
    return o.toString().replace("\n", "\n    ");
  }

  private String toIndentedString(java.lang.Object o) {
    return toIndentedString(o, false);
  }
}

RequestType1, RequestType2 also implement this empty interface: OneOfSomeBody but they don't extend SomeBody?

Now my API class that is generated has in the method declaration only SomeBody as a parameter. Therefore in the implementation of this method I'm not able to make use of the generated classes: RequestType1, RequestType2

Could you please advice now to proceed here? Is there an example of how to use this functionality?

Best regards, Iva

wing328 commented 2 years ago

Please give the latest master or stable version a try as there's been lots of enhancement to the spring generator recently.