Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add @PartFilename config - on/off, value, value suffix #452

Merged
merged 5 commits into from
Sep 5, 2023
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ public class MultipartBody {

@FormParam("file")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
@PartFilename("defaultFileName")
@PartFilename("fileFile")
public File file;

@FormParam("fileName")
Expand Down Expand Up @@ -639,6 +639,59 @@ quarkus.openapi-generator.codegen.spec.my_multipart_requests_yml.skip-form-model

See the module [multipart-request](integration-tests/multipart-request) for an example of how to use this feature.

In case the default `PartFilename` annotation is not required, its generation can be disabled by setting the `generate-part-filename` property corresponding to your spec in the `application.properties` to `false`, e.g.:

```properties
quarkus.openapi-generator.codegen.spec.my_multipart_requests_yml.generate-part-filename=false
```.

And in case the default `PartFilename` value is not suitable (e.g. a conversion service only allows/support specific extensions), the value can be set by using the `part-filename-value` property corresponding to your spec in the `application.properties`, e.g.:

```properties
quarkus.openapi-generator.codegen.spec.my_first_multipart_requests_yml.part-filename-value="some.pdf"
quarkus.openapi-generator.codegen.spec.my_second_multipart_requests_yml.part-filename-value-suffix=".pdf"
```.

By setting `part-filename-value` to `some.pdf` the generated code will look like this:

```java
public class MultipartBody {

@FormParam("file")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
@PartFilename("some.pdf")
public File file;

@FormParam("fileName")
@PartType(MediaType.TEXT_PLAIN)
public String fileName;

@FormParam("someObject")
@PartType(MediaType.APPLICATION_JSON)
public MyComplexObject someObject;
}

And by setting `part-filename-value-suffix` to `.pdf` the generated code will look like this:

```java
public class MultipartBody {

@FormParam("file")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
@PartFilename("file.pdf")
public File file;

@FormParam("fileName")
@PartType(MediaType.TEXT_PLAIN)
public String fileName;

@FormParam("someObject")
@PartType(MediaType.APPLICATION_JSON)
public MyComplexObject someObject;
}

See the module [part-filename](integration-tests/part-filename) for examples of how to use these features.

### Default content-types according to OpenAPI Specification and limitations

The [OAS 3.0](https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#special-considerations-for-multipart-content) specifies the following default content-types for a multipart:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,10 @@ public enum ConfigName {
IMPORT_MAPPINGS("import-mappings"),
NORMALIZER("open-api-normalizer"),
RETURN_RESPONSE("return-response"),
ENABLE_SECURITY_GENERATION("enable-security-generation");
ENABLE_SECURITY_GENERATION("enable-security-generation"),
GENERATE_PART_FILENAME("generate-part-filename"),
PART_FILENAME_VALUE("part-filename-value"),
PART_FILENAME_VALUE_SUFFIX("part-filename-value-suffix");

private final String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,29 @@ public class CommonItemConfig {
*/
@ConfigItem(name = "mutiny")
public Optional<Boolean> supportMutiny;

/**
* Defines, whether the `PartFilename` ({@link org.jboss.resteasy.reactive.PartFilename} or
* {@link org.jboss.resteasy.annotations.providers.multipart.PartFilename}) annotation should be generated for MultipartForm
* POJOs. By setting to `false`, the annotation will not be generated.
*/
@ConfigItem(name = "generate-part-filename")
public Optional<Boolean> generatePartFilename;

/**
* Defines the filename for a part in case the `PartFilename` annotation ({@link org.jboss.resteasy.reactive.PartFilename}
* or
* {@link org.jboss.resteasy.annotations.providers.multipart.PartFilename}) is generated.
*/
@ConfigItem(name = "part-filename-value")
public Optional<String> partFilenameValue;

/**
* Defines the filename's suffix, added to the property name, for a part in case the `PartFilename` annotation
* ({@link org.jboss.resteasy.reactive.PartFilename} or
* {@link org.jboss.resteasy.annotations.providers.multipart.PartFilename})
* is generated.
*/
@ConfigItem(name = "part-filename-value-suffix")
public Optional<String> partFilenameValueSuffix;
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,16 @@ protected void generate(final Config config, final Path openApiFilePath, final P
getValues(config, openApiFilePath, CodegenConfig.ConfigName.ENABLE_SECURITY_GENERATION, Boolean.class)
.orElse(true));

generator.withGeneratePartFilenameConfig(
getValues(config, openApiFilePath, CodegenConfig.ConfigName.GENERATE_PART_FILENAME, Boolean.class)
.orElse(true));

getValues(config, openApiFilePath, CodegenConfig.ConfigName.PART_FILENAME_VALUE, String.class)
.ifPresent(generator::withPartFilenameValueConfig);

getValues(config, openApiFilePath, CodegenConfig.ConfigName.PART_FILENAME_VALUE_SUFFIX, String.class)
.ifPresent(generator::withPartFilenameValueSuffixConfig);

SmallRyeConfig smallRyeConfig = config.unwrap(SmallRyeConfig.class);

getValues(smallRyeConfig, openApiFilePath, CodegenConfig.ConfigName.TYPE_MAPPINGS, String.class, String.class)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,21 @@ public void withTemplateDir(Path templateDir) {
this.configurator.addAdditionalProperty("templateDir", templateDir.toString());
}

public OpenApiClientGeneratorWrapper withGeneratePartFilenameConfig(final Boolean generatePartFilename) {
this.configurator.addAdditionalProperty("generate-part-filename", generatePartFilename);
return this;
}

public OpenApiClientGeneratorWrapper withPartFilenameValueConfig(final String partFilenameValue) {
this.configurator.addAdditionalProperty("part-filename-value", partFilenameValue);
return this;
}

public OpenApiClientGeneratorWrapper withPartFilenameValueSuffixConfig(final String partFilenameValueSiffix) {
patr1kt0th marked this conversation as resolved.
Show resolved Hide resolved
this.configurator.addAdditionalProperty("part-filename-value-suffix", partFilenameValueSiffix);
return this;
}

public List<File> generate(final String basePackage) {
this.basePackage = basePackage;
this.consolidatePackageNames();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,26 @@
@FormParam("{p.baseName}")
{#if p.isFile || (p.isString && p.dataFormat == 'base64')}
{#if is-resteasy-reactive}
{#if generate-part-filename}
{#if part-filename-value}
@org.jboss.resteasy.reactive.PartFilename("{part-filename-value}")
{#else if part-filename-value-suffix}
@org.jboss.resteasy.reactive.PartFilename("{p.baseName}{part-filename-value-suffix}")
{#else}
@org.jboss.resteasy.reactive.PartFilename("{p.baseName}File")
{/if}
{/if}
@org.jboss.resteasy.reactive.PartType(MediaType.APPLICATION_OCTET_STREAM)
{#else}
{#if generate-part-filename}
{#if part-filename-value}
@org.jboss.resteasy.annotations.providers.multipart.PartFilename("{part-filename-value}")
{#else if part-filename-value-suffix}
@org.jboss.resteasy.annotations.providers.multipart.PartFilename("{p.baseName}{part-filename-value-suffix}")
{#else}
@org.jboss.resteasy.annotations.providers.multipart.PartFilename("{p.baseName}File")
{/if}
{/if}
@org.jboss.resteasy.annotations.providers.multipart.PartType(MediaType.APPLICATION_OCTET_STREAM)
{/if}
{#else if p.isPrimitiveType or (p.isArray && p.items.isPrimitiveType)}
Expand All @@ -24,4 +40,4 @@
{/if}
public {p.dataType} {p.paramName};
{/for}
}
}
93 changes: 93 additions & 0 deletions integration-tests/part-filename/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-openapi-generator-integration-tests</artifactId>
<groupId>io.quarkiverse.openapi.generator</groupId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-openapi-generator-it-part-filename</artifactId>
<name>Quarkus - Openapi Generator - Integration Tests - PartFilename</name>

<dependencies>
<dependency>
<groupId>io.quarkiverse.openapi.generator</groupId>
<artifactId>quarkus-openapi-generator</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.resteasy</groupId>
<artifactId>resteasy-multipart-provider</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-resteasy-reactive-common</artifactId>
</dependency>
Comment on lines +20 to +27
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We declare dependencies that are specific for RESTEasy Reactive or Classic in its equivalent profile in integration-tests/pom.xml. I'm not sure about resteasy-multipart-provider, but you can move at least quarkus-resteasy-reactive-common to there.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the info, I noticed it, but I did not want to put them there, as they're (only) relevant to the part-filename tests and I didn't want to influence the rest of the tests.

I need them to be able to compile the tests as they're checking both types of annotations and I couldn't figure out, how to do it in a more elegant way. Or maybe I'm just doing something wrong?

Btw. I think the quarkus-resteasy-reactive-common should already be part of quarkus-rest-client-reactive-jackson which you already have there.

<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-maven-plugin</artifactId>
<extensions>true</extensions>
<executions>
<execution>
<goals>
<goal>build</goal>
<goal>generate-code</goal>
<goal>generate-code-tests</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<profiles>
<profile>
<id>native-image</id>
<activation>
<property>
<name>native</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skipTests>${native.surefire.skip}</skipTests>
</configuration>
</plugin>
<plugin>
<artifactId>maven-failsafe-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
</goals>
<configuration>
<systemPropertyVariables>
<native.image.path>${project.build.directory}/${project.build.finalName}-runner</native.image.path>
<java.util.logging.manager>org.jboss.logmanager.LogManager</java.util.logging.manager>
<maven.home>${maven.home}</maven.home>
</systemPropertyVariables>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<properties>
<quarkus.package.type>native</quarkus.package.type>
</properties>
</profile>
</profiles>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
openapi: 3.0.3
info:
title: "Multipart form data API"
description: An api that uses multipart/form-data as request type
version: 1.0.0

servers:
- url: "http://my.endpoint.com/api/v1"

paths:
/user-profile-data:
post:
tags:
- Do Not Generate Part Filename
description: Upload data for a user profile, adapted from https://swagger.io/docs/specification/describing-request-body/multipart-requests/
operationId: PostUserProfileData
requestBody:
required: true
description: The data of a user profile
content:
multipart/form-data:
schema:
$ref: "#/components/schemas/UserProfileData"
responses:
"204":
description: "Data uploaded"
"400":
description: "Invalid ID supplied"

components:
schemas:
Address:
title: Address
type: object
properties:
street:
type: string
description: Street name
example: 7th avenue
city:
type: string
description: Name of the city
example: New Delhi
UserId:
type: string
format: string
ProfileImage:
type: string
format: binary

UserProfileData:
type: object
properties: # Request parts
id: # Part 1 (string value)
$ref: "#/components/schemas/UserId"
address: # Part2 (object)
$ref: "#/components/schemas/Address"
profileImage: # Part 3 (an image)
$ref: "#/components/schemas/ProfileImage"
Loading