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.58k stars 6.52k forks source link

[BUG][Java][jaxrs-spec] Codegen creates invalid code for multipart/form-data for quarkus #7994

Open M-Landwehr opened 3 years ago

M-Landwehr commented 3 years ago
Description

When creating Java code from a openapi.yml for quakus, the resulting code does not work:

RESTEASY003875: Unable to find a constructor that takes a String param or a valueOf() or fromString() method for javax.ws.rs.FormParam("file") on public abstract void org.openapitools.api.UploadApi.uploadPost(java.io.InputStream,java.lang.String) for basetype: java.io.InputStream

Quarkus cannot handle the geneated method signature of the Rest-resource-class:

@POST
@Consumes({ "multipart/form-data" })
@Produces({ "application/json" })
@ApiOperation(value = "", notes = "", tags={  })
@ApiResponses(value = { 
@ApiResponse(code = 200, message = "", response = Void.class) })
void uploadPost( @FormParam(value = "file") InputStream fileInputStream,@FormParam(value = "name")  String name);

(Additionally, no validation annotations are created!)

openapi-generator version
<groupId>org.openapitools</groupId>
<artifactId>openapi-generator-maven-plugin</artifactId>
<version>4.3.1</version>
OpenAPI declaration file content or url
openapi: 3.0.1
info:
  title: ''
  description: ''
  version: "1.0"

paths:
  /upload:
    post:
      requestBody:
        content:
          multipart/form-data:
            schema:
              $ref: '#/components/schemas/MultipartData'
      responses:
        200:
          description: ''
          content:
            application/json: {}
components:
  schemas:
    MultipartData:
      type: object
      properties:
        file:
          format: binary
          type: string
        name:
          pattern: .*
          type: string
Command line used for generation

Quarkus-Version: 1.9.2.Final

<plugin>
    <groupId>org.openapitools</groupId>
    <artifactId>openapi-generator-maven-plugin</artifactId>
    <version>4.3.1</version>
    <executions>
        <execution>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <inputSpec>${project.basedir}/src/main/resources/META-INF/openapi.yml</inputSpec>
                <generatorName>jaxrs-spec</generatorName>
                <configOptions>
                    <library>quarkus</library>
                    <sourceFolder>src/gen/java/main</sourceFolder>
                    <interfaceOnly>true</interfaceOnly>
                </configOptions>
            </configuration>
        </execution>
    </executions>
</plugin>
Steps to reproduce

Alternatively simply generate java code from the openapi.yml above using the jaxrs-spec generator and the quarkus library

Related issues/PRs
Suggest a fix/enhancement

The model-class is created but is not used in this case. (althogh is has @JsonProperty annotations insteadod the @FormParam annotations

A possible fix would be to

mzellho commented 3 years ago

hey @M-Landwehr, did you find a way to work around this? we're facing the exact same issue, also tried a couple of other generators but didn't get lucky so far...btw still present in 5.1.0 :-/

AlbanSeurat commented 3 years ago

Any news on the issue, I am facing the same issue. Any work-around ?

gigaga commented 2 years ago

Same for me :(

fsiegrist commented 2 years ago

Same for me. Tried to use a custom ParamConverter (see https://resteasy.dev/2021/04/05/blog-ParamConverter-with-Quarkus/), without success.

fsiegrist commented 2 years ago

I "solved" it by modifying the mustache template files.

In api.mustache I added the following two imports:

import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
import org.jboss.resteasy.plugins.providers.multipart.MultipartFormDataInput;

In apiInterface.mustache I replaced the last line with {{#supportAsync}}{{>returnAsyncTypeInterface}}{{/supportAsync}}{{^supportAsync}}{{#returnResponse}}Response{{/returnResponse}}{{^returnResponse}}{{>returnTypeInterface}}{{/returnResponse}}{{/supportAsync}} {{nickname}}({{#isMultipart}}@MultipartForm MultipartFormDataInput multipartFormDataInput{{#allParams}}{{^isFormParam}}, {{/isFormParam}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{/allParams}}{{/isMultipart}}{{^isMultipart}}{{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}}, {{/-last}}{{/allParams}}{{/isMultipart}});

And in apiMethod.mustache I replaced the third line from the bottom with public {{#supportAsync}}CompletionStage<{{/supportAsync}}Response{{#supportAsync}}>{{/supportAsync}} {{nickname}}({{#isMultipart}}@MultipartForm MultipartFormDataInput multipartFormDataInput{{#allParams}}{{^isFormParam}}, {{/isFormParam}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{/allParams}}{{/isMultipart}}{{^isMultipart}}{{#allParams}}{{>queryParams}}{{>pathParams}}{{>cookieParams}}{{>headerParams}}{{>bodyParams}}{{>formParams}}{{^-last}}, {{/-last}}{{/allParams}}{{/isMultipart}}) {

Ragin-LundF commented 2 years ago

@fsiegrist It works with the link you provided. But you need to implement both classes:

package example

import java.io.ByteArrayInputStream
import java.io.InputStream
import java.nio.charset.StandardCharsets
import javax.ws.rs.ext.ParamConverter
import javax.ws.rs.ext.Provider

@Provider
class FileConverterProvider: ParamConverter<InputStream> {
    override fun toString(value: InputStream): String {
        return String(value.readAllBytes(), StandardCharsets.UTF_8)
    }

    override fun fromString(value: String): InputStream {
        return ByteArrayInputStream(value.toByteArray())
    }
}
package example

import java.io.InputStream
import java.lang.reflect.Type
import javax.ws.rs.ext.ParamConverter
import javax.ws.rs.ext.ParamConverterProvider
import javax.ws.rs.ext.Provider

@Provider
class FileParamConverterProvider: ParamConverterProvider {
    override fun <T : Any?> getConverter(
        rawType: Class<T>?,
        genericType: Type?,
        annotations: Array<out Annotation>?
    ): ParamConverter<T>? {
        return if (rawType!!.isAssignableFrom(InputStream::class.java)) {
            (FileConverterProvider() as ParamConverter<T>?)!!
        } else null
    }

}
vikalpsareen commented 2 years ago

@fsiegrist thanks for suggesting custom converter, it worked for me below is the java code


@Provider
public class InstantParamConverter implements ParamConverter<InputStream> {

   public InputStream fromString(String value){
      try {
         return  IOUtils.toInputStream(value);
      } catch (Exception e) {

      }
      return null;
   }

   public String toString(InputStream value){
      return value.toString();
   }
}
@Provider
public class InstantParamConverterProvider implements ParamConverterProvider
{

   @Override
   public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations)
   {
      if (rawType.isAssignableFrom(InputStream.class)) {
         return (ParamConverter<T>) new InstantParamConverter();
      }
      return null;
   }
}