diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/formParams.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/formParams.mustache index 9508d72750d..fdb51cc6c69 100644 --- a/modules/openapi-generator/src/main/resources/JavaSpring/formParams.mustache +++ b/modules/openapi-generator/src/main/resources/JavaSpring/formParams.mustache @@ -1 +1 @@ -{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}@RequestPart{{/isArray}}{{^isArray}}{{#reactive}}@RequestPart{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/isArray}}{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{{dataType}}} {{paramName}}{{/isFile}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#isArray}}List<{{/isArray}}{{#reactive}}Flux{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}} {{paramName}}{{/isFile}}{{/isFormParam}} \ No newline at end of file +{{#isFormParam}}{{^isFile}}{{>paramDoc}}{{#useBeanValidation}} @Valid{{/useBeanValidation}} {{#isModel}}@RequestPart{{/isModel}}{{^isModel}}{{#isArray}}@RequestPart{{/isArray}}{{^isArray}}{{#reactive}}@RequestPart{{/reactive}}{{^reactive}}@RequestParam{{/reactive}}{{/isArray}}{{/isModel}}(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}){{>dateTimeParam}} {{^required}}{{#useOptional}}Optional<{{/useOptional}}{{/required}}{{{dataType}}}{{^required}}{{#useOptional}}>{{/useOptional}}{{/required}} {{paramName}}{{/isFile}}{{#isFile}}{{>paramDoc}} @RequestPart(value = "{{baseName}}"{{#required}}, required = true{{/required}}{{^required}}, required = false{{/required}}) {{#isArray}}List<{{/isArray}}{{#reactive}}Flux{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}} {{paramName}}{{/isFile}}{{/isFormParam}} \ No newline at end of file diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java index 917a912281e..328a8acdb34 100644 --- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java +++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/java/spring/SpringCodegenTest.java @@ -5384,6 +5384,37 @@ public class SpringCodegenTest { JavaFileAssert.assertThat(files.get("Type.java")).fileContains("Type implements java.io.Serializable {"); } + @Test + public void givenMultipartForm_whenGenerateUsingOptional_thenParameterAreCreatedAsOptional() throws IOException { + File output = Files.createTempDirectory("test").toFile().getCanonicalFile(); + output.deleteOnExit(); + String outputPath = output.getAbsolutePath().replace('\\', '/'); + + final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/spring/issue_9530.yaml"); + final SpringCodegen codegen = new SpringCodegen(); + codegen.additionalProperties().put(INTERFACE_ONLY, "true"); + codegen.additionalProperties().put(SpringCodegen.USE_OPTIONAL, "true"); + codegen.setOpenAPI(openAPI); + codegen.setOutputDir(output.getAbsolutePath()); + + ClientOptInput input = new ClientOptInput(); + input.openAPI(openAPI); + input.config(codegen); + + + DefaultGenerator generator = new DefaultGenerator(); + generator.setGenerateMetadata(false); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_TESTS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.MODEL_DOCS, "false"); + generator.setGeneratorPropertyDefault(CodegenConstants.APIS, "true"); + + generator.opts(input).generate(); + + assertFileContains(Paths.get(outputPath + "/src/main/java/org/openapitools/api/PetApi.java"), + "@Valid @RequestParam(value = \"additionalMetadata\", required = false) Optional additionalMetadata", + "@Valid @RequestParam(value = \"length\", required = true) Integer length"); + } + @Test public void shouldEnableBuiltInValidationOptionWhenSetToTrue() throws IOException { final SpringCodegen codegen = new SpringCodegen(); diff --git a/modules/openapi-generator/src/test/resources/3_0/spring/issue_9530.yaml b/modules/openapi-generator/src/test/resources/3_0/spring/issue_9530.yaml new file mode 100644 index 00000000000..6a564293285 --- /dev/null +++ b/modules/openapi-generator/src/test/resources/3_0/spring/issue_9530.yaml @@ -0,0 +1,75 @@ +openapi: 3.0.1 +info: + version: "1.0.0" + title: use-optional-multipart-spring-boot-request-body-issue +paths: + /pet/{petId}/uploadImage: + post: + tags: + - pet tag + summary: uploads an image + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + requestBody: + content: + multipart/form-data: + schema: + required: + - length + properties: + additionalMetadata: + type: string + description: Additional data to pass to server + length: + type: integer + description: Content length + file: + type: string + description: file to upload + format: binary + responses: + 200: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets +components: + schemas: + ApiResponse: + title: An uploaded response + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + description: Describes the result of uploading an image resource + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/api/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header + diff --git a/samples/openapi3/client/petstore/spring-cloud-3-with-optional/src/main/java/org/openapitools/api/PetApi.java b/samples/openapi3/client/petstore/spring-cloud-3-with-optional/src/main/java/org/openapitools/api/PetApi.java index 8bdcd1c986a..282cf379a85 100644 --- a/samples/openapi3/client/petstore/spring-cloud-3-with-optional/src/main/java/org/openapitools/api/PetApi.java +++ b/samples/openapi3/client/petstore/spring-cloud-3-with-optional/src/main/java/org/openapitools/api/PetApi.java @@ -166,8 +166,8 @@ public interface PetApi { ResponseEntity updatePetWithForm( @PathVariable("petId") Long petId, - @Valid @RequestParam(value = "name", required = false) String name, - @Valid @RequestParam(value = "status", required = false) String status + @Valid @RequestParam(value = "name", required = false) Optional name, + @Valid @RequestParam(value = "status", required = false) Optional status ); @@ -189,7 +189,7 @@ public interface PetApi { ResponseEntity uploadFile( @PathVariable("petId") Long petId, - @Valid @RequestParam(value = "additionalMetadata", required = false) String additionalMetadata, + @Valid @RequestParam(value = "additionalMetadata", required = false) Optional additionalMetadata, @RequestPart(value = "file", required = false) MultipartFile file ); diff --git a/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/FakeApi.java b/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/FakeApi.java index 36e53145c70..907e29eb80e 100644 --- a/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/FakeApi.java +++ b/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/FakeApi.java @@ -396,16 +396,16 @@ public interface FakeApi { @ApiParam(value = "None", required = true) @Valid @RequestParam(value = "double", required = true) Double _double, @ApiParam(value = "None", required = true) @Valid @RequestParam(value = "pattern_without_delimiter", required = true) String patternWithoutDelimiter, @ApiParam(value = "None", required = true) @Valid @RequestParam(value = "byte", required = true) byte[] _byte, - @ApiParam(value = "None") @Valid @RequestParam(value = "integer", required = false) Integer integer, - @ApiParam(value = "None") @Valid @RequestParam(value = "int32", required = false) Integer int32, - @ApiParam(value = "None") @Valid @RequestParam(value = "int64", required = false) Long int64, - @ApiParam(value = "None") @Valid @RequestParam(value = "float", required = false) Float _float, - @ApiParam(value = "None") @Valid @RequestParam(value = "string", required = false) String string, + @ApiParam(value = "None") @Valid @RequestParam(value = "integer", required = false) Optional integer, + @ApiParam(value = "None") @Valid @RequestParam(value = "int32", required = false) Optional int32, + @ApiParam(value = "None") @Valid @RequestParam(value = "int64", required = false) Optional int64, + @ApiParam(value = "None") @Valid @RequestParam(value = "float", required = false) Optional _float, + @ApiParam(value = "None") @Valid @RequestParam(value = "string", required = false) Optional string, @ApiParam(value = "None") @RequestPart(value = "binary", required = false) MultipartFile binary, - @ApiParam(value = "None") @Valid @RequestParam(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate date, - @ApiParam(value = "None") @Valid @RequestParam(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) OffsetDateTime dateTime, - @ApiParam(value = "None") @Valid @RequestParam(value = "password", required = false) String password, - @ApiParam(value = "None") @Valid @RequestParam(value = "callback", required = false) String paramCallback + @ApiParam(value = "None") @Valid @RequestParam(value = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional date, + @ApiParam(value = "None") @Valid @RequestParam(value = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Optional dateTime, + @ApiParam(value = "None") @Valid @RequestParam(value = "password", required = false) Optional password, + @ApiParam(value = "None") @Valid @RequestParam(value = "callback", required = false) Optional paramCallback ) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); @@ -450,8 +450,8 @@ public interface FakeApi { @ApiParam(value = "Query parameter enum test (string)", allowableValues = "_abc, -efg, (xyz)", defaultValue = "-efg") @Valid @RequestParam(value = "enum_query_string", required = false, defaultValue = "-efg") Optional enumQueryString, @ApiParam(value = "Query parameter enum test (double)", allowableValues = "1, -2") @Valid @RequestParam(value = "enum_query_integer", required = false) Optional enumQueryInteger, @ApiParam(value = "Query parameter enum test (double)", allowableValues = "1.1, -1.2") @Valid @RequestParam(value = "enum_query_double", required = false) Optional enumQueryDouble, - @ApiParam(value = "Form parameter enum test (string array)", allowableValues = ">, $", defaultValue = "$") @Valid @RequestPart(value = "enum_form_string_array", required = false) List enumFormStringArray, - @ApiParam(value = "Form parameter enum test (string)", allowableValues = "_abc, -efg, (xyz)", defaultValue = "-efg") @Valid @RequestParam(value = "enum_form_string", required = false) String enumFormString + @ApiParam(value = "Form parameter enum test (string array)", allowableValues = ">, $", defaultValue = "$") @Valid @RequestPart(value = "enum_form_string_array", required = false) Optional> enumFormStringArray, + @ApiParam(value = "Form parameter enum test (string)", allowableValues = "_abc, -efg, (xyz)", defaultValue = "-efg") @Valid @RequestParam(value = "enum_form_string", required = false) Optional enumFormString ) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); @@ -698,7 +698,7 @@ public interface FakeApi { default ResponseEntity uploadFileWithRequiredFile( @ApiParam(value = "ID of pet to update", required = true) @PathVariable("petId") Long petId, @ApiParam(value = "file to upload", required = true) @RequestPart(value = "requiredFile", required = true) MultipartFile requiredFile, - @ApiParam(value = "Additional data to pass to server") @Valid @RequestParam(value = "additionalMetadata", required = false) String additionalMetadata + @ApiParam(value = "Additional data to pass to server") @Valid @RequestParam(value = "additionalMetadata", required = false) Optional additionalMetadata ) { getRequest().ifPresent(request -> { for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { diff --git a/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/PetApi.java b/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/PetApi.java index 8585447dd46..09899e99387 100644 --- a/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/PetApi.java +++ b/samples/server/petstore/springboot-useoptional/src/main/java/org/openapitools/api/PetApi.java @@ -346,8 +346,8 @@ public interface PetApi { default ResponseEntity updatePetWithForm( @ApiParam(value = "ID of pet that needs to be updated", required = true) @PathVariable("petId") Long petId, - @ApiParam(value = "Updated name of the pet") @Valid @RequestParam(value = "name", required = false) String name, - @ApiParam(value = "Updated status of the pet") @Valid @RequestParam(value = "status", required = false) String status + @ApiParam(value = "Updated name of the pet") @Valid @RequestParam(value = "name", required = false) Optional name, + @ApiParam(value = "Updated status of the pet") @Valid @RequestParam(value = "status", required = false) Optional status ) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); @@ -388,7 +388,7 @@ public interface PetApi { default ResponseEntity uploadFile( @ApiParam(value = "ID of pet to update", required = true) @PathVariable("petId") Long petId, - @ApiParam(value = "Additional data to pass to server") @Valid @RequestParam(value = "additionalMetadata", required = false) String additionalMetadata, + @ApiParam(value = "Additional data to pass to server") @Valid @RequestParam(value = "additionalMetadata", required = false) Optional additionalMetadata, @ApiParam(value = "file to upload") @RequestPart(value = "file", required = false) MultipartFile file ) { getRequest().ifPresent(request -> {