diff --git a/multiapi-engine/pom.xml b/multiapi-engine/pom.xml index 1f447e23..fb183eb1 100644 --- a/multiapi-engine/pom.xml +++ b/multiapi-engine/pom.xml @@ -4,7 +4,7 @@ com.sngular multiapi-engine - 6.6.1 + 6.6.2 jar diff --git a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/OpenApiUtil.java b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/OpenApiUtil.java index 5dbc47fe..6807f25f 100644 --- a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/OpenApiUtil.java +++ b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/OpenApiUtil.java @@ -207,6 +207,11 @@ private static void processParameters(final HashMap basicJsonN private static void processRequestBody(final HashMap basicJsonNodeMap, final JsonNode operation, SpecFile specFile) { if (ApiTool.hasNode(operation, "requestBody") && !operation.at("/requestBody/content").isMissingNode()) { final var content = operation.at("/requestBody/content"); + if (content.has("multipart/form-data")) { + // multipart parts are exposed individually as @RequestPart parameters, + // so no wrapper model is generated for them. + return; + } final var schema = content.findValue("schema"); if (!ApiTool.hasRef(schema)) { basicJsonNodeMap.put(StringCaseUtils.titleToSnakeCase(MapperUtil.getPojoName("InlineObject" + StringUtils.capitalize(getOperationId(operation)), specFile)), schema); diff --git a/multiapi-engine/src/main/resources/templates/openapi/template.ftlh b/multiapi-engine/src/main/resources/templates/openapi/template.ftlh index f98549b4..cca53a5d 100644 --- a/multiapi-engine/src/main/resources/templates/openapi/template.ftlh +++ b/multiapi-engine/src/main/resources/templates/openapi/template.ftlh @@ -3,9 +3,28 @@ package <#if packageApi??>${packageApi}<#elseif package??> ${package}; <#assign imports=[]> <#assign model_imports=[]> +<#assign needsMultipartFile=false> <#list pathObjects as path> <#list path.operationObjects as operation> <#list operation.requestObjects as request> + <#if request.isFormData> + <#list request.contentObjects as content> + <#if content.schemaObject??> + <#list content.schemaObject.fieldObjectList as field> + <#if "${field.dataType}"?contains("MultipartFile")> + <#assign needsMultipartFile=true> + + + + + + + + +<#list pathObjects as path> + <#list path.operationObjects as operation> + <#list operation.requestObjects as request> + <#if !request.isFormData> <#list request.contentObjects as content> <#if content.importName?? && (!imports?seq_contains(content.importName)) && (!model_imports?seq_contains(content.importName))> <#if (!checkBasicTypes?seq_contains(content.importName))> @@ -17,6 +36,7 @@ package <#if packageApi??>${packageApi}<#elseif package??> ${package}; + <#list operation.responseObjects as response> <#list response.contentObjects as content> @@ -62,6 +82,9 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.NativeWebRequest; +<#if needsMultipartFile> +import org.springframework.web.multipart.MultipartFile; + <#list model_imports as import> import <#if packageModel??>${packageModel}<#elseif package??>${package}.${import}; @@ -80,7 +103,7 @@ public interface ${className?cap_first}Api { *<#list path.parameterObjects as parameter> @param ${parameter.name} <#if parameter.description?has_content>${parameter.description} ${parameter.required?c} <#if operation.requestObjects?has_content> - *<#list operation.requestObjects as request><#list request.contentObjects as content> @param ${content.dataType?api.getVariableNameString()}<#if content?has_next>, ${request.description! ""}<#if request.required == true> (required) + *<#list operation.requestObjects as request><#if request.isFormData><#list request.contentObjects[0].schemaObject.fieldObjectList as part> @param ${part.baseName}<#else><#list request.contentObjects as content> @param ${content.dataType?api.getVariableNameString()}<#if content?has_next>, ${request.description! ""}<#if request.required == true> (required) * @return<#list operation.responseObjects as response><#if response.responseName != "default"> ${response.description}; (status code ${response.responseName}) */ @@ -97,15 +120,16 @@ public interface ${className?cap_first}Api { @RequestMapping( method = RequestMethod.${operation.operationType}, value = "${path.pathName}", - produces = {"application/json"} + produces = {"application/json"}<#if operation.requestObjects?has_content && operation.requestObjects[0].isFormData>, + consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} ) default ResponseEntity<@compress single_line=true><#if operation.responseObjects[0].contentObjects[0]??> <${operation.responseObjects[0].contentObjects[0].dataType}<#else>> ${operation.operationId}(<@compress single_line=true> <#if operation.parameterObjects?has_content><#list operation.parameterObjects as parameter> @Parameter(name = "${parameter.name}", <#if parameter.description?has_content>description = "${parameter.description}", required = ${parameter.required?c}, schema = @Schema(description = "")) <#if parameter.in == "path"> @PathVariable("${parameter.name}") <#elseif parameter.in == "query"> @RequestParam(required = ${parameter.required?c}) ${parameter.dataType} ${parameter.name} <#if parameter?has_next || operation.requestObjects?has_content>, <#if path.parameterObjects?has_content><#list path.parameterObjects as parameter> @Parameter(name = "${parameter.name}", <#if parameter.description?has_content>description = "${parameter.description}", required = ${parameter.required?c}, schema = @Schema(description = "")) <#if parameter.in == "path"> @PathVariable("${parameter.name}") <#elseif parameter.in == "query"> @RequestParam(required = ${parameter.required?c}) ${parameter.dataType} ${parameter.name} <#if parameter?has_next || operation.requestObjects?has_content>, - <#if operation.requestObjects?has_content><#list operation.requestObjects as request> @Parameter(name = "${request.contentObjects[0].dataType?api.getVariableNameString()}", description = "${request.description! ""}", required = ${request.required?c}, schema = @Schema(description = "${request.contentObjects[0].description! ""}")) @Valid <#if request.isFormData == false>@RequestBody - ${request.contentObjects[0].dataType} ${request.contentObjects[0].dataType?api.getVariableNameString()} <#if request?has_next>, ) { + <#if operation.requestObjects?has_content><#list operation.requestObjects as request><#if request.isFormData><#list request.contentObjects[0].schemaObject.fieldObjectList as part> @Parameter(name = "${part.baseName}", required = ${part.required?c}, schema = @Schema(description = "")) @RequestPart(value = "${part.baseName}", required = ${part.required?c}) ${part.dataType} ${part.baseName} <#if part?has_next || request?has_next>, <#else> @Parameter(name = "${request.contentObjects[0].dataType?api.getVariableNameString()}", description = "${request.description! ""}", required = ${request.required?c}, schema = @Schema(description = "${request.contentObjects[0].description! ""}")) @Valid @RequestBody + ${request.contentObjects[0].dataType} ${request.contentObjects[0].dataType?api.getVariableNameString()} <#if request?has_next>, ) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } diff --git a/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/api-test.yml b/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/api-test.yml index 7ab7b27f..c2181132 100644 --- a/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/api-test.yml +++ b/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/api-test.yml @@ -22,6 +22,11 @@ paths: someFile: type: string format: binary + someFiles: + type: array + items: + type: string + format: binary responses: '200': diff --git a/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/assets/TestApi.java b/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/assets/TestApi.java index f55eba8d..8af926c8 100644 --- a/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/assets/TestApi.java +++ b/multiapi-engine/src/test/resources/openapigenerator/testFormDataMultipartGeneration/assets/TestApi.java @@ -15,14 +15,14 @@ import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.multipart.MultipartFile; -import com.sngular.multifileplugin.testformdatamultipartgeneration.InlineObjectTestMultipart; public interface TestApi { /** * GET /test - * @param inlineObjectTestMultipart (required) + * @param someFile @param someFiles @param someString (required) * @return OK; (status code 200) */ @@ -36,10 +36,11 @@ public interface TestApi { @RequestMapping( method = RequestMethod.GET, value = "/test", - produces = {"application/json"} + produces = {"application/json"}, + consumes = {MediaType.MULTIPART_FORM_DATA_VALUE} ) - default ResponseEntity testMultipart(@Parameter(name = "inlineObjectTestMultipart", description = "", required = true, schema = @Schema(description = "")) @Valid InlineObjectTestMultipart inlineObjectTestMultipart) { + default ResponseEntity testMultipart(@Parameter(name = "someFile", required = false, schema = @Schema(description = "")) @RequestPart(value = "someFile", required = false) MultipartFile someFile , @Parameter(name = "someFiles", required = false, schema = @Schema(description = "")) @RequestPart(value = "someFiles", required = false) List someFiles , @Parameter(name = "someString", required = false, schema = @Schema(description = "")) @RequestPart(value = "someString", required = false) String someString) { return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); } diff --git a/scs-multiapi-gradle-plugin/build.gradle b/scs-multiapi-gradle-plugin/build.gradle index 3d8ecccf..a28a2352 100644 --- a/scs-multiapi-gradle-plugin/build.gradle +++ b/scs-multiapi-gradle-plugin/build.gradle @@ -21,7 +21,7 @@ repositories { } group = 'com.sngular' -version = '6.6.1' +version = '6.6.2' def SCSMultiApiPluginGroupId = group def SCSMultiApiPluginVersion = version @@ -31,7 +31,7 @@ dependencies { shadow localGroovy() shadow gradleApi() - implementation 'com.sngular:multiapi-engine:6.6.1' + implementation 'com.sngular:multiapi-engine:6.6.2' testImplementation 'org.assertj:assertj-core:3.24.2' testImplementation 'com.puppycrawl.tools:checkstyle:10.12.3' testImplementation 'org.junit.platform:junit-platform-launcher:1.9.2' @@ -100,7 +100,7 @@ testing { integrationTest(JvmTestSuite) { dependencies { - implementation 'com.sngular:scs-multiapi-gradle-plugin:6.6.1' + implementation 'com.sngular:scs-multiapi-gradle-plugin:6.6.2' implementation 'org.assertj:assertj-core:3.24.2' } diff --git a/scs-multiapi-maven-plugin/pom.xml b/scs-multiapi-maven-plugin/pom.xml index 13c08fe2..c26471b9 100644 --- a/scs-multiapi-maven-plugin/pom.xml +++ b/scs-multiapi-maven-plugin/pom.xml @@ -4,7 +4,7 @@ com.sngular scs-multiapi-maven-plugin - 6.6.1 + 6.6.2 maven-plugin AsyncApi - OpenApi Code Generator Maven Plugin @@ -271,7 +271,7 @@ com.sngular multiapi-engine - 6.6.1 + 6.6.2 org.apache.maven