Skip to content

Commit

Permalink
Don't lose reference to the application class when an unchecked excep…
Browse files Browse the repository at this point in the history
…tion occurs in dynamicClient.executeSync
  • Loading branch information
jmartisk committed Jan 21, 2025
1 parent 930c099 commit ac670b2
Show file tree
Hide file tree
Showing 2 changed files with 96 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -212,7 +213,8 @@ private Response executeSync(JsonObject json, MultiMap additionalHeaders) {
for (Map.Entry<String, Uni<String>> dynamicHeaderEntry : dynamicHeaders.entrySet()) {
allHeaders.add(dynamicHeaderEntry.getKey(), dynamicHeaderEntry.getValue().await().indefinitely());
}
return executeSingleResultOperationOverHttp(json, allHeaders).await().indefinitely();
HttpResponse<Buffer> httpResponse = executeSingleResultOperationOverHttp(json, allHeaders).await().indefinitely();
return toResponse(httpResponse);
}
}

Expand Down Expand Up @@ -322,11 +324,12 @@ private Uni<Response> 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);
}
}
}
Expand Down Expand Up @@ -442,15 +445,18 @@ private Uni<WebSocketSubprotocolHandler> webSocketHandler() {
});
}

private Uni<Response> executeSingleResultOperationOverHttp(JsonObject json, MultiMap allHeaders) {
private Uni<HttpResponse<Buffer>> 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<Buffer> httpResponse) {
return ResponseReader.readFrom(httpResponse.bodyAsString(),
convertHeaders(httpResponse.headers()), httpResponse.statusCode(), httpResponse.statusMessage(),
allowUnexpectedResponseFields);
}

private Uni<Response> executeSingleResultOperationOverWebsocket(JsonObject json) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
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;
}

// mock server with websocket support which does not send any messages to the client,
// so this should cause a timeout while waiting for the subscription to be acknowledged
private HttpServer runMockServer() throws ExecutionException, InterruptedException, TimeoutException {
HttpServerOptions options = new HttpServerOptions();
options.setHost("localhost");
HttpServer server = vertx.createHttpServer(options);
server.requestHandler(new Handler<io.vertx.core.http.HttpServerRequest>() {
@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();
}
}

0 comments on commit ac670b2

Please sign in to comment.