swagger-api / swagger-codegen

swagger-codegen contains a template-driven engine to generate documentation, API clients and server stubs in different languages by parsing your OpenAPI / Swagger definition.
http://swagger.io
Apache License 2.0
17.04k stars 6.03k forks source link

[BUG] For java Resttemplate Client there is no way to upload file with defined contentType #7538

Open bademux opened 6 years ago

bademux commented 6 years ago

Hello, there is no way to do the same request (providing application/pdf) as

curl -X POST -H "content-type: multipart/form-data" -F "file=@pdf-test.pdf;type=application/pdf"

to do this java.io.File should be replaced with more generic org.springframework.http.HttpEntity

    private HttpEntity<Object> createUploadData(MediaType mimeType, InputStream attachment) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(mimeType);
        return new HttpEntity<>(new org.springframework.core.io.InputStreamResource(attachment), headers);
    }

HttpEntity allows us to use filename and file mime type.

My proposition is to replace formParams.add("file", new FileSystemResource(file)); with formParams.add("file", httpEntity)

Side notes:

  1. java.io.File is very restrictive object type, you can`t extend it in a simple way.
  2. selectBody logic is misplaced. by the time we invoke ApiClient.invokeAPI(), MyGeneratedApi shpould determine whether we will use Object body or MultiValueMap<String, Object> formParams to pass form parameters
bademux commented 6 years ago

My hackfix:

@UtilityClass
public class HttpEntityFactory {
    public static HttpEntity<?> createHttpEntityFrom(Resource resource) {
        return new HttpEntity<>(resource, createHttpHeadersFrom(resource));
    }

    private static HttpHeaders createHttpHeadersFrom(Resource resource) {
        if (resource instanceof UploadFileResource) {
            HttpHeaders headers = new HttpHeaders();
            headers.setContentType(((UploadFileResource) resource).getType());
            return headers;
        }
        return null;
    }

    @Getter
    public final class UploadFileResource extends InputStreamResource {
        private final String filename;
        private final MediaType contentType;

        @Builder
        public UploadFileResource(InputStream inputStream, String filename, String contentType) {
            super(inputStream, "UploadFileSource named: '" + String.valueOf(filename) + "', type: " + String.valueOf(contentType));
            this.filename = filename;
            this.contentType = contentType == null ? null : MediaType.parseMediaType(contentType);
        }

        @Override
        public long contentLength() throws IOException {
            return -1;
        }
    }

}

Gradle task:

task swagger {
    def swaggerConfig = new io.swagger.codegen.config.CodegenConfigurator()
    swaggerConfig.inputSpec = "$rootDir/test.yaml"
    swaggerConfig.outputDir = project.projectDir
    //....
    swaggerConfig.lang = 'java'
    swaggerConfig.library = 'resttemplate'
    println 'Applying HACKFIX: upload file with defined contentType https://github.com/swagger-api/swagger-codegen/issues/7538' 
    swaggerConfig.typeMappings = [
            'File': 'org.springframework.core.io.InputStreamResource',
    ]
    inputs.file(swaggerConfig.inputSpec)
    outputs.dir(swaggerConfig.outputDir)
    doLast {
        def generator = new io.swagger.codegen.DefaultGenerator().opts(swaggerConfig.toClientOptInput())
        def generatedFiles = generator.generate()
        println 'Applying HACKFIX: upload file with defined contentType https://github.com/swagger-api/swagger-codegen/issues/7538'
        generatedFiles.findAll { it.name.contains('AttachmentApi') }
                .each { it.text = it.text.replace('new FileSystemResource', 'HttpEntityFactory.createHttpEntityFrom') }
    }
}