diff --git a/README.md b/README.md
index 4a13d762..131c7834 100644
--- a/README.md
+++ b/README.md
@@ -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.
@@ -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`
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`
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)
@@ -580,7 +578,7 @@ public class MultipartBody {
@FormParam("file")
@PartType(MediaType.APPLICATION_OCTET_STREAM)
- @PartFilename("defaultFileName")
+ @PartFilename("fileFile")
public File file;
@FormParam("fileName")
@@ -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:
diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java
index f9b06870..c30e3d38 100644
--- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java
+++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CodegenConfig.java
@@ -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;
diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CommonItemConfig.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CommonItemConfig.java
index 3be6ef13..1ec1e375 100644
--- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CommonItemConfig.java
+++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/CommonItemConfig.java
@@ -71,4 +71,30 @@ public class CommonItemConfig {
*/
@ConfigItem(name = "mutiny")
public Optional 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 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 `<fieldName>File` or `file`, depending on the
+ * {@link CommonItemConfig#useFieldNameInPartFilename} configuration.
+ */
+ @ConfigItem(name = "part-filename-value")
+ public Optional 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 useFieldNameInPartFilename;
}
diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java
index 19712b9a..a43536fd 100644
--- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java
+++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/codegen/OpenApiGeneratorCodeGenBase.java
@@ -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)
diff --git a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java
index 38f30d6b..eb30fee0 100644
--- a/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java
+++ b/deployment/src/main/java/io/quarkiverse/openapi/generator/deployment/wrapper/OpenApiClientGeneratorWrapper.java
@@ -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 generate(final String basePackage) {
this.basePackage = basePackage;
this.consolidatePackageNames();
diff --git a/deployment/src/main/resources/templates/libraries/microprofile/multipartFormdataPojo.qute b/deployment/src/main/resources/templates/libraries/microprofile/multipartFormdataPojo.qute
index 5e8bd470..c8bf791c 100644
--- a/deployment/src/main/resources/templates/libraries/microprofile/multipartFormdataPojo.qute
+++ b/deployment/src/main/resources/templates/libraries/microprofile/multipartFormdataPojo.qute
@@ -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)}
@@ -24,4 +52,4 @@
{/if}
public {p.dataType} {p.paramName};
{/for}
- }
\ No newline at end of file
+ }
diff --git a/integration-tests/part-filename/pom.xml b/integration-tests/part-filename/pom.xml
new file mode 100644
index 00000000..a97e3344
--- /dev/null
+++ b/integration-tests/part-filename/pom.xml
@@ -0,0 +1,93 @@
+
+
+
+ quarkus-openapi-generator-integration-tests
+ io.quarkiverse.openapi.generator
+ 3.0.0-SNAPSHOT
+
+ 4.0.0
+
+ quarkus-openapi-generator-it-part-filename
+ Quarkus - Openapi Generator - Integration Tests - PartFilename
+
+
+
+ io.quarkiverse.openapi.generator
+ quarkus-openapi-generator
+
+
+ org.jboss.resteasy
+ resteasy-multipart-provider
+
+
+ io.quarkus
+ quarkus-resteasy-reactive-common
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+
+
+
+ native-image
+
+
+ native
+
+
+
+
+
+ maven-surefire-plugin
+
+ ${native.surefire.skip}
+
+
+
+ maven-failsafe-plugin
+
+
+
+ integration-test
+ verify
+
+
+
+ ${project.build.directory}/${project.build.finalName}-runner
+ org.jboss.logmanager.LogManager
+ ${maven.home}
+
+
+
+
+
+
+
+
+ native
+
+
+
+
\ No newline at end of file
diff --git a/integration-tests/part-filename/src/main/openapi/do-not-generate-part-filename.yml b/integration-tests/part-filename/src/main/openapi/do-not-generate-part-filename.yml
new file mode 100644
index 00000000..8218696e
--- /dev/null
+++ b/integration-tests/part-filename/src/main/openapi/do-not-generate-part-filename.yml
@@ -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"
diff --git a/integration-tests/part-filename/src/main/openapi/do-not-use-field-name-in-part-filename.yml b/integration-tests/part-filename/src/main/openapi/do-not-use-field-name-in-part-filename.yml
new file mode 100644
index 00000000..49c9f937
--- /dev/null
+++ b/integration-tests/part-filename/src/main/openapi/do-not-use-field-name-in-part-filename.yml
@@ -0,0 +1,61 @@
+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 Use Field Name In 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'
+
+
diff --git a/integration-tests/part-filename/src/main/openapi/generate-part-filename.yml b/integration-tests/part-filename/src/main/openapi/generate-part-filename.yml
new file mode 100644
index 00000000..622d5aef
--- /dev/null
+++ b/integration-tests/part-filename/src/main/openapi/generate-part-filename.yml
@@ -0,0 +1,61 @@
+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:
+ - 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'
+
+
diff --git a/integration-tests/part-filename/src/main/openapi/global-generate-part-filename.yml b/integration-tests/part-filename/src/main/openapi/global-generate-part-filename.yml
new file mode 100644
index 00000000..9b864c30
--- /dev/null
+++ b/integration-tests/part-filename/src/main/openapi/global-generate-part-filename.yml
@@ -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:
+ - Global 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"
diff --git a/integration-tests/part-filename/src/main/openapi/part-filename-value.yml b/integration-tests/part-filename/src/main/openapi/part-filename-value.yml
new file mode 100644
index 00000000..8ec7fe98
--- /dev/null
+++ b/integration-tests/part-filename/src/main/openapi/part-filename-value.yml
@@ -0,0 +1,61 @@
+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:
+ - Part Filename Value
+ 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'
+
+
diff --git a/integration-tests/part-filename/src/main/resources/application.properties b/integration-tests/part-filename/src/main/resources/application.properties
new file mode 100644
index 00000000..b48826d7
--- /dev/null
+++ b/integration-tests/part-filename/src/main/resources/application.properties
@@ -0,0 +1,25 @@
+# By default the openapi-generator doesn't generate models for multipart requests
+quarkus.openapi-generator.codegen.skip-form-model=false
+# global
+quarkus.openapi-generator.codegen.generate-part-filename=false
+quarkus.openapi-generator.codegen.spec.global_generate_part_filename_yml.base-package=org.acme.openapi
+quarkus.openapi-generator.codegen.spec.global_generate_part_filename_yml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection
+# do not generate
+quarkus.openapi-generator.codegen.spec.do_not_generate_part_filename_yml.base-package=org.acme.openapi
+quarkus.openapi-generator.codegen.spec.do_not_generate_part_filename_yml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection
+quarkus.openapi-generator.codegen.spec.do_not_generate_part_filename_yml.generate-part-filename=false
+# generate
+quarkus.openapi-generator.codegen.spec.generate_part_filename_yml.base-package=org.acme.openapi
+quarkus.openapi-generator.codegen.spec.generate_part_filename_yml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection
+quarkus.openapi-generator.codegen.spec.generate_part_filename_yml.generate-part-filename=true
+# value
+quarkus.openapi-generator.codegen.spec.part_filename_value_yml.base-package=org.acme.openapi
+quarkus.openapi-generator.codegen.spec.part_filename_value_yml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection
+quarkus.openapi-generator.codegen.spec.part_filename_value_yml.generate-part-filename=true
+quarkus.openapi-generator.codegen.spec.part_filename_value_yml.part-filename-value=.pdf
+# do not use field name
+quarkus.openapi-generator.codegen.spec.do_not_use_field_name_in_part_filename_yml.base-package=org.acme.openapi
+quarkus.openapi-generator.codegen.spec.do_not_use_field_name_in_part_filename_yml.additional-model-type-annotations=@io.quarkus.runtime.annotations.RegisterForReflection
+quarkus.openapi-generator.codegen.spec.do_not_use_field_name_in_part_filename_yml.generate-part-filename=true
+quarkus.openapi-generator.codegen.spec.do_not_use_field_name_in_part_filename_yml.part-filename-value=test.pdf
+quarkus.openapi-generator.codegen.spec.do_not_use_field_name_in_part_filename_yml.use-field-name-in-part-filename=false
diff --git a/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/BasePartFilenameTest.java b/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/BasePartFilenameTest.java
new file mode 100644
index 00000000..aeb13e49
--- /dev/null
+++ b/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/BasePartFilenameTest.java
@@ -0,0 +1,87 @@
+package io.quarkiverse.openapi.generator.it;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+
+import java.lang.reflect.Field;
+
+import jakarta.ws.rs.FormParam;
+
+import org.acme.openapi.api.DoNotGeneratePartFilenameApi;
+import org.acme.openapi.api.DoNotUseFieldNameInPartFilenameApi;
+import org.acme.openapi.api.GeneratePartFilenameApi;
+import org.acme.openapi.api.GlobalGeneratePartFilenameApi;
+import org.acme.openapi.api.PartFilenameValueApi;
+import org.junit.jupiter.api.Test;
+
+public abstract class BasePartFilenameTest {
+
+ protected static final String PROFILE_IMAGE = "profileImage";
+
+ protected abstract void testPartTypeAnnotation(Field field);
+
+ protected abstract void testPartFilenameAnnotation(Field field, boolean present, String value);
+
+ protected void testNonPresentPartFilenameAnnotation(Field field) {
+ this.testPartFilenameAnnotation(field, false, null);
+ }
+
+ protected Field getProfileImageField(Class> clazz) throws NoSuchFieldException {
+ return clazz.getField(PROFILE_IMAGE);
+ }
+
+ protected void testOtherAnnotations(Field field) {
+ // test @FormParam, it is the same for classic and reactive
+ var formParam = field.getAnnotation(FormParam.class);
+ assertNotNull(formParam);
+ assertEquals(PROFILE_IMAGE, formParam.value());
+
+ // test @PartType, it is different for classic and reactive
+ testPartTypeAnnotation(field);
+ }
+
+ @Test
+ void testGlobalGeneratePartFilename() throws NoSuchFieldException {
+ var field = getProfileImageField(
+ GlobalGeneratePartFilenameApi.PostUserProfileDataMultipartForm.class);
+
+ this.testOtherAnnotations(field);
+ this.testNonPresentPartFilenameAnnotation(field);
+ }
+
+ @Test
+ void testDoNotGeneratePartFilename() throws NoSuchFieldException {
+ var field = getProfileImageField(
+ DoNotGeneratePartFilenameApi.PostUserProfileDataMultipartForm.class);
+
+ this.testOtherAnnotations(field);
+ this.testNonPresentPartFilenameAnnotation(field);
+ }
+
+ @Test
+ void testGeneratePartFilename() throws NoSuchFieldException {
+ var field = getProfileImageField(
+ GeneratePartFilenameApi.PostUserProfileDataMultipartForm.class);
+
+ this.testOtherAnnotations(field);
+ this.testPartFilenameAnnotation(field, true, PROFILE_IMAGE + "File");
+ }
+
+ @Test
+ void testPartFilenameValue() throws NoSuchFieldException {
+ var field = getProfileImageField(
+ PartFilenameValueApi.PostUserProfileDataMultipartForm.class);
+
+ this.testOtherAnnotations(field);
+ this.testPartFilenameAnnotation(field, true, PROFILE_IMAGE + ".pdf");
+ }
+
+ @Test
+ void testDoNotUseFieldNameInPartFilename() throws NoSuchFieldException {
+ var field = getProfileImageField(
+ DoNotUseFieldNameInPartFilenameApi.PostUserProfileDataMultipartForm.class);
+
+ this.testOtherAnnotations(field);
+ this.testPartFilenameAnnotation(field, true, "test.pdf");
+ }
+}
diff --git a/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/PartFilenameRestEasyClassicTest.java b/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/PartFilenameRestEasyClassicTest.java
new file mode 100644
index 00000000..5fce40ec
--- /dev/null
+++ b/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/PartFilenameRestEasyClassicTest.java
@@ -0,0 +1,40 @@
+package io.quarkiverse.openapi.generator.it;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.lang.reflect.Field;
+
+import jakarta.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.annotations.providers.multipart.PartFilename;
+import org.jboss.resteasy.annotations.providers.multipart.PartType;
+import org.junit.jupiter.api.Tag;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@Tag("resteasy-classic")
+class PartFilenameRestEasyClassicTest extends BasePartFilenameTest {
+
+ @Override
+ protected void testPartTypeAnnotation(Field field) {
+ var partType = field.getAnnotation(PartType.class);
+ assertNotNull(partType);
+ assertEquals(MediaType.APPLICATION_OCTET_STREAM, partType.value());
+ }
+
+ @Override
+ protected void testPartFilenameAnnotation(Field field, boolean present, String value) {
+ var partFilename = field.getAnnotation(PartFilename.class);
+ if (present) {
+ assertNotNull(partFilename);
+ if (value != null) {
+ assertEquals(value, partFilename.value());
+ }
+ } else {
+ assertNull(partFilename);
+ }
+ }
+}
diff --git a/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/PartFilenameRestEasyReactiveTest.java b/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/PartFilenameRestEasyReactiveTest.java
new file mode 100644
index 00000000..5010e2bc
--- /dev/null
+++ b/integration-tests/part-filename/src/test/java/io/quarkiverse/openapi/generator/it/PartFilenameRestEasyReactiveTest.java
@@ -0,0 +1,40 @@
+package io.quarkiverse.openapi.generator.it;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import java.lang.reflect.Field;
+
+import jakarta.ws.rs.core.MediaType;
+
+import org.jboss.resteasy.reactive.PartFilename;
+import org.jboss.resteasy.reactive.PartType;
+import org.junit.jupiter.api.Tag;
+
+import io.quarkus.test.junit.QuarkusTest;
+
+@QuarkusTest
+@Tag("resteasy-reactive")
+class PartFilenameRestEasyReactiveTest extends BasePartFilenameTest {
+
+ @Override
+ protected void testPartTypeAnnotation(Field field) {
+ var partType = field.getAnnotation(PartType.class);
+ assertNotNull(partType);
+ assertEquals(MediaType.APPLICATION_OCTET_STREAM, partType.value());
+ }
+
+ @Override
+ protected void testPartFilenameAnnotation(Field field, boolean present, String value) {
+ var partFilename = field.getAnnotation(PartFilename.class);
+ if (present) {
+ assertNotNull(partFilename);
+ if (value != null) {
+ assertEquals(value, partFilename.value());
+ }
+ } else {
+ assertNull(partFilename);
+ }
+ }
+}
diff --git a/integration-tests/pom.xml b/integration-tests/pom.xml
index 8176e93e..c57cc333 100644
--- a/integration-tests/pom.xml
+++ b/integration-tests/pom.xml
@@ -23,6 +23,7 @@
multipart-request
mutiny
open-api-normalizer
+ part-filename
return-response
security
simple