Skip to content

Commit

Permalink
Add @PartFilename config - on/off, value, value suffix (#452)
Browse files Browse the repository at this point in the history
* Add @PartFilename config - on/off, value, value suffix

* Split PartFilenameTest into classic and reactive

* Get rid of code duplicity in tests, fix README.md

* Revert messed up README.md formatting

* Apply suggestions from code review

Co-authored-by: Ricardo Zanini <[email protected]>

---------

Co-authored-by: Ricardo Zanini <[email protected]>
  • Loading branch information
patr1kt0th and ricardozanini authored Sep 5, 2023
1 parent 72831fb commit adfa283
Show file tree
Hide file tree
Showing 17 changed files with 719 additions and 6 deletions.
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ quarkus.openapi-generator.codegen.spec.my_openapi_yaml.return-response=true
Since the most part of this extension work is in the `generate-code` execution phase of the Quarkus Maven's plugin, the log configuration must be set in the Maven context. When building your project, add `-Dorg.slf4j.simpleLogger.log.org.openapitools=off` to the `mvn` command to reduce the internal generator noise. For example:

```shell
mvn clean install -Dorg.slf4j.simpleLogger.log.org.openapitools=off
mvn clean install -Dorg.slf4j.simpleLogger.log.org.openapitools=off
```

For more information, see the [Maven Logging Configuration](https://maven.apache.org/maven-logging.html) guide.
Expand Down Expand Up @@ -430,8 +430,6 @@ The token propagation can be used with type "oauth2" or "bearer" security scheme
| `quarkus.openapi-generator.[filename].auth.[security_scheme_name].token-propagation=[true,false]` | `quarkus.openapi-generator.petstore_json.auth.petstore_auth.token-propagation=true`<br/>Enables the token propagation for all the operations that are secured with the `petstore_auth` scheme in the `petstore_json` file.
| `quarkus.openapi-generator.[filename].auth.[security_scheme_name].header-name=[http_header_name]` | `quarkus.openapi-generator.petstore_json.auth.petstore_auth.header-name=MyHeaderName`<br/>Says that the authorization token to propagate will be read from the HTTP header `MyHeaderName` instead of the standard HTTP `Authorization` header.



## Circuit Breaker

You can define the [CircuitBreaker annotation from MicroProfile Fault Tolerance](https://microprofile.io/project/eclipse/microprofile-fault-tolerance/spec/src/main/asciidoc/circuitbreaker.asciidoc)
Expand Down Expand Up @@ -583,7 +581,7 @@ public class MultipartBody {

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

@FormParam("fileName")
Expand Down Expand Up @@ -642,6 +640,50 @@ 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 (globally or 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
```

By default, the `PartFilename`'s value representing the filename is prefixed by the field name. This can be changed by setting the `use-field-name-in-part-filename` property (globally or corresponding to your spec) in the `application.properties` to `false`, e.g.:

```properties
quarkus.openapi-generator.codegen.spec.my_multipart_requests_yml.use-field-name-in-part-filename=false
```

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

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

So for instance, by setting `part-filename-value` to `some.pdf` and `use-field-name-in-part-filename` to `false` the generated code will look like this:

```java
public class MultipartBody {

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

And by setting only `part-filename-value` 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;
}
```

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"),
USE_FIELD_NAME_IN_PART_FILENAME("use-field-name-in-part-filename");

private final String name;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,30 @@ 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.
* In case no value is set, the default one is `&lt;fieldName&gt;File` or `file`, depending on the
* {@link CommonItemConfig#useFieldNameInPartFilename} configuration.
*/
@ConfigItem(name = "part-filename-value")
public Optional<String> partFilenameValue;

/**
* Defines, whether the filename should also include the property name in case the `PartFilename` annotation
* ({@link org.jboss.resteasy.reactive.PartFilename} or
* {@link org.jboss.resteasy.annotations.providers.multipart.PartFilename}) is generated.
*/
@ConfigItem(name = "use-field-name-in-part-filename")
public Optional<Boolean> useFieldNameInPartFilename;
}
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,18 @@ 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);

generator.withUseFieldNameInPartFilenameConfig(
getValues(config, openApiFilePath, CodegenConfig.ConfigName.USE_FIELD_NAME_IN_PART_FILENAME,
Boolean.class)
.orElse(true));

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 withUseFieldNameInPartFilenameConfig(final Boolean useFieldNameInPartFilename) {
this.configurator.addAdditionalProperty("use-field-name-in-part-filename", useFieldNameInPartFilename);
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,38 @@
@FormParam("{p.baseName}")
{#if p.isFile || (p.isString && p.dataFormat == 'base64')}
{#if is-resteasy-reactive}
{#if generate-part-filename}
{#if part-filename-value}
{#if use-field-name-in-part-filename}
@org.jboss.resteasy.reactive.PartFilename("{p.baseName}{part-filename-value}")
{#else}
@org.jboss.resteasy.reactive.PartFilename("{part-filename-value}")
{/if}
{#else}
{#if use-field-name-in-part-filename}
@org.jboss.resteasy.reactive.PartFilename("{p.baseName}File")
{#else}
@org.jboss.resteasy.reactive.PartFilename("file")
{/if}
{/if}
{/if}
@org.jboss.resteasy.reactive.PartType(MediaType.APPLICATION_OCTET_STREAM)
{#else}
{#if generate-part-filename}
{#if part-filename-value}
{#if use-field-name-in-part-filename}
@org.jboss.resteasy.annotations.providers.multipart.PartFilename("{p.baseName}{part-filename-value}")
{#else}
@org.jboss.resteasy.annotations.providers.multipart.PartFilename("{part-filename-value}")
{/if}
{#else}
{#if use-field-name-in-part-filename}
@org.jboss.resteasy.annotations.providers.multipart.PartFilename("{p.baseName}File")
{#else}
@org.jboss.resteasy.annotations.providers.multipart.PartFilename("file")
{/if}
{/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 +52,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>
<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

0 comments on commit adfa283

Please sign in to comment.