diff --git a/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZioJavaProcessor.java b/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOJavaProcessor.java similarity index 93% rename from zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZioJavaProcessor.java rename to zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOJavaProcessor.java index dd6210f..2f7676b 100644 --- a/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZioJavaProcessor.java +++ b/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOJavaProcessor.java @@ -4,7 +4,7 @@ import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem; -public class Scala3ZioJavaProcessor { +public class Scala3ZIOJavaProcessor { @BuildStep public FeatureBuildItem feature() { diff --git a/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOResponseHandler.scala b/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOResponseHandler.scala index a68a6a6..e9bb416 100644 --- a/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOResponseHandler.scala +++ b/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOResponseHandler.scala @@ -4,48 +4,44 @@ import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext import org.jboss.resteasy.reactive.server.spi.ServerRestHandler import zio.ZIO -import java.lang.reflect.ParameterizedType import scala.jdk.CollectionConverters.* -import scala.util.Failure -import scala.util.Success class Scala3ZIOResponseHandler() extends ServerRestHandler { override def handle(requestContext: ResteasyReactiveRequestContext): Unit = { val result = requestContext.getResult + /* + TODO if we're able to read the environment from the effect, we might be able to hook into + Quarkus dependency injection mechanism to fill it here. For now, we can only assume its any. + */ type R = Any + + /* fixing the error type to Throwable. We can be sure its this type, as we've checked + it before in io.quarkiverse.scala.scala3.zio.deployment.Scala3ZIOReturnTypeMethodScanner.scan + There it can only be Nothing, or Throwable or subtypes of Throwable, so either way, we're + safe to assume it's Throwable here. + */ type E = Throwable + + /* We assume any as return type, as quarkus also accepts any object as return type. + */ type A = Any - /* - // TODO at the moment, we're just stupidly assume, the effect has no environment - // and the error type is a throwable. We need to figure out a way on how to access - // the type arguments of the ZIO effect in a more structured way. - // At the moment, a Environment of e.g. String with Int will be read as java.lang.Object, - // so we loose the type information which could be used for dependency injection - println("in handle of Scala3ZIOResponseHandler") - - val p = requestContext.getGenericReturnType.asInstanceOf[ParameterizedType] - val typeArgs = p.getActualTypeArguments.toSeq - val (environment, errorType, successType) = (typeArgs(0), typeArgs(1), typeArgs(2)) - - println(s"environment: $environment") - println(s"errorType: $errorType") - println(s"successType: $successType") - */ - result match - case r: ZIO[R, E, A] => - requestContext.suspend() - val f = zio.Unsafe.unsafe(u => zio.Runtime.default.unsafe.runToFuture(r)(zio.Trace.empty, u)) - f.onComplete { - case Success(value) => - requestContext.setResult(value) - requestContext.resume() - case Failure(exception) => - requestContext.handleException(exception, true) - requestContext.resume() - }(scala.concurrent.ExecutionContext.global) - - case _ => () + + requestContext.suspend() + val r = result.asInstanceOf[ZIO[R, E, A]] + + // we can experiment to update the requestContext directly from within the zio runtime.. for + // now working with a future seems good enough + + val r1 = r.fold(e => { + requestContext.handleException(e) + requestContext.resume() + }, a => { + requestContext.setResult(a) + requestContext.resume() + }) + + zio.Unsafe.unsafe(u => zio.Runtime.default.unsafe.runToFuture(r1)(zio.Trace.empty, u)) } } diff --git a/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOReturnTypeMethodScanner.scala b/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOReturnTypeMethodScanner.scala index 0db1c3e..f0718fe 100644 --- a/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOReturnTypeMethodScanner.scala +++ b/zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOReturnTypeMethodScanner.scala @@ -1,35 +1,29 @@ package io.quarkiverse.scala.scala3.zio.deployment -import org.jboss.jandex.AnnotationInstance import org.jboss.jandex.ClassInfo import org.jboss.jandex.DotName import org.jboss.jandex.MethodInfo import org.jboss.jandex.Type -import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor import org.jboss.resteasy.reactive.server.model.FixedHandlerChainCustomizer import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner -import zio.RIO import java.util import java.util.List as JList import java.util.Collections as JCollections class Scala3ZIOReturnTypeMethodScanner extends MethodScanner { - val ZIO: DotName = DotName.createSimple("zio.ZIO") -// val TASK: DotName = DotName.createSimple("zio.Task") -// val UIO: DotName = DotName.createSimple("zio.UIO") -// val RIO: DotName = DotName.createSimple("zio.RIO") - - + private val ZIO = DotName.createSimple("zio.ZIO") + private val nothing$ = DotName.createSimple("scala.Nothing$") + private val throwable = DotName.createSimple("java.lang.Throwable") override def scan(method: MethodInfo, actualEndpointClass: ClassInfo, methodContext: util.Map[String, AnyRef] ): JList[HandlerChainCustomizer] = { - println("in Scala3ZIOReturnTypeMethodScanner scan") if(isMethodSignatureAsync(method)) { + ensuringFailureTypeIsNothingOrAThrowable(method) JCollections.singletonList( new FixedHandlerChainCustomizer( new Scala3ZIOResponseHandler(), @@ -41,16 +35,28 @@ class Scala3ZIOReturnTypeMethodScanner extends MethodScanner { } } + private def ensuringFailureTypeIsNothingOrAThrowable(info: MethodInfo): Unit = { + import scala.jdk.CollectionConverters._ + val returnType = info.returnType() + val typeArguments: JList[Type] = returnType.asParameterizedType().arguments() + if (typeArguments.size() != 3) { + throw new RuntimeException("ZIO must have three type arguments") + } + val errorType = typeArguments.get(1) + + if !(errorType.name() == nothing$) && !(errorType.name() == throwable) then + val realClazz = Class.forName(errorType.name().toString(), false, Thread.currentThread().getContextClassLoader) + if (!classOf[Throwable].isAssignableFrom(realClazz)) { + val returnType = info.returnType().toString.replaceAll("<","[").replaceAll(">","]") + val parameters = info.parameters().asScala.map(v => s"${v.name()}:${v.`type`().toString}").mkString(",") + val signature = s"${info.name()}(${parameters}):${returnType}" + + throw new RuntimeException(s"The error type of def ${signature} in ${info.declaringClass()} needs to be either Nothing, a Throwable or subclass of Throwable") + } + } override def isMethodSignatureAsync(info: MethodInfo): Boolean = { - val name = info.returnType().name() - val isCorrect = name == ZIO - println(s"return type is $name, isCorrect: $isCorrect") - isCorrect -// name match { -// case ZIO | TASK | UIO => true -// case _ => false -// } + info.returnType().name() == ZIO } } diff --git a/zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/CustomThrowable.java b/zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/CustomThrowable.java new file mode 100644 index 0000000..197934e --- /dev/null +++ b/zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/CustomThrowable.java @@ -0,0 +1,5 @@ +package io.quarkiverse.scala.scala3.zio.it; + +public class CustomThrowable extends RuntimeException { + +} diff --git a/zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResource.scala b/zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResource.scala index fc9a48d..bfef1a3 100644 --- a/zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResource.scala +++ b/zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResource.scala @@ -4,7 +4,8 @@ package io.quarkiverse.scala.scala3.zio.it import jakarta.enterprise.context.ApplicationScoped import jakarta.ws.rs.GET import jakarta.ws.rs.Path -import zio.ZIO +import jakarta.ws.rs.QueryParam +import zio.* @Path("/scala3-zio") @ApplicationScoped @@ -17,12 +18,48 @@ class Scala3ZioResource { @GET @Path("/zio-string") - def zioString: ZIO[String with Int, Throwable, String] = { + def zioString: ZIO[String with Int, CustomThrowable, String] = { import zio._ for { _ <- ZIO.sleep(2.seconds) } yield "Hello ZIO" } - + + + @GET + @Path("/zio-task") + def zioTask(@QueryParam("a") a: String): Task[String] = { + import zio._ + for { + _ <- ZIO.unit + } yield s"Hello ZIO Task: ${a}" + } + + @GET + @Path("/zio-uio") + def zioUIO(@QueryParam("a") a: String): UIO[String] = { + import zio._ + for { + _ <- ZIO.unit + } yield s"Hello ZIO UIO: ${a}" + } + + @GET + @Path("/zio-io") + def zioIO(@QueryParam("a") a: String): IO[CustomThrowable, String] = { + import zio._ + for { + _ <- ZIO.unit + } yield s"Hello ZIO IO: ${a}" + } + +// @GET +// @Path("/zio-wrong-error-type") +// def zioError(a: String): ZIO[Any, Nothing, String] = { +// import zio._ +// for { +// _ <- ZIO.unit +// } yield "Hello ZIO" +// } } diff --git a/zio/integration-tests/src/test/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResourceTest.scala b/zio/integration-tests/src/test/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResourceTest.scala index bc6dfa9..c81f91d 100644 --- a/zio/integration-tests/src/test/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResourceTest.scala +++ b/zio/integration-tests/src/test/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResourceTest.scala @@ -23,7 +23,6 @@ class Scala3ZioResourceTest { @Test def `test zio-string Endpoint`(): Unit = { - println("running `test zio-string Endpoint`") Given { _.params("something", "value") }.When { @@ -33,4 +32,40 @@ class Scala3ZioResourceTest { } } + @Test + def `test zio Task Endpoint`(): Unit = { + Given { + _.params("a", "value") + }.When { + _.get("/scala3-zio/zio-task") + }.Then { + _.statusCode(200).body(is("Hello ZIO Task: value")) + } + } + + @Test + def `test zio UIO Endpoint`(): Unit = { + Given { + _.params("a", "value") + }.When { + _.get("/scala3-zio/zio-uio") + }.Then { + _.statusCode(200).body(is("Hello ZIO UIO: value")) + } + } + + @Test + def `test zio IO Endpoint`(): Unit = { + Given { + _.params("a", "value") + }.When { + _.get("/scala3-zio/zio-io") + }.Then { + _.statusCode(200).body(is("Hello ZIO IO: value")) + } + } + + + + }