diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java index 3c0ff1155ba8..87b6c8841fde 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenOperation.java @@ -24,7 +24,7 @@ public class CodegenOperation { public final List responseHeaders = new ArrayList(); - public boolean hasAuthMethods, hasConsumes, hasProduces, hasParams, hasOptionalParams, hasRequiredParams, + public boolean hasAuthMethods, hasConsumes, hasProduces, hasOptionalParams, hasRequiredParams, returnTypeIsPrimitive, returnSimpleType, subresourceOperation, isMap, isArray, isMultipart, isVoid = false, hasVersionHeaders = false, hasVersionQueryParams = false, @@ -81,6 +81,15 @@ private static boolean nonEmpty(Map params) { return params != null && !params.isEmpty(); } + /** + * Check if there's at least one parameter + * + * @return true if parameter exists, false otherwise + */ + public boolean getHasParams() { + return nonEmpty(allParams); + } + /** * Check if there's at least one body parameter * @@ -362,7 +371,6 @@ public String toString() { sb.append(", hasAuthMethods=").append(hasAuthMethods); sb.append(", hasConsumes=").append(hasConsumes); sb.append(", hasProduces=").append(hasProduces); - sb.append(", hasParams=").append(hasParams); sb.append(", hasOptionalParams=").append(hasOptionalParams); sb.append(", hasRequiredParams=").append(hasRequiredParams); sb.append(", returnTypeIsPrimitive=").append(returnTypeIsPrimitive); @@ -445,7 +453,6 @@ public boolean equals(Object o) { return hasAuthMethods == that.hasAuthMethods && hasConsumes == that.hasConsumes && hasProduces == that.hasProduces && - hasParams == that.hasParams && hasOptionalParams == that.hasOptionalParams && hasRequiredParams == that.hasRequiredParams && returnTypeIsPrimitive == that.returnTypeIsPrimitive && @@ -522,7 +529,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(responseHeaders, hasAuthMethods, hasConsumes, hasProduces, hasParams, hasOptionalParams, + return Objects.hash(responseHeaders, hasAuthMethods, hasConsumes, hasProduces, hasOptionalParams, hasRequiredParams, returnTypeIsPrimitive, returnSimpleType, subresourceOperation, isMap, isArray, isMultipart, isVoid, isResponseBinary, isResponseFile, isResponseOptional, hasReference, hasDefaultResponse, hasOnlyDefaultResponse, isRestfulIndex, isRestfulShow, isRestfulCreate, isRestfulUpdate, isRestfulDestroy, diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java index 97a047e82fff..5d3061134cad 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultCodegen.java @@ -4834,9 +4834,6 @@ public CodegenOperation fromOperation(String path, // legacy support op.nickname = op.operationId; - if (op.allParams.size() > 0) { - op.hasParams = true; - } op.hasRequiredParams = op.requiredParams.size() > 0; // check if the operation has only a single parameter @@ -8678,6 +8675,5 @@ protected void handleConstantParams(CodegenOperation operation) { operation.allParams.add(p); } } - operation.hasParams = !operation.allParams.isEmpty(); } } diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java index f339e332c93d..8a624b0bbf86 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AbstractJavaCodegen.java @@ -2386,7 +2386,6 @@ protected void handleImplicitHeaders(CodegenOperation operation) { operation.allParams.add(p); } } - operation.hasParams = !operation.allParams.isEmpty(); } private boolean shouldBeImplicitHeader(CodegenParameter parameter) { diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java index 05b626fdc1a8..fa8a54d0a9f8 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ElixirClientCodegen.java @@ -712,7 +712,6 @@ public ExtendedCodegenOperation(CodegenOperation o) { this.hasAuthMethods = o.hasAuthMethods; this.hasConsumes = o.hasConsumes; this.hasProduces = o.hasProduces; - this.hasParams = o.hasParams; this.hasOptionalParams = o.hasOptionalParams; this.returnTypeIsPrimitive = o.returnTypeIsPrimitive; this.returnSimpleType = o.returnSimpleType; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java index c294665e310c..4149454422b8 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangClientCodegen.java @@ -417,7 +417,6 @@ public ExtendedCodegenOperation(CodegenOperation o) { this.hasAuthMethods = o.hasAuthMethods; this.hasConsumes = o.hasConsumes; this.hasProduces = o.hasProduces; - this.hasParams = o.hasParams; this.hasOptionalParams = o.hasOptionalParams; this.returnTypeIsPrimitive = o.returnTypeIsPrimitive; this.returnSimpleType = o.returnSimpleType; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java index 80d9f7667b11..52166375da5c 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ErlangProperCodegen.java @@ -507,7 +507,6 @@ class ExtendedCodegenOperation extends CodegenOperation { this.hasAuthMethods = o.hasAuthMethods; this.hasConsumes = o.hasConsumes; this.hasProduces = o.hasProduces; - this.hasParams = o.hasParams; this.hasOptionalParams = o.hasOptionalParams; this.returnTypeIsPrimitive = o.returnTypeIsPrimitive; this.returnSimpleType = o.returnSimpleType; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java index 0d3668e67035..50d84e6b0cbc 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java @@ -1379,7 +1379,6 @@ public ExtendedCodegenOperation(CodegenOperation o) { this.hasAuthMethods = o.hasAuthMethods; this.hasConsumes = o.hasConsumes; this.hasProduces = o.hasProduces; - this.hasParams = o.hasParams; this.hasOptionalParams = o.hasOptionalParams; this.hasRequiredParams = o.hasRequiredParams; this.returnTypeIsPrimitive = o.returnTypeIsPrimitive; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptRxjsClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptRxjsClientCodegen.java index b06ca5e10ca0..037738bcae40 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptRxjsClientCodegen.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptRxjsClientCodegen.java @@ -348,7 +348,6 @@ public ExtendedCodegenOperation(CodegenOperation o) { this.hasAuthMethods = o.hasAuthMethods; this.hasConsumes = o.hasConsumes; this.hasProduces = o.hasProduces; - this.hasParams = o.hasParams; this.hasOptionalParams = o.hasOptionalParams; this.hasRequiredParams = o.hasRequiredParams; this.returnTypeIsPrimitive = o.returnTypeIsPrimitive; diff --git a/modules/openapi-generator/src/main/resources/JavaSpring/formParams.mustache b/modules/openapi-generator/src/main/resources/JavaSpring/formParams.mustache index 9508d72750d1..fdb51cc6c69c 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 917a912281ee..328a8acdb348 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 void testEnumWithImplements() { 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 000000000000..6a564293285f --- /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 8bdcd1c986ab..282cf379a852 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 @@ ResponseEntity updatePet( 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 @@ ResponseEntity updatePetWithForm( 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 36e53145c704..907e29eb80e8 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 @@ default ResponseEntity testEndpointParameters( @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 @@ default ResponseEntity testEnumParameters( @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 @@ default ResponseEntity testWithResultExample( 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 8585447dd467..09899e99387c 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 @@ default ResponseEntity updatePet( 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 @@ default ResponseEntity updatePetWithForm( 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 -> {