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