diff --git a/multiapi-engine/pom.xml b/multiapi-engine/pom.xml index 73b8563e..f9aa78e0 100644 --- a/multiapi-engine/pom.xml +++ b/multiapi-engine/pom.xml @@ -4,7 +4,7 @@ com.sngular multiapi-engine - 6.6.3 + 6.6.4 jar diff --git a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/SchemaFieldObjectType.java b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/SchemaFieldObjectType.java index 72bed1be..291bb3fb 100644 --- a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/SchemaFieldObjectType.java +++ b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/SchemaFieldObjectType.java @@ -38,7 +38,8 @@ public class SchemaFieldObjectType { new SimpleImmutableEntry<>(TypeConstants.ZONEDDATETIME, ZONED_DATE_TIME), new SimpleImmutableEntry<>(TypeConstants.OFFSETDATE, OFFSET_DATE_TIME), new SimpleImmutableEntry<>(TypeConstants.OFFSETDATETIME, OFFSET_DATE_TIME), - new SimpleImmutableEntry<>(TypeConstants.MULTIPART_FILE, "MultipartFile") + new SimpleImmutableEntry<>(TypeConstants.MULTIPART_FILE, "MultipartFile"), + new SimpleImmutableEntry<>(TypeConstants.RESOURCE, "Resource") ); private static final Map IMPORT_TYPE_MAPPINGS = Map.ofEntries( @@ -53,7 +54,8 @@ public class SchemaFieldObjectType { new SimpleImmutableEntry<>(TypeConstants.ZONEDDATETIME, "java.time." + ZONED_DATE_TIME), new SimpleImmutableEntry<>(TypeConstants.OFFSETDATE, "java.time." + OFFSET_DATE_TIME), new SimpleImmutableEntry<>(TypeConstants.OFFSETDATETIME, "java.time." + OFFSET_DATE_TIME), - new SimpleImmutableEntry<>(TypeConstants.MULTIPART_FILE, "MultipartFile") + new SimpleImmutableEntry<>(TypeConstants.MULTIPART_FILE, "MultipartFile"), + new SimpleImmutableEntry<>(TypeConstants.RESOURCE, "org.springframework.core.io.Resource") ); private static final Map IMPL_TYPE_MAPPINGS = Map.ofEntries( @@ -73,7 +75,8 @@ public class SchemaFieldObjectType { new SimpleImmutableEntry<>(TypeConstants.ZONEDDATETIME, ZONED_DATE_TIME), new SimpleImmutableEntry<>(TypeConstants.OFFSETDATE, OFFSET_DATE_TIME), new SimpleImmutableEntry<>(TypeConstants.OFFSETDATETIME, OFFSET_DATE_TIME), - new SimpleImmutableEntry<>(TypeConstants.MULTIPART_FILE, "MultipartFile") + new SimpleImmutableEntry<>(TypeConstants.MULTIPART_FILE, "MultipartFile"), + new SimpleImmutableEntry<>(TypeConstants.RESOURCE, "Resource") ); private final String baseType; diff --git a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/TypeConstants.java b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/TypeConstants.java index 7b98ed51..c3db8182 100644 --- a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/TypeConstants.java +++ b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/common/model/TypeConstants.java @@ -56,6 +56,8 @@ public final class TypeConstants { public static final String MULTIPART_FILE = "multipartfile"; + public static final String RESOURCE = "resource"; + public static final Set BASIC_OBJECT_TYPE = Set.of(NUMBER, STRING, BOOLEAN, INTEGER, ARRAY); public static final Set NO_IMPORT_TYPE = Set.of(STRING, INTEGER, OBJECT); @@ -94,7 +96,8 @@ public final class TypeConstants { ZONEDDATETIME, OFFSETDATE, OFFSETDATETIME, - MULTIPART_FILE + MULTIPART_FILE, + RESOURCE ); private TypeConstants() { diff --git a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/MapperPathUtil.java b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/MapperPathUtil.java index b8540ca6..c42de51e 100644 --- a/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/MapperPathUtil.java +++ b/multiapi-engine/src/main/java/com/sngular/api/generator/plugin/openapi/utils/MapperPathUtil.java @@ -473,6 +473,11 @@ private static JsonNode getRefSchema(JsonNode schema, SpecFile specFile, GlobalO private static SchemaFieldObjectType getObjectOrType( final JsonNode schema, final String pojoName, final SpecFile specFile, final GlobalObject globalObject, final Path baseDir) { + if (ApiTool.isBinary(schema)) { + // A raw binary body (e.g. application/octet-stream, type: string + format: binary, + // not wrapped in a multipart object) maps to a streaming Resource, not String. + return new SchemaFieldObjectType(TypeConstants.RESOURCE); + } return switch (ApiTool.getType(schema)) { case TypeConstants.OBJECT -> SchemaFieldObjectType.fromTypeList(TypeConstants.OBJECT, pojoName); case TypeConstants.INTEGER -> new SchemaFieldObjectType(getIntegerFormat(schema)); diff --git a/multiapi-engine/src/main/resources/templates/openapi/template.ftlh b/multiapi-engine/src/main/resources/templates/openapi/template.ftlh index cca53a5d..86ce833a 100644 --- a/multiapi-engine/src/main/resources/templates/openapi/template.ftlh +++ b/multiapi-engine/src/main/resources/templates/openapi/template.ftlh @@ -4,6 +4,7 @@ package <#if packageApi??>${packageApi}<#elseif package??> ${package}; <#assign imports=[]> <#assign model_imports=[]> <#assign needsMultipartFile=false> +<#assign needsResource=false> <#list pathObjects as path> <#list path.operationObjects as operation> <#list operation.requestObjects as request> @@ -18,6 +19,18 @@ package <#if packageApi??>${packageApi}<#elseif package??> ${package}; + <#list request.contentObjects as content> + <#if content.dataType.containsType("resource")> + <#assign needsResource=true> + + + + <#list operation.responseObjects as response> + <#list response.contentObjects as content> + <#if content.dataType.containsType("resource")> + <#assign needsResource=true> + + @@ -85,6 +98,9 @@ import org.springframework.web.context.request.NativeWebRequest; <#if needsMultipartFile> import org.springframework.web.multipart.MultipartFile; +<#if needsResource> +import org.springframework.core.io.Resource; + <#list model_imports as import> import <#if packageModel??>${packageModel}<#elseif package??>${package}.${import}; diff --git a/multiapi-engine/src/main/resources/templates/openapi/templateReactive.ftlh b/multiapi-engine/src/main/resources/templates/openapi/templateReactive.ftlh index 7f56c742..ab3b5d94 100644 --- a/multiapi-engine/src/main/resources/templates/openapi/templateReactive.ftlh +++ b/multiapi-engine/src/main/resources/templates/openapi/templateReactive.ftlh @@ -23,6 +23,7 @@ import reactor.core.publisher.Mono; import reactor.core.publisher.Flux; import springfox.documentation.annotations.ApiIgnore; <#assign needsFilePart=false> +<#assign needsResource=false> <#list pathObjects as path> <#list path.operationObjects as operation> <#list operation.requestObjects as request> @@ -37,12 +38,27 @@ import springfox.documentation.annotations.ApiIgnore; + <#list request.contentObjects as content> + <#if content.dataType.containsType("resource")> + <#assign needsResource=true> + + + + <#list operation.responseObjects as response> + <#list response.contentObjects as content> + <#if content.dataType.containsType("resource")> + <#assign needsResource=true> + + <#if needsFilePart> import org.springframework.http.codec.multipart.FilePart; +<#if needsResource> +import org.springframework.core.io.Resource; + <#assign imports=[]> <#list pathObjects as path> diff --git a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java index 3b66654a..3af6ff97 100644 --- a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java +++ b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorFixtures.java @@ -86,6 +86,16 @@ public final class OpenApiGeneratorFixtures { .apiPackage("com.sngular.multifileplugin.testreactiveformdatamultipart") .useLombokModelAnnotation(false).isReactive(true).build()); + static final List TEST_BINARY_BODY_RESOURCE = List + .of(SpecFile.builder().filePath("openapigenerator/testBinaryBodyResource/api-test.yml") + .apiPackage("com.sngular.multifileplugin.testbinarybodyresource") + .useLombokModelAnnotation(false).build()); + + static final List TEST_REACTIVE_BINARY_BODY_RESOURCE = List + .of(SpecFile.builder().filePath("openapigenerator/testReactiveBinaryBodyResource/api-test.yml") + .apiPackage("com.sngular.multifileplugin.testreactivebinarybodyresource") + .useLombokModelAnnotation(false).isReactive(true).build()); + static final List TEST_API_TAGS_GENERATION = List .of(SpecFile.builder().filePath("openapigenerator/testApiTagsGeneration/api-tags-test.yml") .apiPackage("com.sngular.multifileplugin.tagsgeneration") @@ -607,6 +617,38 @@ static Function validateApiReactiveGeneration(int springBootVersi } + static Function validateBinaryBodyResource() { + + final String DEFAULT_TARGET_API = "generated/com/sngular/multifileplugin/testbinarybodyresource"; + + final String DEFAULT_MODEL_API = "generated/com/sngular/multifileplugin/testbinarybodyresource/model"; + + final String COMMON_PATH = "openapigenerator/testBinaryBodyResource/"; + + final String ASSETS_PATH = COMMON_PATH + "assets/"; + + final List expectedTestApiFile = List.of(ASSETS_PATH + "UploadApi.java"); + + return path -> commonTest(path, expectedTestApiFile, Collections.emptyList(), DEFAULT_TARGET_API, + DEFAULT_MODEL_API, Collections.emptyList(), null); + } + + static Function validateReactiveBinaryBodyResource() { + + final String DEFAULT_TARGET_API = "generated/com/sngular/multifileplugin/testreactivebinarybodyresource"; + + final String DEFAULT_MODEL_API = "generated/com/sngular/multifileplugin/testreactivebinarybodyresource/model"; + + final String COMMON_PATH = "openapigenerator/testReactiveBinaryBodyResource/"; + + final String ASSETS_PATH = COMMON_PATH + "assets/"; + + final List expectedTestApiFile = List.of(ASSETS_PATH + "UploadApi.java"); + + return path -> commonTest(path, expectedTestApiFile, Collections.emptyList(), DEFAULT_TARGET_API, + DEFAULT_MODEL_API, Collections.emptyList(), null); + } + static Function validateReactiveFormDataMultipart() { final String DEFAULT_TARGET_API = "generated/com/sngular/multifileplugin/testreactiveformdatamultipart"; diff --git a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java index 4bfa1dac..e5a9b604 100644 --- a/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java +++ b/multiapi-engine/src/test/java/com/sngular/api/generator/plugin/openapi/OpenApiGeneratorTest.java @@ -65,6 +65,10 @@ static Stream fileSpecToProcess() { OpenApiGeneratorFixtures.validateApiReactiveGeneration(SPRING_BOOT_VERSION)), Arguments.of("testReactiveFormDataMultipart", OpenApiGeneratorFixtures.TEST_REACTIVE_FORM_DATA_MULTIPART, OpenApiGeneratorFixtures.validateReactiveFormDataMultipart()), + Arguments.of("testBinaryBodyResource", OpenApiGeneratorFixtures.TEST_BINARY_BODY_RESOURCE, + OpenApiGeneratorFixtures.validateBinaryBodyResource()), + Arguments.of("testReactiveBinaryBodyResource", OpenApiGeneratorFixtures.TEST_REACTIVE_BINARY_BODY_RESOURCE, + OpenApiGeneratorFixtures.validateReactiveBinaryBodyResource()), Arguments.of("testApiTagsGeneration", OpenApiGeneratorFixtures.TEST_API_TAGS_GENERATION, OpenApiGeneratorFixtures.validateTagsGeneration()), Arguments.of("testMultipleRefGeneration", OpenApiGeneratorFixtures.TEST_MULTIPLE_REF_GENERATION, diff --git a/multiapi-engine/src/test/resources/openapigenerator/testBinaryBodyResource/api-test.yml b/multiapi-engine/src/test/resources/openapigenerator/testBinaryBodyResource/api-test.yml new file mode 100644 index 00000000..257a0157 --- /dev/null +++ b/multiapi-engine/src/test/resources/openapigenerator/testBinaryBodyResource/api-test.yml @@ -0,0 +1,23 @@ +openapi: 3.0.2 +info: + title: Testing raw binary request body + version: 1.0.0 +servers: +- url: http://localhost/v1 +paths: + /upload: + post: + tags: + - test + operationId: uploadBinary + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: + OK diff --git a/multiapi-engine/src/test/resources/openapigenerator/testBinaryBodyResource/assets/UploadApi.java b/multiapi-engine/src/test/resources/openapigenerator/testBinaryBodyResource/assets/UploadApi.java new file mode 100644 index 00000000..50be0b6a --- /dev/null +++ b/multiapi-engine/src/test/resources/openapigenerator/testBinaryBodyResource/assets/UploadApi.java @@ -0,0 +1,46 @@ +package com.sngular.multifileplugin.testbinarybodyresource; + +import java.util.Optional; +import java.util.List; +import java.util.Map; +import javax.validation.Valid; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.core.io.Resource; + + +public interface UploadApi { + + /** + * POST /upload + * @param resource (required) + * @return OK; (status code 200) + */ + + @Operation( + operationId = "uploadBinary", + tags = {"test"}, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + @RequestMapping( + method = RequestMethod.POST, + value = "/upload", + produces = {"application/json"} + ) + + default ResponseEntity uploadBinary(@Parameter(name = "resource", description = "", required = true, schema = @Schema(description = "")) @Valid @RequestBody Resource resource) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + +} diff --git a/multiapi-engine/src/test/resources/openapigenerator/testReactiveBinaryBodyResource/api-test.yml b/multiapi-engine/src/test/resources/openapigenerator/testReactiveBinaryBodyResource/api-test.yml new file mode 100644 index 00000000..7441748e --- /dev/null +++ b/multiapi-engine/src/test/resources/openapigenerator/testReactiveBinaryBodyResource/api-test.yml @@ -0,0 +1,23 @@ +openapi: 3.0.2 +info: + title: Testing reactive raw binary request body + version: 1.0.0 +servers: +- url: http://localhost/v1 +paths: + /upload: + post: + tags: + - test + operationId: uploadBinary + requestBody: + required: true + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: + OK diff --git a/multiapi-engine/src/test/resources/openapigenerator/testReactiveBinaryBodyResource/assets/UploadApi.java b/multiapi-engine/src/test/resources/openapigenerator/testReactiveBinaryBodyResource/assets/UploadApi.java new file mode 100644 index 00000000..2235f657 --- /dev/null +++ b/multiapi-engine/src/test/resources/openapigenerator/testReactiveBinaryBodyResource/assets/UploadApi.java @@ -0,0 +1,50 @@ +package com.sngular.multifileplugin.testreactivebinarybodyresource; + +import java.util.List; +import java.util.Map; +import java.nio.charset.StandardCharsets; +import javax.validation.Valid; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import org.springframework.http.MediaType; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.core.io.buffer.DefaultDataBufferFactory; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; +import reactor.core.publisher.Flux; +import springfox.documentation.annotations.ApiIgnore; +import org.springframework.core.io.Resource; + + +public interface UploadApi { + + /** + * POST /upload + * @param resource (required) + * @return OK; (status code 200) + * @throws WebClientResponseException if an error occurs while attempting to invoke the API + */ + @Operation( + operationId = "uploadBinary", + tags = {"test"}, + responses = { + @ApiResponse(responseCode = "200", description = "OK") + } + ) + @RequestMapping( + method = RequestMethod.POST, + value = "/upload", + produces = {"application/json"} + ) + default ResponseEntity uploadBinary(@Parameter(name = "resource", description = "", required = true, schema = @Schema(description = "")) @Valid @RequestBody Mono resource, @ApiIgnore final ServerWebExchange exchange) { + return new ResponseEntity<>(HttpStatus.NOT_IMPLEMENTED); + } + +} \ No newline at end of file diff --git a/scs-multiapi-gradle-plugin/build.gradle b/scs-multiapi-gradle-plugin/build.gradle index 68e961a3..c5225e0e 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.3' +version = '6.6.4' def SCSMultiApiPluginGroupId = group def SCSMultiApiPluginVersion = version @@ -31,7 +31,7 @@ dependencies { shadow localGroovy() shadow gradleApi() - implementation 'com.sngular:multiapi-engine:6.6.3' + implementation 'com.sngular:multiapi-engine:6.6.4' 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.3' + implementation 'com.sngular:scs-multiapi-gradle-plugin:6.6.4' 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 27bc47e7..230e9c80 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.3 + 6.6.4 maven-plugin AsyncApi - OpenApi Code Generator Maven Plugin @@ -271,7 +271,7 @@ com.sngular multiapi-engine - 6.6.3 + 6.6.4 org.apache.maven