Skip to content

Commit a36eb6b

Browse files
committed
Future[T] and Promise[T] as Controller ReturnTypes
1 parent 6a3743f commit a36eb6b

File tree

6 files changed

+153
-5
lines changed

6 files changed

+153
-5
lines changed

README.md

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ VERSIONS = [
6161
6262
dependencies {
6363
implementation "io.quarkiverse.scala:quarkus-scala3:${VERSIONS.QUARKUS_SCALA3}"
64+
implementation "io.quarkiverse.scala:quarkus-scala3-deployment:${VERSIONS.QUARKUS_SCALA3}"
6465
implementation("org.scala-lang:scala3-compiler_3") {
6566
version {
6667
strictly VERSIONS.SCALA3
@@ -133,7 +134,12 @@ In your `pom.xml` file, add:
133134
<dependency>
134135
<groupId>io.quarkiverse.scala</groupId>
135136
<artifactId>quarkus-scala3</artifactId>
136-
<version>0.0.1<version>
137+
<version>1.0.0<version>
138+
</dependency>
139+
<dependency>
140+
<groupId>io.quarkiverse.scala</groupId>
141+
<artifactId>quarkus-scala3-deployment</artifactId>
142+
<version>1.0.0</version>
137143
</dependency>
138144
```
139145

@@ -440,6 +446,36 @@ def mkRoutes(router: Router) =
440446
})
441447
```
442448

449+
450+
### `Future[T]` and `Promise[T]` support in Rest-Endpoints
451+
452+
If you're using Scala 3, you might be using `Future` for async operations.
453+
This extension allows you to return `Future[T]` and `Promise[T]` from your rest-endpoints.
454+
455+
```scala
456+
457+
@Path("/")
458+
class GreetingResource
459+
460+
@GET
461+
@Path("/greet/future")
462+
@Produces(Array(TEXT_PLAIN))
463+
def futureGreeting(): Future[String] =
464+
Future.successful("Hello from the future")
465+
end futureGreeting
466+
467+
@GET
468+
@Path("/greet/promise")
469+
def promiseGreeting(): Promise[String] =
470+
Promise.successful("Hello from the promise")
471+
end promiseGreeting
472+
473+
end GreetingResource
474+
```
475+
476+
If the `Future[T]` or `Promise[T]` fails, the normal exception handling is invoked.
477+
478+
443479
## Contributors ✨
444480

445481
Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
@@ -452,6 +488,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
452488
<tr>
453489
<td align="center" valign="top" width="14.28%"><a href="https://github.com/GavinRay97"><img src="https://avatars.githubusercontent.com/u/26604994?v=4?s=100" width="100px;" alt="Gavin Ray"/><br /><sub><b>Gavin Ray</b></sub></a><br /><a href="https://github.com/quarkiverse/quarkus-scala3/commits?author=GavinRay97" title="Code">💻</a> <a href="#maintenance-GavinRay97" title="Maintenance">🚧</a></td>
454490
<td align="center" valign="top" width="14.28%"><a href="https://lesincroyableslivres.fr/"><img src="https://avatars.githubusercontent.com/u/1279749?v=4?s=100" width="100px;" alt="Guillaume Smet"/><br /><sub><b>Guillaume Smet</b></sub></a><br /><a href="https://github.com/quarkiverse/quarkus-scala3/commits?author=gsmet" title="Code">💻</a></td>
491+
<td align="center" valign="top" width="14.28%"><a href="https://github.com/domdorn"><img src="https://avatars.githubusercontent.com/u/100349?v=4?s=100" width="100px;" alt="Dominik Dorn"/><br /><sub><b>Dominik Dorn</b></sub></a><br /><a href="https://github.com/quarkiverse/quarkus-scala3/commits?author=domdorn" title="Code">🚀🍺💻</a></td>
455492
</tr>
456493
</tbody>
457494
</table>
@@ -462,3 +499,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
462499
<!-- ALL-CONTRIBUTORS-LIST:END -->
463500

464501
This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
502+
503+
## TODOs
504+
- correctly generate OpenAPI Spec for methods returning `Future[T]` or `Promise[T]`, e.g. similar to [Quarkus #8499](https://github.com/quarkusio/quarkus/issues/8499)
505+
- Quarkus-Arc has special handling for `CompletionStage[T]`, maybe we should add similar handling for `Future[T]` and `Promise[T]`, see [ActiveRequestContextInterceptor](https://github.com/quarkusio/quarkus/blob/24d3e5262d20fdaa8c056d59f012f8c7b5b1c5c8/independent-projects/arc/runtime/src/main/java/io/quarkus/arc/impl/ActivateRequestContextInterceptor.java) ?

deployment/pom.xml

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,19 @@
2727
<artifactId>quarkus-jackson-spi</artifactId>
2828
</dependency>
2929

30+
<dependency>
31+
<groupId>io.quarkus</groupId>
32+
<artifactId>quarkus-rest-spi-deployment</artifactId>
33+
</dependency>
34+
<dependency>
35+
<groupId>io.quarkus.resteasy.reactive</groupId>
36+
<artifactId>resteasy-reactive-processor</artifactId>
37+
</dependency>
38+
<dependency>
39+
<groupId>io.quarkus</groupId>
40+
<artifactId>quarkus-rest-server-spi-deployment</artifactId>
41+
</dependency>
42+
3043
<dependency>
3144
<groupId>io.quarkus</groupId>
3245
<artifactId>quarkus-junit5-internal</artifactId>
@@ -36,7 +49,14 @@
3649
<dependency>
3750
<groupId>org.scala-lang</groupId>
3851
<artifactId>scala3-interfaces</artifactId>
39-
<version>3.3.1</version>
52+
<version>3.4.1</version>
53+
<scope>compile</scope>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.scala-lang</groupId>
57+
<artifactId>scala3-library_3</artifactId>
58+
<version>3.4.1</version>
59+
<scope>compile</scope>
4060
</dependency>
4161
</dependencies>
4262
<build>
@@ -55,4 +75,4 @@
5575
</plugin>
5676
</plugins>
5777
</build>
58-
</project>
78+
</project>
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package io.quarkiverse.scala.scala3.deployment;
2+
3+
import java.util.Collections;
4+
import java.util.List;
5+
import java.util.Map;
6+
7+
import org.jboss.jandex.ClassInfo;
8+
import org.jboss.jandex.DotName;
9+
import org.jboss.jandex.MethodInfo;
10+
import org.jboss.resteasy.reactive.server.model.FixedHandlerChainCustomizer;
11+
import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
12+
import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer.Phase;
13+
import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner;
14+
15+
public class FutureReturnTypeMethodScanner implements MethodScanner {
16+
private static final DotName FUTURE = DotName.createSimple("scala.concurrent.Future");
17+
private static final DotName PROMISE = DotName.createSimple("scala.concurrent.Promise");
18+
private static final DotName BLOCKING_ANNOTATION = DotName.createSimple("io.smallrye.common.annotation.Blocking");
19+
20+
private void ensureNotBlocking(MethodInfo method) {
21+
if (method.annotation(BLOCKING_ANNOTATION) != null) {
22+
String format = String.format("Suspendable @Blocking methods are not supported yet: %s.%s",
23+
method.declaringClass().name(), method.name());
24+
throw new IllegalStateException(format);
25+
}
26+
}
27+
28+
@Override
29+
public List<HandlerChainCustomizer> scan(MethodInfo method, ClassInfo actualEndpointClass,
30+
Map<String, Object> methodContext) {
31+
32+
if (isMethodSignatureAsync(method)) {
33+
ensureNotBlocking(method);
34+
35+
return Collections.singletonList(new FixedHandlerChainCustomizer(
36+
new ScalaFutureResponseHandler(), Phase.AFTER_METHOD_INVOKE));
37+
}
38+
return Collections.emptyList();
39+
}
40+
41+
@Override
42+
public boolean isMethodSignatureAsync(MethodInfo info) {
43+
DotName name = info.returnType().name();
44+
return name.equals(FUTURE) || name.equals(PROMISE);
45+
}
46+
47+
}

deployment/src/main/java/io/quarkiverse/scala/scala3/deployment/Scala3Processor.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import io.quarkus.deployment.annotations.BuildStep;
55
import io.quarkus.deployment.builditem.FeatureBuildItem;
66
import io.quarkus.jackson.spi.ClassPathJacksonModuleBuildItem;
7+
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
78

89
class Scala3Processor {
910

@@ -28,4 +29,9 @@ void registerScalaJacksonModule(BuildProducer<ClassPathJacksonModuleBuildItem> c
2829
} catch (Exception ignored) {
2930
}
3031
}
32+
33+
@BuildStep
34+
MethodScannerBuildItem enableAsyncMethodsInRestEndpoints() {
35+
return new MethodScannerBuildItem(new FutureReturnTypeMethodScanner());
36+
}
3137
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package io.quarkiverse.scala.scala3.deployment;
2+
3+
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
4+
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;
5+
6+
public class ScalaFutureResponseHandler implements ServerRestHandler {
7+
8+
private void handleFuture(ResteasyReactiveRequestContext requestContext,
9+
scala.concurrent.Future<?> f) {
10+
11+
requestContext.suspend();
12+
13+
f.onComplete(tryValue -> {
14+
if (tryValue.isSuccess()) {
15+
requestContext.setResult(tryValue.get());
16+
} else {
17+
requestContext.handleException(tryValue.failed().get(), true);
18+
}
19+
requestContext.resume();
20+
return null;
21+
}, scala.concurrent.ExecutionContext.global());
22+
23+
}
24+
25+
@Override
26+
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
27+
if (requestContext.getResult() instanceof scala.concurrent.Future<?> future) {
28+
handleFuture(requestContext, future);
29+
} else if (requestContext.getResult() instanceof scala.concurrent.Promise<?> promise) {
30+
handleFuture(requestContext, promise.future());
31+
}
32+
}
33+
34+
}

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
<parent>
55
<groupId>io.quarkiverse</groupId>
66
<artifactId>quarkiverse-parent</artifactId>
7-
<version>15</version>
7+
<version>16</version>
88
</parent>
99
<groupId>io.quarkiverse.scala</groupId>
1010
<artifactId>quarkus-scala3-parent</artifactId>
@@ -24,7 +24,7 @@
2424
<properties>
2525
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
2626
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
27-
<quarkus.version>3.6.4</quarkus.version>
27+
<quarkus.version>3.10.0</quarkus.version>
2828
</properties>
2929
<dependencyManagement>
3030
<dependencies>

0 commit comments

Comments
 (0)