From 74cdb4d36d459601824a89311b6a00378cc21d9f Mon Sep 17 00:00:00 2001 From: Michael Edgar Date: Wed, 11 Dec 2024 09:52:59 -0500 Subject: [PATCH] Handle Kotlin `Flow` as a wrapper type Signed-off-by: Michael Edgar --- .../api/constants/KotlinConstants.java | 3 ++ .../openapi/runtime/util/TypeUtil.java | 1 + testsuite/data/pom.xml | 30 ++++++++++++++++++- .../openapi/testdata/kotlin/KotlinResource.kt | 25 ++++++++++++++++ .../testdata/QuarkusAnnotationScanIT.java | 11 +++++++ ...ponents.schemas.kotlin-flow-unwrapped.json | 21 +++++++++++++ 6 files changed, 90 insertions(+), 1 deletion(-) create mode 100644 testsuite/data/src/main/kotlin/io/smallrye/openapi/testdata/kotlin/KotlinResource.kt create mode 100644 testsuite/data/src/test/resources/io/smallrye/openapi/testdata/components.schemas.kotlin-flow-unwrapped.json diff --git a/core/src/main/java/io/smallrye/openapi/api/constants/KotlinConstants.java b/core/src/main/java/io/smallrye/openapi/api/constants/KotlinConstants.java index c19e051a6..afe9d5461 100644 --- a/core/src/main/java/io/smallrye/openapi/api/constants/KotlinConstants.java +++ b/core/src/main/java/io/smallrye/openapi/api/constants/KotlinConstants.java @@ -21,6 +21,9 @@ public class KotlinConstants { public static final DotName JETBRAINS_NOT_NULL = DotName .createSimple("org.jetbrains.annotations.NotNull"); + public static final DotName FLOW = DotName + .createSimple("kotlinx.coroutines.flow.Flow"); + private KotlinConstants() { } } diff --git a/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java b/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java index 7ed7d583c..94d3b71cb 100644 --- a/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java +++ b/core/src/main/java/io/smallrye/openapi/runtime/util/TypeUtil.java @@ -310,6 +310,7 @@ public class TypeUtil { jdkIndex = indexer.complete(); wrapperTypes.addAll(JaxbConstants.JAXB_ELEMENT); + wrapperTypes.add(KotlinConstants.FLOW); wrapperTypes.add(MutinyConstants.UNI_TYPE.name()); wrapperTypes.add(JDKConstants.COMPLETION_STAGE_NAME); wrapperTypes.add(JDKConstants.COMPLETABLE_FUTURE_NAME); diff --git a/testsuite/data/pom.xml b/testsuite/data/pom.xml index 1e3592dfe..d18e54990 100644 --- a/testsuite/data/pom.xml +++ b/testsuite/data/pom.xml @@ -14,7 +14,7 @@ 4.0.2 - 2.1.0 + 2.0.21 17 ${java.version} ${java.version} @@ -99,6 +99,10 @@ org.jetbrains.kotlin kotlin-stdlib + + org.jetbrains.kotlinx + kotlinx-coroutines-core + io.quarkus @@ -190,7 +194,31 @@ + + test-compile + + test-compile + + + + + all-open + + + + + + + + + + + org.jetbrains.kotlin + kotlin-maven-allopen + ${kotlin.version} + + org.apache.maven.plugins diff --git a/testsuite/data/src/main/kotlin/io/smallrye/openapi/testdata/kotlin/KotlinResource.kt b/testsuite/data/src/main/kotlin/io/smallrye/openapi/testdata/kotlin/KotlinResource.kt new file mode 100644 index 000000000..097c2bdf1 --- /dev/null +++ b/testsuite/data/src/main/kotlin/io/smallrye/openapi/testdata/kotlin/KotlinResource.kt @@ -0,0 +1,25 @@ +package io.smallrye.openapi.testdata.kotlin + +import jakarta.ws.rs.GET +import jakarta.ws.rs.Path +import jakarta.ws.rs.Produces +import jakarta.ws.rs.core.MediaType + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import org.jboss.resteasy.reactive.RestStreamElementType + +@Path("kotlin") +class KotlinResource { + @Path("hello") + @GET + @Produces(MediaType.SERVER_SENT_EVENTS) + @RestStreamElementType(MediaType.APPLICATION_JSON) + fun hello(): Flow { + return flow { + Foobar("Hello") + } + } +} + +data class Foobar(val data: String) \ No newline at end of file diff --git a/testsuite/data/src/test/java/io/smallrye/openapi/testdata/QuarkusAnnotationScanIT.java b/testsuite/data/src/test/java/io/smallrye/openapi/testdata/QuarkusAnnotationScanIT.java index abd4df92d..96c3d03d2 100644 --- a/testsuite/data/src/test/java/io/smallrye/openapi/testdata/QuarkusAnnotationScanIT.java +++ b/testsuite/data/src/test/java/io/smallrye/openapi/testdata/QuarkusAnnotationScanIT.java @@ -108,6 +108,17 @@ void testRecordWithPojoPrefixedRecordComponents() throws Exception { assertJsonEquals("components.schemas.prefixed-record-component-names.json", result); } + @Test + void testKotlinResourceWithUnwrappedFlowSSE() throws Exception { + Index index = Index.of(io.smallrye.openapi.testdata.kotlin.KotlinResource.class); + OpenApiAnnotationScanner scanner = new OpenApiAnnotationScanner(emptyConfig(), index); + + OpenAPI result = scanner.scan(); + + printToConsole(result); + assertJsonEquals("components.schemas.kotlin-flow-unwrapped.json", result); + } + @Test void testSyntheticClassesAndInterfacesIgnoredByDefault() throws Exception { try (InputStream source = getClass().getResourceAsStream("/smallrye-open-api-testsuite-data.idx")) { diff --git a/testsuite/data/src/test/resources/io/smallrye/openapi/testdata/components.schemas.kotlin-flow-unwrapped.json b/testsuite/data/src/test/resources/io/smallrye/openapi/testdata/components.schemas.kotlin-flow-unwrapped.json new file mode 100644 index 000000000..37ce6bbde --- /dev/null +++ b/testsuite/data/src/test/resources/io/smallrye/openapi/testdata/components.schemas.kotlin-flow-unwrapped.json @@ -0,0 +1,21 @@ +{ + "openapi" : "3.1.0", + "paths" : { + "/kotlin/hello" : { + "get" : { + "responses" : { + "200" : { + "description" : "OK", + "content" : { + "text/event-stream" : { + "schema" : { + "type" : "string" + } + } + } + } + } + } + } + } +}