diff --git a/client/implementation-vertx/src/main/java/io/smallrye/graphql/client/vertx/dynamic/VertxDynamicGraphQLClient.java b/client/implementation-vertx/src/main/java/io/smallrye/graphql/client/vertx/dynamic/VertxDynamicGraphQLClient.java index 5c90b3469..8a2de7687 100644 --- a/client/implementation-vertx/src/main/java/io/smallrye/graphql/client/vertx/dynamic/VertxDynamicGraphQLClient.java +++ b/client/implementation-vertx/src/main/java/io/smallrye/graphql/client/vertx/dynamic/VertxDynamicGraphQLClient.java @@ -36,6 +36,7 @@ import io.vertx.core.http.WebSocket; import io.vertx.core.http.WebsocketVersion; import io.vertx.core.http.impl.headers.HeadersMultiMap; +import io.vertx.ext.web.client.HttpResponse; import io.vertx.ext.web.client.WebClient; import io.vertx.ext.web.client.WebClientOptions; @@ -212,7 +213,8 @@ private Response executeSync(JsonObject json, MultiMap additionalHeaders) { for (Map.Entry> dynamicHeaderEntry : dynamicHeaders.entrySet()) { allHeaders.add(dynamicHeaderEntry.getKey(), dynamicHeaderEntry.getValue().await().indefinitely()); } - return executeSingleResultOperationOverHttp(json, allHeaders).await().indefinitely(); + HttpResponse httpResponse = executeSingleResultOperationOverHttp(json, allHeaders).await().indefinitely(); + return toResponse(httpResponse); } } @@ -322,11 +324,12 @@ private Uni executeAsync(JsonObject json, MultiMap additionalHeaders) }).replaceWithVoid()); } if (unis.isEmpty()) { - return executeSingleResultOperationOverHttp(json, allHeaders); + return executeSingleResultOperationOverHttp(json, allHeaders).map(this::toResponse); } else { return Uni.combine().all().unis(unis) .combinedWith(f -> f).onItem() - .transformToUni(f -> executeSingleResultOperationOverHttp(json, allHeaders)); + .transformToUni(f -> executeSingleResultOperationOverHttp(json, allHeaders)) + .map(this::toResponse); } } } @@ -442,15 +445,18 @@ private Uni webSocketHandler() { }); } - private Uni executeSingleResultOperationOverHttp(JsonObject json, MultiMap allHeaders) { + private Uni> executeSingleResultOperationOverHttp(JsonObject json, MultiMap allHeaders) { return Uni.createFrom().completionStage( url.get().subscribeAsCompletionStage().thenCompose(instanceUrl -> webClient.postAbs(instanceUrl) .putHeaders(allHeaders) .sendBuffer(Buffer.buffer(json.toString())) - .toCompletionStage())) - .map(response -> ResponseReader.readFrom(response.bodyAsString(), - convertHeaders(response.headers()), response.statusCode(), response.statusMessage(), - allowUnexpectedResponseFields)); + .toCompletionStage())); + } + + private Response toResponse(HttpResponse httpResponse) { + return ResponseReader.readFrom(httpResponse.bodyAsString(), + convertHeaders(httpResponse.headers()), httpResponse.statusCode(), httpResponse.statusMessage(), + allowUnexpectedResponseFields); } private Uni executeSingleResultOperationOverWebsocket(JsonObject json) { diff --git a/client/implementation-vertx/src/test/java/io/smallrye/graphql/client/vertx/test/DynamicClientExceptionTest.java b/client/implementation-vertx/src/test/java/io/smallrye/graphql/client/vertx/test/DynamicClientExceptionTest.java new file mode 100644 index 000000000..f33b49ea4 --- /dev/null +++ b/client/implementation-vertx/src/test/java/io/smallrye/graphql/client/vertx/test/DynamicClientExceptionTest.java @@ -0,0 +1,80 @@ +package io.smallrye.graphql.client.vertx.test; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import jakarta.json.JsonObject; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import io.smallrye.graphql.client.InvalidResponseException; +import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; +import io.smallrye.graphql.client.vertx.dynamic.VertxDynamicGraphQLClientBuilder; +import io.vertx.core.Handler; +import io.vertx.core.Vertx; +import io.vertx.core.http.HttpServer; +import io.vertx.core.http.HttpServerOptions; + +/** + * Make sure than when executing a sync operation with a dynamic client and + * it throws an unchecked exception, the resulting exception will contain a + * reference to the application class that invoked the call. + */ +public class DynamicClientExceptionTest { + + static Vertx vertx = Vertx.vertx(); + + @Test + public void test() throws ExecutionException, InterruptedException, TimeoutException { + HttpServer server = runMockServer(); + try { + DynamicGraphQLClient client = new VertxDynamicGraphQLClientBuilder() + .url("http://localhost:" + server.actualPort()) + .build(); + try { + JsonObject data = client.executeSync("{something-whatever}").getData(); + Assertions.fail("Expected an exception"); + } catch (Exception e) { + Assertions.assertTrue(e instanceof InvalidResponseException); + StackTraceElement element = findStackTraceElementThatContainsClass("DynamicClientExceptionTest", + e.getStackTrace()); + Assertions.assertNotNull(element, () -> { + e.printStackTrace(); + return "Expected the stack trace to contain a reference to the test class"; + }); + } + } finally { + server.close(); + } + } + + private StackTraceElement findStackTraceElementThatContainsClass(String string, StackTraceElement[] stackTrace) { + for (StackTraceElement stackTraceElement : stackTrace) { + if (stackTraceElement.getClassName().contains(string)) { + return stackTraceElement; + } + } + return null; + } + + private HttpServer runMockServer() throws ExecutionException, InterruptedException, TimeoutException { + HttpServerOptions options = new HttpServerOptions(); + options.setHost("localhost"); + HttpServer server = vertx.createHttpServer(options); + server.requestHandler(new Handler() { + @Override + public void handle(io.vertx.core.http.HttpServerRequest event) { + event.response().end("BAD RESPONSE"); + } + }); + return server.listen(0).toCompletionStage().toCompletableFuture().get(10, TimeUnit.SECONDS); + } + + @AfterAll + public static void cleanup() { + vertx.close(); + } +}