[java] [spring] Fix multipart optional params error compilation using delegates (#20793)

* fix spring multipart optional parameters

* use optional only in not required params

* remove debug line

---------

Co-authored-by: Jaime Sanchez <jaime.sanchezf@externos.santalucia.es>
Co-authored-by: William Cheng <wing328hk@gmail.com>
This commit is contained in:
Jaime Sánchez 2025-04-30 08:28:38 +02:00 committed by GitHub
parent 76540e591f
commit 3c664d1a59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 125 additions and 19 deletions

View File

@ -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<Part>{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}} {{paramName}}{{/isFile}}{{/isFormParam}} {{#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<Part>{{/reactive}}{{^reactive}}MultipartFile{{/reactive}}{{#isArray}}>{{/isArray}} {{paramName}}{{/isFile}}{{/isFormParam}}

View File

@ -5384,6 +5384,37 @@ public class SpringCodegenTest {
JavaFileAssert.assertThat(files.get("Type.java")).fileContains("Type implements java.io.Serializable {"); 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<String> additionalMetadata",
"@Valid @RequestParam(value = \"length\", required = true) Integer length");
}
@Test @Test
public void shouldEnableBuiltInValidationOptionWhenSetToTrue() throws IOException { public void shouldEnableBuiltInValidationOptionWhenSetToTrue() throws IOException {
final SpringCodegen codegen = new SpringCodegen(); final SpringCodegen codegen = new SpringCodegen();

View File

@ -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

View File

@ -166,8 +166,8 @@ public interface PetApi {
ResponseEntity<Void> updatePetWithForm( ResponseEntity<Void> updatePetWithForm(
@PathVariable("petId") Long petId, @PathVariable("petId") Long petId,
@Valid @RequestParam(value = "name", required = false) String name, @Valid @RequestParam(value = "name", required = false) Optional<String> name,
@Valid @RequestParam(value = "status", required = false) String status @Valid @RequestParam(value = "status", required = false) Optional<String> status
); );
@ -189,7 +189,7 @@ public interface PetApi {
ResponseEntity<ModelApiResponse> uploadFile( ResponseEntity<ModelApiResponse> uploadFile(
@PathVariable("petId") Long petId, @PathVariable("petId") Long petId,
@Valid @RequestParam(value = "additionalMetadata", required = false) String additionalMetadata, @Valid @RequestParam(value = "additionalMetadata", required = false) Optional<String> additionalMetadata,
@RequestPart(value = "file", required = false) MultipartFile file @RequestPart(value = "file", required = false) MultipartFile file
); );

View File

@ -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 = "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 = "pattern_without_delimiter", required = true) String patternWithoutDelimiter,
@ApiParam(value = "None", required = true) @Valid @RequestParam(value = "byte", required = true) byte[] _byte, @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 = "integer", required = false) Optional<Integer> integer,
@ApiParam(value = "None") @Valid @RequestParam(value = "int32", required = false) Integer int32, @ApiParam(value = "None") @Valid @RequestParam(value = "int32", required = false) Optional<Integer> int32,
@ApiParam(value = "None") @Valid @RequestParam(value = "int64", required = false) Long int64, @ApiParam(value = "None") @Valid @RequestParam(value = "int64", required = false) Optional<Long> int64,
@ApiParam(value = "None") @Valid @RequestParam(value = "float", required = false) Float _float, @ApiParam(value = "None") @Valid @RequestParam(value = "float", required = false) Optional<Float> _float,
@ApiParam(value = "None") @Valid @RequestParam(value = "string", required = false) String string, @ApiParam(value = "None") @Valid @RequestParam(value = "string", required = false) Optional<String> string,
@ApiParam(value = "None") @RequestPart(value = "binary", required = false) MultipartFile binary, @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 = "date", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) Optional<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 = "dateTime", required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) Optional<OffsetDateTime> dateTime,
@ApiParam(value = "None") @Valid @RequestParam(value = "password", required = false) String password, @ApiParam(value = "None") @Valid @RequestParam(value = "password", required = false) Optional<String> password,
@ApiParam(value = "None") @Valid @RequestParam(value = "callback", required = false) String paramCallback @ApiParam(value = "None") @Valid @RequestParam(value = "callback", required = false) Optional<String> paramCallback
) { ) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); 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<String> enumQueryString, @ApiParam(value = "Query parameter enum test (string)", allowableValues = "_abc, -efg, (xyz)", defaultValue = "-efg") @Valid @RequestParam(value = "enum_query_string", required = false, defaultValue = "-efg") Optional<String> enumQueryString,
@ApiParam(value = "Query parameter enum test (double)", allowableValues = "1, -2") @Valid @RequestParam(value = "enum_query_integer", required = false) Optional<Integer> enumQueryInteger, @ApiParam(value = "Query parameter enum test (double)", allowableValues = "1, -2") @Valid @RequestParam(value = "enum_query_integer", required = false) Optional<Integer> enumQueryInteger,
@ApiParam(value = "Query parameter enum test (double)", allowableValues = "1.1, -1.2") @Valid @RequestParam(value = "enum_query_double", required = false) Optional<Double> enumQueryDouble, @ApiParam(value = "Query parameter enum test (double)", allowableValues = "1.1, -1.2") @Valid @RequestParam(value = "enum_query_double", required = false) Optional<Double> enumQueryDouble,
@ApiParam(value = "Form parameter enum test (string array)", allowableValues = ">, $", defaultValue = "$") @Valid @RequestPart(value = "enum_form_string_array", required = false) List<String> enumFormStringArray, @ApiParam(value = "Form parameter enum test (string array)", allowableValues = ">, $", defaultValue = "$") @Valid @RequestPart(value = "enum_form_string_array", required = false) Optional<List<String>> 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)", allowableValues = "_abc, -efg, (xyz)", defaultValue = "-efg") @Valid @RequestParam(value = "enum_form_string", required = false) Optional<String> enumFormString
) { ) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
@ -698,7 +698,7 @@ public interface FakeApi {
default ResponseEntity<ModelApiResponse> uploadFileWithRequiredFile( default ResponseEntity<ModelApiResponse> uploadFileWithRequiredFile(
@ApiParam(value = "ID of pet to update", required = true) @PathVariable("petId") Long petId, @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 = "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<String> additionalMetadata
) { ) {
getRequest().ifPresent(request -> { getRequest().ifPresent(request -> {
for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) { for (MediaType mediaType: MediaType.parseMediaTypes(request.getHeader("Accept"))) {

View File

@ -346,8 +346,8 @@ public interface PetApi {
default ResponseEntity<Void> updatePetWithForm( default ResponseEntity<Void> updatePetWithForm(
@ApiParam(value = "ID of pet that needs to be updated", required = true) @PathVariable("petId") Long petId, @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 name of the pet") @Valid @RequestParam(value = "name", required = false) Optional<String> name,
@ApiParam(value = "Updated status of the pet") @Valid @RequestParam(value = "status", required = false) String status @ApiParam(value = "Updated status of the pet") @Valid @RequestParam(value = "status", required = false) Optional<String> status
) { ) {
return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED);
@ -388,7 +388,7 @@ public interface PetApi {
default ResponseEntity<ModelApiResponse> uploadFile( default ResponseEntity<ModelApiResponse> uploadFile(
@ApiParam(value = "ID of pet to update", required = true) @PathVariable("petId") Long petId, @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<String> additionalMetadata,
@ApiParam(value = "file to upload") @RequestPart(value = "file", required = false) MultipartFile file @ApiParam(value = "file to upload") @RequestPart(value = "file", required = false) MultipartFile file
) { ) {
getRequest().ifPresent(request -> { getRequest().ifPresent(request -> {