diff --git a/docs/src/main/asciidoc/smallrye-graphql-client.adoc b/docs/src/main/asciidoc/smallrye-graphql-client.adoc index 918f80a9b95dc..fc17cf9559775 100644 --- a/docs/src/main/asciidoc/smallrye-graphql-client.adoc +++ b/docs/src/main/asciidoc/smallrye-graphql-client.adoc @@ -229,6 +229,11 @@ or move this over to the configuration file, `application.properties`: quarkus.smallrye-graphql-client.star-wars-typesafe.url=https://swapi-graphql.netlify.app/.netlify/functions/index ---- +NOTE: During *tests only*, the URL is an optional property, and if it's not specified, Quarkus will assume +that the target of the client is the application that is being tested (typically, `http://localhost:8081/graphql`). +This is useful if your application contains a GraphQL server-side API as well as a GraphQL client that is used for +testing the API. + `star-wars-typesafe` is the name of the configured client instance, and corresponds to the `configKey` in the `@GraphQLClientApi` annotation. If you don't want to specify a custom name, you can leave out the `configKey`, and then refer to it by using the fully qualified name of the interface. diff --git a/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java b/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java index 63400a4b51cf8..b91095b52400d 100644 --- a/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java +++ b/extensions/smallrye-graphql-client/deployment/src/main/java/io/quarkus/smallrye/graphql/client/deployment/SmallRyeGraphQLClientProcessor.java @@ -11,6 +11,7 @@ import javax.inject.Singleton; import org.jboss.jandex.AnnotationInstance; +import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; import org.jboss.jandex.MethodInfo; @@ -140,17 +141,31 @@ void setTypesafeApiClasses(BeanArchiveIndexBuildItem index, */ @BuildStep @Record(RUNTIME_INIT) - void shortNamesToQualifiedNames(BuildProducer syntheticBeans, + void initializeClientSupport(BuildProducer syntheticBeans, SmallRyeGraphQLClientRecorder recorder, GraphQLClientsConfig quarkusConfig, BeanArchiveIndexBuildItem index) { + // to store config keys of all clients found in the application code + List knownConfigKeys = new ArrayList<>(); + Map shortNamesToQualifiedNames = new HashMap<>(); for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) { ClassInfo clazz = annotation.target().asClass(); shortNamesToQualifiedNames.put(clazz.name().withoutPackagePrefix(), clazz.name().toString()); + AnnotationValue configKeyValue = annotation.value("configKey"); + String configKey = configKeyValue != null ? configKeyValue.asString() : null; + knownConfigKeys.add((configKey != null && !configKey.equals("")) ? configKey : clazz.name().toString()); + } + + for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT)) { + String configKey = annotation.value().asString(); + if (configKey == null) { + configKey = "default"; + } + knownConfigKeys.add(configKey); } - RuntimeValue support = recorder.clientSupport(shortNamesToQualifiedNames); + RuntimeValue support = recorder.clientSupport(shortNamesToQualifiedNames, knownConfigKeys); DotName supportClassName = DotName.createSimple(GraphQLClientSupport.class.getName()); SyntheticBeanBuildItem bean = SyntheticBeanBuildItem diff --git a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientMissingUrlTest.java b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientMissingUrlTest.java deleted file mode 100644 index c0eb75ccd635e..0000000000000 --- a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/DynamicGraphQLClientMissingUrlTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.quarkus.smallrye.graphql.client.deployment; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -import javax.enterprise.inject.Instance; -import javax.inject.Inject; - -import org.jboss.shrinkwrap.api.asset.EmptyAsset; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.test.QuarkusUnitTest; -import io.smallrye.graphql.client.GraphQLClient; -import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient; - -public class DynamicGraphQLClientMissingUrlTest { - - @RegisterExtension - static QuarkusUnitTest test = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); - - @Inject - @GraphQLClient("invalid") - Instance client; - - /** - * Check that when URL is not defined for a dynamic client, the thrown error message suggests the - * `quarkus.*` config property rather than the SmallRye property (`CLIENT_NAME/mp-graphql/url`). This - * is achieved by using `io.quarkus.smallrye.graphql.client.runtime.QuarkifiedErrorMessageProvider`. - */ - @Test - public void checkErrorMessage() { - try { - client.get(); - fail("Injection of a dynamic client must fail because no URL is defined"); - } catch (RuntimeException e) { - assertTrue(e.getMessage().contains("quarkus.smallrye-graphql-client.invalid.url")); - } - } - -} diff --git a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientMissingUrlTest.java b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientMissingUrlTest.java deleted file mode 100644 index c241d27f5c2c8..0000000000000 --- a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientMissingUrlTest.java +++ /dev/null @@ -1,43 +0,0 @@ -package io.quarkus.smallrye.graphql.client.deployment; - -import static org.junit.jupiter.api.Assertions.*; - -import javax.enterprise.inject.Instance; -import javax.inject.Inject; - -import org.jboss.shrinkwrap.api.asset.EmptyAsset; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import io.quarkus.smallrye.graphql.client.deployment.model.Person; -import io.quarkus.smallrye.graphql.client.deployment.model.TestingGraphQLApi; -import io.quarkus.smallrye.graphql.client.deployment.model.TestingGraphQLClientApi; -import io.quarkus.test.QuarkusUnitTest; - -public class TypesafeGraphQLClientMissingUrlTest { - - @RegisterExtension - static QuarkusUnitTest test = new QuarkusUnitTest() - .withApplicationRoot((jar) -> jar - .addClasses(TestingGraphQLApi.class, TestingGraphQLClientApi.class, Person.class) - .addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml")); - - @Inject - Instance client; - - /** - * Check that when URL is not defined for a typesafe client, the thrown error message suggests the - * `quarkus.*` config property rather than the SmallRye property (`CLIENT_NAME/mp-graphql/url`). This - * is achieved by using `io.quarkus.smallrye.graphql.client.runtime.QuarkifiedErrorMessageProvider`. - */ - @Test - public void checkErrorMessage() { - try { - client.get(); - fail("Injection of a typesafe client must fail because no URL is defined"); - } catch (RuntimeException e) { - assertTrue(e.getMessage().contains("quarkus.smallrye-graphql-client.typesafeclient.url")); - } - } - -} diff --git a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java index 79c6f9282c045..ebe1d544ef686 100644 --- a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java +++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java @@ -7,6 +7,11 @@ import javax.inject.Inject; import javax.inject.Singleton; +import org.eclipse.microprofile.config.Config; +import org.eclipse.microprofile.config.ConfigProvider; +import org.jboss.logging.Logger; + +import io.quarkus.runtime.LaunchMode; import io.smallrye.graphql.client.impl.GraphQLClientConfiguration; import io.smallrye.graphql.client.impl.GraphQLClientsConfiguration; @@ -21,6 +26,8 @@ @Singleton public class GraphQLClientConfigurationMergerBean { + private final Logger logger = Logger.getLogger(GraphQLClientConfigurationMergerBean.class); + GraphQLClientsConfiguration upstreamConfigs; @Inject @@ -54,6 +61,32 @@ void enhanceGraphQLConfiguration() { } } + // allow automatically wiring client to the local server instance in test mode + if (LaunchMode.current() == LaunchMode.TEST) { + String testUrl = null; + for (String configKey : support.getKnownConfigKeys()) { + GraphQLClientConfiguration config = upstreamConfigs.getClient(configKey); + if (config.getUrl() == null) { + if (testUrl == null) { + testUrl = getTestingServerUrl(); + } + logger.info("Automatically wiring the URL of GraphQL client named " + configKey + " to " + testUrl + + ". If this is incorrect, " + + "please set it manually using the quarkus.smallrye-graphql-client." + maybeWithQuotes(configKey) + + ".url property. Also note that" + + " this autowiring is only supported during tests."); + config.setUrl(testUrl); + } + } + } + } + + private String maybeWithQuotes(String key) { + if (key.contains(".")) { + return "\"" + key + "\""; + } else { + return key; + } } // translates a Quarkus `GraphQLClientConfig` configuration object to `GraphQLClientConfiguration` which is understood @@ -80,6 +113,13 @@ private GraphQLClientConfiguration toSmallRyeNativeConfiguration(GraphQLClientCo return transformed; } + private String getTestingServerUrl() { + Config config = ConfigProvider.getConfig(); + // the client extension doesn't have dependencies on neither the server extension nor quarkus-vertx-http, so guessing + // is somewhat limited + return "http://localhost:" + config.getOptionalValue("quarkus.http.test-port", int.class).orElse(8081) + "/graphql"; + } + public void nothing() { } } diff --git a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientSupport.java b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientSupport.java index 2055789e4973f..e2438d9ee3d32 100644 --- a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientSupport.java +++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientSupport.java @@ -1,5 +1,6 @@ package io.quarkus.smallrye.graphql.client.runtime; +import java.util.List; import java.util.Map; /** @@ -14,6 +15,13 @@ public class GraphQLClientSupport { */ private Map shortNamesToQualifiedNamesMapping; + /** + * All config keys of clients found in the application. The reason for having this is that in TEST mode, + * if a config key is used but doesn't have any associated configuration, we can automatically generate a configuration + * containing a guess of the target URL. + */ + private List knownConfigKeys; + public Map getShortNamesToQualifiedNamesMapping() { return shortNamesToQualifiedNamesMapping; } @@ -21,4 +29,12 @@ public Map getShortNamesToQualifiedNamesMapping() { public void setShortNamesToQualifiedNamesMapping(Map shortNamesToQualifiedNamesMapping) { this.shortNamesToQualifiedNamesMapping = shortNamesToQualifiedNamesMapping; } + + public void setKnownConfigKeys(List knownConfigKeys) { + this.knownConfigKeys = knownConfigKeys; + } + + public List getKnownConfigKeys() { + return knownConfigKeys; + } } diff --git a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java index 2204c616d1dac..210a103da7a5d 100644 --- a/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java +++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/SmallRyeGraphQLClientRecorder.java @@ -34,9 +34,11 @@ public void setTypesafeApiClasses(List apiClassNames) { configBean.addTypesafeClientApis(classes); } - public RuntimeValue clientSupport(Map shortNamesToQualifiedNames) { + public RuntimeValue clientSupport(Map shortNamesToQualifiedNames, + List knownConfigKeys) { GraphQLClientSupport support = new GraphQLClientSupport(); support.setShortNamesToQualifiedNamesMapping(shortNamesToQualifiedNames); + support.setKnownConfigKeys(knownConfigKeys); return new RuntimeValue<>(support); } diff --git a/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/GraphQLClientTester.java b/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/GraphQLClientTester.java index 7e099d4aff2b8..d0969e6423c63 100644 --- a/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/GraphQLClientTester.java +++ b/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/GraphQLClientTester.java @@ -10,10 +10,13 @@ import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; +import javax.enterprise.inject.Instance; +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; +import io.smallrye.graphql.client.GraphQLClient; import io.smallrye.graphql.client.Response; import io.smallrye.graphql.client.core.Document; import io.smallrye.graphql.client.core.OperationType; @@ -148,4 +151,22 @@ public void dynamicSubscription(@PathParam("url") String url) throws Exception { } } + @GraphQLClient("some-key") + Instance autowiredDynamicClient; + + @GET + @Path("/autowired-dynamic") + public void autowiredDynamicClient() throws ExecutionException, InterruptedException { + testSingleResultOperationsWithDynamicClient(autowiredDynamicClient.get()); + } + + @Inject + Instance autowiredTypesafeClient; + + @GET + @Path("/autowired-typesafe") + public void autowiredTypesafeClient() throws Exception { + testSingleResultOperationsWithTypesafeClient(autowiredTypesafeClient.get()); + } + } diff --git a/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/DynamicClientTest.java b/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/DynamicClientTest.java index 24d86f42cc070..9a7127587e87c 100644 --- a/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/DynamicClientTest.java +++ b/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/DynamicClientTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.DisabledOnIntegrationTest; import io.quarkus.test.junit.QuarkusTest; /** @@ -45,4 +46,14 @@ public void testDynamicClientSubscription() throws Exception { .statusCode(204); } + @Test + @DisabledOnIntegrationTest(forArtifactTypes = DisabledOnIntegrationTest.ArtifactType.NATIVE_BINARY) + public void testDynamicClientAutowiredUrl() throws Exception { + when() + .get("/autowired-dynamic/") + .then() + .log().everything() + .statusCode(204); + } + } diff --git a/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/TypesafeClientTest.java b/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/TypesafeClientTest.java index 601420b62e26d..d312551c32a3f 100644 --- a/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/TypesafeClientTest.java +++ b/integration-tests/smallrye-graphql-client/src/test/java/io/quarkus/it/smallrye/graphql/client/TypesafeClientTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Test; import io.quarkus.test.common.http.TestHTTPResource; +import io.quarkus.test.junit.DisabledOnIntegrationTest; import io.quarkus.test.junit.QuarkusTest; /** @@ -60,4 +61,14 @@ public void testHeader() { .statusCode(204); } + @DisabledOnIntegrationTest(forArtifactTypes = DisabledOnIntegrationTest.ArtifactType.NATIVE_BINARY) + @Test + public void testAutowiredUrl() throws Exception { + when() + .get("/autowired-typesafe/") + .then() + .log().everything() + .statusCode(204); + } + }