feat(vertx-graphql): adds the possibility to control graphql-ui path and resources inclusion

This commit is contained in:
Manyanda Chitimbo
2019-11-14 12:51:02 +01:00
committed by Guillaume Smet
parent c5d04d8382
commit 7fab92439a
7 changed files with 184 additions and 3 deletions

View File

@@ -26,6 +26,16 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-vertx-graphql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>

View File

@@ -0,0 +1,32 @@
package io.quarkus.vertx.graphql.deployment;
import io.quarkus.runtime.annotations.ConfigGroup;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
@ConfigRoot
public final class VertxGraphqlConfig {
/**
* GraphQL UI configuration
*/
@ConfigItem
VertxGraphqlUiConfig ui;
@ConfigGroup
public static class VertxGraphqlUiConfig {
/**
* If GraphQL UI should be included every time. By default this is only included when the application is running
* in dev mode.
*/
@ConfigItem(defaultValue = "false")
boolean alwaysInclude;
/**
* The path where GraphQL UI is available.
* <p>
* The value `/` is not allowed as it blocks the application from serving anything else.
*/
@ConfigItem(defaultValue = "/graphql-ui")
String path;
}
}

View File

@@ -2,17 +2,31 @@ package io.quarkus.vertx.graphql.deployment;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.annotations.ExecutionTime;
import io.quarkus.deployment.annotations.Record;
import io.quarkus.deployment.builditem.FeatureBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImagePackageResourceBuildItem;
import io.quarkus.deployment.builditem.LaunchModeBuildItem;
import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceDirectoryBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.vertx.graphql.runtime.VertxGraphqlRecorder;
import io.quarkus.vertx.http.deployment.RouteBuildItem;
import io.quarkus.vertx.http.deployment.WebsocketSubProtocolsBuildItem;
import io.quarkus.vertx.http.deployment.devmode.NotFoundPageDisplayableEndpointBuildItem;
import io.vertx.core.Handler;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.graphql.impl.GraphQLBatch;
import io.vertx.ext.web.handler.graphql.impl.GraphQLInputDeserializer;
import io.vertx.ext.web.handler.graphql.impl.GraphQLQuery;
class VertxGraphqlProcessor {
private static Pattern TRAILING_SLASH_SUFFIX_REGEX = Pattern.compile("/+$");
@BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem(FeatureBuildItem.VERTX_GRAPHQL);
@@ -32,7 +46,29 @@ class VertxGraphqlProcessor {
}
@BuildStep
NativeImagePackageResourceBuildItem registerNativeImageResources() {
return new NativeImagePackageResourceBuildItem("io/vertx/ext/web/handler/graphiql");
@Record(ExecutionTime.STATIC_INIT)
void registerVertxGraphqlUI(VertxGraphqlRecorder recorder,
BuildProducer<NativeImageResourceDirectoryBuildItem> nativeResourcesProducer, VertxGraphqlConfig config,
LaunchModeBuildItem launchMode, BuildProducer<NotFoundPageDisplayableEndpointBuildItem> displayableEndpoints,
BuildProducer<RouteBuildItem> routes) {
boolean includeVertxGraphqlUi = launchMode.getLaunchMode().isDevOrTest() || config.ui.alwaysInclude;
if (!includeVertxGraphqlUi) {
return;
}
Matcher matcher = TRAILING_SLASH_SUFFIX_REGEX.matcher(config.ui.path);
String path = matcher.replaceAll("");
if (path.isEmpty()) {
throw new ConfigurationException(
"quarkus.vertx-graphql.ui.path was set to \"" + config.ui.path
+ "\", this is not allowed as it blocks the application from serving anything else.");
}
Handler<RoutingContext> handler = recorder.handler(path);
routes.produce(new RouteBuildItem(path, handler));
routes.produce(new RouteBuildItem(path + "/*", handler));
displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(path + "/"));
nativeResourcesProducer.produce(new NativeImageResourceDirectoryBuildItem("io/vertx/ext/web/handler/graphiql"));
}
}

View File

@@ -0,0 +1,25 @@
package io.quarkus.vertx.graphql.deployment;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.runtime.configuration.ConfigurationException;
import io.quarkus.test.QuarkusUnitTest;
public class ErroneousConfigTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setExpectedException(ConfigurationException.class)
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource(new StringAsset("quarkus.vertx-graphql.ui.path=/\n"), "application.properties"));
@Test
public void shouldNotStartApplicationIfPathIsASlash() {
Assertions.fail();
}
}

View File

@@ -0,0 +1,23 @@
package io.quarkus.vertx.graphql.deployment;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
public class ServingUIFromCustomPathTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addAsResource(new StringAsset("quarkus.vertx-graphql.ui.path=/custom\n"), "application.properties"));
@Test
public void shouldServeVertxGraphqlUiFromCustomPath() {
RestAssured.when().get("/custom").then().statusCode(200);
}
}

View File

@@ -0,0 +1,21 @@
package io.quarkus.vertx.graphql.deployment;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;
public class ServingUIFromDefaultPathTest {
@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class));
@Test
public void shouldServeVertxGraphqlUiFromDefaultPath() {
RestAssured.when().get("/graphql-ui").then().statusCode(200);
}
}

View File

@@ -0,0 +1,34 @@
package io.quarkus.vertx.graphql.runtime;
import io.quarkus.runtime.annotations.Recorder;
import io.vertx.core.Handler;
import io.vertx.core.http.HttpHeaders;
import io.vertx.ext.web.RoutingContext;
import io.vertx.ext.web.handler.graphql.GraphiQLHandler;
import io.vertx.ext.web.handler.graphql.GraphiQLHandlerOptions;
@Recorder
public class VertxGraphqlRecorder {
public Handler<RoutingContext> handler(String path) {
GraphiQLHandlerOptions options = new GraphiQLHandlerOptions();
options.setEnabled(true);
Handler<RoutingContext> handler = GraphiQLHandler.create(options);
return new Handler<RoutingContext>() {
@Override
public void handle(RoutingContext event) {
if (event.normalisedPath().length() == path.length()) {
event.response().setStatusCode(302);
event.response().headers().set(HttpHeaders.LOCATION, path + "/");
event.response().end();
return;
}
handler.handle(event);
}
};
}
}