Skip to content

Commit a3b5aaf

Browse files
committed
ZIO: checking the error type is either Nothing or a Throwable
1 parent 992c22e commit a3b5aaf

File tree

6 files changed

+132
-56
lines changed

6 files changed

+132
-56
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import io.quarkus.deployment.builditem.FeatureBuildItem;
55
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
66

7-
public class Scala3ZioJavaProcessor {
7+
public class Scala3ZIOJavaProcessor {
88

99
@BuildStep
1010
public FeatureBuildItem feature() {

zio/deployment/src/main/scala/io/quarkiverse/scala/scala3/zio/deployment/Scala3ZIOResponseHandler.scala

Lines changed: 26 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -4,48 +4,41 @@ import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext
44
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler
55
import zio.ZIO
66

7-
import java.lang.reflect.ParameterizedType
87
import scala.jdk.CollectionConverters.*
9-
import scala.util.Failure
10-
import scala.util.Success
118

129
class Scala3ZIOResponseHandler() extends ServerRestHandler {
1310

1411
override def handle(requestContext: ResteasyReactiveRequestContext): Unit = {
1512
val result = requestContext.getResult
1613

14+
/*
15+
TODO if we're able to read the environment from the effect, we might be able to hook into
16+
Quarkus dependency injection mechanism to fill it here. For now, we can only assume its any.
17+
*/
1718
type R = Any
19+
20+
/* fixing the error type to Throwable. We can be sure its this type, as we've checked
21+
it before in io.quarkiverse.scala.scala3.zio.deployment.Scala3ZIOReturnTypeMethodScanner.scan
22+
There it can only be Nothing, or Throwable or subtypes of Throwable, so either way, we're
23+
safe to assume it's Throwable here.
24+
*/
1825
type E = Throwable
26+
27+
/* We assume any as return type, as quarkus also accepts any object as return type.
28+
*/
1929
type A = Any
20-
/*
21-
// TODO at the moment, we're just stupidly assume, the effect has no environment
22-
// and the error type is a throwable. We need to figure out a way on how to access
23-
// the type arguments of the ZIO effect in a more structured way.
24-
// At the moment, a Environment of e.g. String with Int will be read as java.lang.Object,
25-
// so we loose the type information which could be used for dependency injection
26-
println("in handle of Scala3ZIOResponseHandler")
27-
28-
val p = requestContext.getGenericReturnType.asInstanceOf[ParameterizedType]
29-
val typeArgs = p.getActualTypeArguments.toSeq
30-
val (environment, errorType, successType) = (typeArgs(0), typeArgs(1), typeArgs(2))
31-
32-
println(s"environment: $environment")
33-
println(s"errorType: $errorType")
34-
println(s"successType: $successType")
35-
*/
36-
result match
37-
case r: ZIO[R, E, A] =>
38-
requestContext.suspend()
39-
val f = zio.Unsafe.unsafe(u => zio.Runtime.default.unsafe.runToFuture(r)(zio.Trace.empty, u))
40-
f.onComplete {
41-
case Success(value) =>
42-
requestContext.setResult(value)
43-
requestContext.resume()
44-
case Failure(exception) =>
45-
requestContext.handleException(exception, true)
46-
requestContext.resume()
47-
}(scala.concurrent.ExecutionContext.global)
48-
49-
case _ => ()
30+
31+
requestContext.suspend()
32+
val r = result.asInstanceOf[ZIO[R, E, A]]
33+
34+
val r1 = r.fold(e => {
35+
requestContext.handleException(e)
36+
requestContext.resume()
37+
}, a => {
38+
requestContext.setResult(a)
39+
requestContext.resume()
40+
})
41+
42+
zio.Unsafe.unsafe(u => zio.Runtime.default.unsafe.runToFuture(r1)(zio.Trace.empty, u))
5043
}
5144
}
Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,29 @@
11
package io.quarkiverse.scala.scala3.zio.deployment
22

3-
import org.jboss.jandex.AnnotationInstance
43
import org.jboss.jandex.ClassInfo
54
import org.jboss.jandex.DotName
65
import org.jboss.jandex.MethodInfo
76
import org.jboss.jandex.Type
8-
import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor
97
import org.jboss.resteasy.reactive.server.model.FixedHandlerChainCustomizer
108
import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer
119
import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner
12-
import zio.RIO
1310

1411
import java.util
1512
import java.util.List as JList
1613
import java.util.Collections as JCollections
1714

1815
class Scala3ZIOReturnTypeMethodScanner extends MethodScanner {
19-
val ZIO: DotName = DotName.createSimple("zio.ZIO")
20-
// val TASK: DotName = DotName.createSimple("zio.Task")
21-
// val UIO: DotName = DotName.createSimple("zio.UIO")
22-
// val RIO: DotName = DotName.createSimple("zio.RIO")
23-
24-
16+
private val ZIO = DotName.createSimple("zio.ZIO")
17+
private val nothing$ = DotName.createSimple("scala.Nothing$")
18+
private val throwable = DotName.createSimple("java.lang.Throwable")
2519

2620

2721
override def scan(method: MethodInfo,
2822
actualEndpointClass: ClassInfo,
2923
methodContext: util.Map[String, AnyRef]
3024
): JList[HandlerChainCustomizer] = {
31-
println("in Scala3ZIOReturnTypeMethodScanner scan")
3225
if(isMethodSignatureAsync(method)) {
26+
ensuringFailureTypeIsNothingOrAThrowable(method)
3327
JCollections.singletonList(
3428
new FixedHandlerChainCustomizer(
3529
new Scala3ZIOResponseHandler(),
@@ -41,16 +35,28 @@ class Scala3ZIOReturnTypeMethodScanner extends MethodScanner {
4135
}
4236
}
4337

38+
private def ensuringFailureTypeIsNothingOrAThrowable(info: MethodInfo): Unit = {
39+
import scala.jdk.CollectionConverters._
40+
val returnType = info.returnType()
41+
val typeArguments: JList[Type] = returnType.asParameterizedType().arguments()
42+
if (typeArguments.size() != 3) {
43+
throw new RuntimeException("ZIO must have three type arguments")
44+
}
45+
val errorType = typeArguments.get(1)
46+
47+
if !(errorType.name() == nothing$) && !(errorType.name() == throwable) then
48+
val realClazz = Class.forName(errorType.name().toString(), false, Thread.currentThread().getContextClassLoader)
49+
if (!classOf[Throwable].isAssignableFrom(realClazz)) {
50+
val returnType = info.returnType().toString.replaceAll("<","[").replaceAll(">","]")
51+
val parameters = info.parameters().asScala.map(v => s"${v.name()}:${v.`type`().toString}").mkString(",")
52+
val signature = s"${info.name()}(${parameters}):${returnType}"
53+
54+
throw new RuntimeException(s"The error type of def ${signature} in ${info.declaringClass()} needs to be either Nothing, a Throwable or subclass of Throwable")
55+
}
56+
}
4457

4558

4659
override def isMethodSignatureAsync(info: MethodInfo): Boolean = {
47-
val name = info.returnType().name()
48-
val isCorrect = name == ZIO
49-
println(s"return type is $name, isCorrect: $isCorrect")
50-
isCorrect
51-
// name match {
52-
// case ZIO | TASK | UIO => true
53-
// case _ => false
54-
// }
60+
info.returnType().name() == ZIO
5561
}
5662
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package io.quarkiverse.scala.scala3.zio.it;
2+
3+
public class CustomThrowable extends RuntimeException {
4+
5+
}

zio/integration-tests/src/main/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResource.scala

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ package io.quarkiverse.scala.scala3.zio.it
44
import jakarta.enterprise.context.ApplicationScoped
55
import jakarta.ws.rs.GET
66
import jakarta.ws.rs.Path
7-
import zio.ZIO
7+
import jakarta.ws.rs.QueryParam
8+
import zio.*
89

910
@Path("/scala3-zio")
1011
@ApplicationScoped
@@ -17,12 +18,48 @@ class Scala3ZioResource {
1718

1819
@GET
1920
@Path("/zio-string")
20-
def zioString: ZIO[String with Int, Throwable, String] = {
21+
def zioString: ZIO[String with Int, CustomThrowable, String] = {
2122
import zio._
2223
for {
2324
_ <- ZIO.sleep(2.seconds)
2425
} yield "Hello ZIO"
2526
}
26-
27+
28+
29+
@GET
30+
@Path("/zio-task")
31+
def zioTask(@QueryParam("a") a: String): Task[String] = {
32+
import zio._
33+
for {
34+
_ <- ZIO.unit
35+
} yield s"Hello ZIO Task: ${a}"
36+
}
37+
38+
@GET
39+
@Path("/zio-uio")
40+
def zioUIO(@QueryParam("a") a: String): UIO[String] = {
41+
import zio._
42+
for {
43+
_ <- ZIO.unit
44+
} yield s"Hello ZIO UIO: ${a}"
45+
}
46+
47+
@GET
48+
@Path("/zio-io")
49+
def zioIO(@QueryParam("a") a: String): IO[CustomThrowable, String] = {
50+
import zio._
51+
for {
52+
_ <- ZIO.unit
53+
} yield s"Hello ZIO IO: ${a}"
54+
}
55+
56+
// @GET
57+
// @Path("/zio-wrong-error-type")
58+
// def zioError(a: String): ZIO[Any, Nothing, String] = {
59+
// import zio._
60+
// for {
61+
// _ <- ZIO.unit
62+
// } yield "Hello ZIO"
63+
// }
2764

2865
}

zio/integration-tests/src/test/scala/io/quarkiverse/scala/scala3/zio/it/Scala3ZioResourceTest.scala

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ class Scala3ZioResourceTest {
2323

2424
@Test
2525
def `test zio-string Endpoint`(): Unit = {
26-
println("running `test zio-string Endpoint`")
2726
Given {
2827
_.params("something", "value")
2928
}.When {
@@ -33,4 +32,40 @@ class Scala3ZioResourceTest {
3332
}
3433
}
3534

35+
@Test
36+
def `test zio Task Endpoint`(): Unit = {
37+
Given {
38+
_.params("a", "value")
39+
}.When {
40+
_.get("/scala3-zio/zio-task")
41+
}.Then {
42+
_.statusCode(200).body(is("Hello ZIO Task: value"))
43+
}
44+
}
45+
46+
@Test
47+
def `test zio UIO Endpoint`(): Unit = {
48+
Given {
49+
_.params("a", "value")
50+
}.When {
51+
_.get("/scala3-zio/zio-uio")
52+
}.Then {
53+
_.statusCode(200).body(is("Hello ZIO UIO: value"))
54+
}
55+
}
56+
57+
@Test
58+
def `test zio IO Endpoint`(): Unit = {
59+
Given {
60+
_.params("a", "value")
61+
}.When {
62+
_.get("/scala3-zio/zio-io")
63+
}.Then {
64+
_.statusCode(200).body(is("Hello ZIO IO: value"))
65+
}
66+
}
67+
68+
69+
70+
3671
}

0 commit comments

Comments
 (0)