Search code examples
javaopenapiopenapi-generator

OpenAPI Maven Plugin for Java Generating Inconsistent Classes


We are using the openapi-generator-maven-plugin version 5.0.1 to generate our APIs. I am trying to specify a request that includes a DTO and also a file.

The first weird thing is that the generated code doesn't use the DTO, it basically flattens the fields, so that the API expects each of the fields to be specified. However, we're not really too concerned about this issue, because we can just specify each field (although it would be nice if it worked as expected).

The problem that is killing us is that the generated classes for the API and the API Delegate are not consistent with each other. The generated API treats each of the fields as a String. However, the API Delegate treats them as Optional<String>. So when we try to compile the code, the API gets a compile error because it's passing String to the Delegate, and the Delegate wants Optional<String>.

Here is our POM, with the related dependencies and plugin config:

        <dependency>
            <groupId>io.swagger</groupId>
            <artifactId>swagger-annotations</artifactId>
            <version>1.6.2</version>
        </dependency>
        <dependency>
            <groupId>org.openapitools</groupId>
            <artifactId>jackson-databind-nullable</artifactId>
            <version>0.2.1</version>
        </dependency>
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>
...
<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>5.0.1</version>
    <executions>
        <execution>
            <id>processor-Generate</id>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>
                    ${project.basedir}/apis/innovation-ivp-inventory-accuracy-acl-api.yml
                </inputSpec>
                <generatorName>spring</generatorName>
                <apiPackage>${project.groupId}.inventory.accuracy.acl.api</apiPackage>
                <modelPackage>${project.groupId}.inventory.accuracy.acl.dto</modelPackage>
                <invokerPackage>${project.groupId}.inventory.accuracy.acl.api.handler</invokerPackage>
                <supportingFilesToGenerate>ApiUtil.java,OpenAPIDocumentationConfig.java
                </supportingFilesToGenerate>
                <configOptions>
                    <useTags>true</useTags>
                    <dateLibrary>java8-localdatetime</dateLibrary>
                    <java8>true</java8>
                    <delegatePattern>true</delegatePattern>
                    <useBeanValidation>true</useBeanValidation>
                    <useOptional>true</useOptional>
                    <configPackage>${project.groupId}.inventory.accuracy.acl.api</configPackage>
                </configOptions>
                <output>${project.build.directory}/generated-sources</output>
            </configuration>
        </execution>
    </executions>
</plugin>

Here is our OpenAPI spec:

'/email':
  post:
    tags:
      - email-service
    summary: Email Service
    operationId: sendEmail
    requestBody:
      required: true
      content:
        multipart/mixed:
          schema:
            allOf:
              - $ref: '#/components/schemas/EmailRequestDTO'
              - type: object
                properties:
                  file:
                    type: string
                    format: binary
    responses:
      "200":
        description: OK
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/EmailResponseDTO'
components:
  schemas:
    EmailRequestDTO:
      type: object
      properties:
        sendTo:
          type: string
        sentFrom:
          type: string
        subject:
          type: string
        content:
          type: string
    EmailResponseDTO:
      type: object
      properties:
        status:
          type: string
        errorMessage:
          type: string

Here is the OpenAPI-generated API class (note that the parameters are all String):

public interface EmailServiceApi {

    @ApiOperation(value = "Email Service", nickname = "sendEmail", notes = "", response = EmailResponseDTO.class, tags={ "email-service", })
    @ApiResponses(value = {
        @ApiResponse(code = 200, message = "OK", response = EmailResponseDTO.class) })
    @PostMapping(
        value = "/email",
        produces = { "application/json" },
        consumes = { "multipart/mixed" }
    )
    default ResponseEntity<EmailResponseDTO> sendEmail(@ApiParam(value = "") @Valid @RequestPart(value = "sendTo", required = false)  String sendTo,@ApiParam(value = "") @Valid @RequestPart(value = "sentFrom", required = false)  String sentFrom,@ApiParam(value = "") @Valid @RequestPart(value = "subject", required = false)  String subject,@ApiParam(value = "") @Valid @RequestPart(value = "content", required = false)  String content,@ApiParam(value = "") @Valid @RequestPart(value = "file", required = false) MultipartFile file) {
        return getDelegate().sendEmail(sendTo, sentFrom, subject, content, file);
    }
}

Here is the OpenAPI-generated API Delegate class (note that the parameters are all Optional):

public interface EmailServiceApiDelegate {
    default ResponseEntity<EmailResponseDTO> sendEmail(Optional<String> sendTo,
        Optional<String> sentFrom,
        Optional<String> subject,
        Optional<String> content,
        MultipartFile file) {
        getRequest().ifPresent(request -> {
            for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {
                if (mediaType.isCompatibleWith(MediaType.valueOf("application/json"))) {
                    String exampleString = "{ \"errorMessage\" : \"errorMessage\", \"status\" : \"status\" }";
                    ApiUtil.setExampleResponse(request, "application/json", exampleString);
                    break;
                }
            }
        });
        return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
    }
}

Solution

  • Using allOf combines all schema listed in one. The properties of EmailRequestDTO and those of the inline defined object are generated as parameters and not as a DTO. See https://swagger.io/docs/specification/data-models/oneof-anyof-allof-not/#allof

    If you don't want Optional String are generated as parameter in the delegate, remove useOptional configuration. See https://openapi-generator.tech/docs/generators/spring/