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 all 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
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,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 @@ -427,8 +427,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 @@ -580,7 +578,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 +637,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 @@ -184,6 +184,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>
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