diff --git a/bom/application/pom.xml b/bom/application/pom.xml
index f90dc199072f0..64ff94449185c 100644
--- a/bom/application/pom.xml
+++ b/bom/application/pom.xml
@@ -46,7 +46,7 @@
3.1.1
3.0.1
2.1.7
- 1.2.5
+ 1.2.7
2.0.0
5.1.0
3.2.0
diff --git a/docs/src/main/asciidoc/smallrye-graphql-client.adoc b/docs/src/main/asciidoc/smallrye-graphql-client.adoc
index 3df8571a01254..348cd5e39f430 100644
--- a/docs/src/main/asciidoc/smallrye-graphql-client.adoc
+++ b/docs/src/main/asciidoc/smallrye-graphql-client.adoc
@@ -235,7 +235,7 @@ specify that within the `@GraphQLClientApi` annotation (by setting the `endpoint
or move this over to the configuration file, `application.properties`:
----
-star-wars-typesafe/mp-graphql/url=https://swapi-graphql.netlify.app/.netlify/functions/index
+quarkus.smallrye-graphql-client.star-wars-typesafe.url=https://swapi-graphql.netlify.app/.netlify/functions/index
----
`star-wars-typesafe` is the name of the configured client instance, and corresponds to the `configKey`
@@ -274,7 +274,7 @@ representations of the GraphQL types and documents. The client API interface is
We still need to configure the URL for the client, so let's put this into `application.properties`:
----
-star-wars-dynamic/mp-graphql/url=https://swapi-graphql.netlify.app/.netlify/functions/index
+quarkus.smallrye-graphql-client.star-wars-dynamic.url=https://swapi-graphql.netlify.app/.netlify/functions/index
----
We decided to name the client `star-wars-dynamic`. We will use this name when injecting a dynamic client
@@ -283,7 +283,7 @@ to properly qualify the injection point.
If you need to add an authorization header, or any other custom HTTP header (in our case
it's not required), this can be done by:
----
-star-wars-dynamic/mp-graphql/header/HEADER-KEY=HEADER-VALUE"
+quarkus.smallrye-graphql-client.star-wars-dynamic.header.HEADER-KEY=HEADER-VALUE"
----
Add this to the `StarWarsResource` created earlier:
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 79c64801bd4a9..1fe495e2d5913 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
@@ -3,7 +3,9 @@
import static io.quarkus.deployment.annotations.ExecutionTime.RUNTIME_INIT;
import static io.quarkus.deployment.annotations.ExecutionTime.STATIC_INIT;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import javax.inject.Singleton;
@@ -17,6 +19,7 @@
import io.quarkus.arc.deployment.AdditionalBeanBuildItem;
import io.quarkus.arc.deployment.AutoInjectAnnotationBuildItem;
import io.quarkus.arc.deployment.BeanArchiveIndexBuildItem;
+import io.quarkus.arc.deployment.BeanContainerBuildItem;
import io.quarkus.arc.deployment.SyntheticBeanBuildItem;
import io.quarkus.deployment.Feature;
import io.quarkus.deployment.annotations.BuildProducer;
@@ -27,6 +30,9 @@
import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ReflectiveHierarchyBuildItem;
import io.quarkus.deployment.builditem.nativeimage.ServiceProviderBuildItem;
+import io.quarkus.runtime.RuntimeValue;
+import io.quarkus.smallrye.graphql.client.runtime.GraphQLClientConfigurationMergerBean;
+import io.quarkus.smallrye.graphql.client.runtime.GraphQLClientSupport;
import io.quarkus.smallrye.graphql.client.runtime.GraphQLClientsConfig;
import io.quarkus.smallrye.graphql.client.runtime.SmallRyeGraphQLClientRecorder;
@@ -109,21 +115,67 @@ void initializeTypesafeClient(BeanArchiveIndexBuildItem index,
reflectiveClass.produce(ReflectiveClassBuildItem.builder("java.util.Collection").methods(true).build());
}
+ /**
+ * io.smallrye.graphql.client.GraphQLClientsConfiguration bean requires knowledge of all interfaces annotated
+ * with `@GraphQLClientApi`
+ */
+ @BuildStep
+ @Record(STATIC_INIT)
+ void setTypesafeApiClasses(BeanArchiveIndexBuildItem index,
+ BeanContainerBuildItem beanContainerBuildItem,
+ SmallRyeGraphQLClientRecorder recorder) {
+ List apiClassNames = new ArrayList<>();
+ for (AnnotationInstance annotation : index.getIndex().getAnnotations(GRAPHQL_CLIENT_API)) {
+ ClassInfo apiClassInfo = annotation.target().asClass();
+ apiClassNames.add(apiClassInfo.name().toString());
+ }
+ recorder.setTypesafeApiClasses(apiClassNames);
+ }
+
+ /**
+ * Allows the optional usage of short class names in GraphQL client configuration rather than
+ * fully qualified names. This method computes a mapping between short names and qualified names,
+ * and the configuration merger bean will take it into account when merging Quarkus configuration
+ * with SmallRye-side configuration.
+ */
@BuildStep
@Record(RUNTIME_INIT)
- void translateClientConfiguration(BeanArchiveIndexBuildItem index,
+ void shortNamesToQualifiedNames(BuildProducer syntheticBeans,
SmallRyeGraphQLClientRecorder recorder,
- GraphQLClientsConfig config) {
- // Map with all classes annotated with @GraphQLApi, the key is its short name,
- // value is the fully qualified name. The reason is being able to match short name
- // used in the configuration to a class
+ GraphQLClientsConfig quarkusConfig,
+ BeanArchiveIndexBuildItem index) {
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());
}
- recorder.translateClientConfiguration(config, shortNamesToQualifiedNames);
+ RuntimeValue support = recorder.clientSupport(shortNamesToQualifiedNames);
+
+ DotName supportClassName = DotName.createSimple(GraphQLClientSupport.class.getName());
+ SyntheticBeanBuildItem bean = SyntheticBeanBuildItem
+ .configure(supportClassName)
+ .addType(supportClassName)
+ .scope(Singleton.class)
+ .runtimeValue(support)
+ .setRuntimeInit()
+ .unremovable()
+ .done();
+ syntheticBeans.produce(bean);
+ }
+
+ @BuildStep
+ AdditionalBeanBuildItem configurationMergerBean() {
+ return AdditionalBeanBuildItem.unremovableOf(GraphQLClientConfigurationMergerBean.class);
+ }
+
+ // FIXME: this seems unnecessary, but is needed to make sure that the GraphQLClientConfigurationMergerBean
+ // gets initialized, can this be done differently?
+ @BuildStep
+ @Record(RUNTIME_INIT)
+ void initializeConfigMergerBean(BeanContainerBuildItem containerBuildItem,
+ SmallRyeGraphQLClientRecorder recorder) {
+ recorder.initializeConfigurationMergerBean();
}
}
diff --git a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionTest.java b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionTest.java
index 24359f315dea5..a132d5b327656 100644
--- a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionTest.java
+++ b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionTest.java
@@ -27,10 +27,8 @@ public class TypesafeGraphQLClientInjectionTest {
static QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(TestingGraphQLApi.class, TestingGraphQLClientApi.class, Person.class)
- .addAsResource(new StringAsset("typesafeclient/mp-graphql/url=" + url),
- // TODO: adding headers via config is not supported by typesafe client yet
- // + "\n" +
- // "typesafeclient/mp-graphql/header/My-Header=My-Value"),
+ .addAsResource(new StringAsset("typesafeclient/mp-graphql/url=" + url + "\n" +
+ "typesafeclient/mp-graphql/header/My-Header=My-Value"),
"application.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"));
@@ -44,10 +42,14 @@ public void performQuery() {
assertEquals("Arthur", people.get(1).getFirstName());
}
- // TODO: adding headers via config is not supported by typesafe client yet
- // @Test
- // public void checkHeaders() throws ExecutionException, InterruptedException {
- // assertEquals("My-Value", client.returnHeader("My-Header"));
- // }
+ /**
+ * Verify that configured HTTP headers are applied by the client.
+ * We do this by asking the server side to read the header received from the client and send
+ * its value back to the client.
+ */
+ @Test
+ public void checkHeaders() {
+ assertEquals("My-Value", client.returnHeader("My-Header"));
+ }
}
diff --git a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionWithQuarkusConfigTest.java b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionWithQuarkusConfigTest.java
index f7dca13874515..982786578d94c 100644
--- a/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionWithQuarkusConfigTest.java
+++ b/extensions/smallrye-graphql-client/deployment/src/test/java/io/quarkus/smallrye/graphql/client/deployment/TypesafeGraphQLClientInjectionWithQuarkusConfigTest.java
@@ -27,10 +27,8 @@ public class TypesafeGraphQLClientInjectionWithQuarkusConfigTest {
static QuarkusUnitTest test = new QuarkusUnitTest()
.setArchiveProducer(() -> ShrinkWrap.create(JavaArchive.class)
.addClasses(TestingGraphQLApi.class, TestingGraphQLClientApi.class, Person.class)
- .addAsResource(new StringAsset("quarkus.smallrye-graphql-client.typesafeclient.url=" + url),
- // TODO: adding headers via config is not supported by typesafe client yet
- // + "\n" +
- // "quarkus.smallrye-graphql-client.typesafeclient.header.My-Header=My-Value"),
+ .addAsResource(new StringAsset("quarkus.smallrye-graphql-client.typesafeclient.url=" + url + "\n" +
+ "quarkus.smallrye-graphql-client.typesafeclient.header.My-Header=My-Value"),
"application.properties")
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"));
@@ -44,10 +42,14 @@ public void performQuery() {
assertEquals("Arthur", people.get(1).getFirstName());
}
- // TODO: adding headers via config is not supported by typesafe client yet
- // @Test
- // public void checkHeaders() throws ExecutionException, InterruptedException {
- // assertEquals("My-Value", client.returnHeader("My-Header"));
- // }
+ /**
+ * Verify that configured HTTP headers are applied by the client.
+ * We do this by asking the server side to read the header received from the client and send
+ * its value back to the client.
+ */
+ @Test
+ public void checkHeaders() {
+ assertEquals("My-Value", client.returnHeader("My-Header"));
+ }
}
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
new file mode 100644
index 0000000000000..f90f741f5a80a
--- /dev/null
+++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientConfigurationMergerBean.java
@@ -0,0 +1,68 @@
+package io.quarkus.smallrye.graphql.client.runtime;
+
+import java.util.Map;
+
+import javax.annotation.PostConstruct;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import io.smallrye.graphql.client.GraphQLClientConfiguration;
+import io.smallrye.graphql.client.GraphQLClientsConfiguration;
+
+/**
+ * On startup, this beans takes Quarkus-specific configuration of GraphQL clients (quarkus.* properties)
+ * and merges this configuration with the configuration parsed by SmallRye GraphQL itself (CLIENT/mp-graphql/* properties)
+ *
+ * The resulting merged configuration resides in the application-scoped `io.smallrye.graphql.client.GraphQLClientConfiguration`
+ *
+ * Quarkus configuration overrides SmallRye configuration where applicable.
+ */
+@Singleton
+public class GraphQLClientConfigurationMergerBean {
+
+ @Inject
+ GraphQLClientsConfiguration upstreamConfiguration;
+
+ @Inject
+ GraphQLClientsConfig quarkusConfiguration;
+
+ @Inject
+ GraphQLClientSupport support;
+
+ @PostConstruct
+ void enhanceGraphQLConfiguration() {
+ for (Map.Entry client : quarkusConfiguration.clients.entrySet()) {
+ // the raw config key provided in the config, this might be a short class name,
+ // so translate that into the fully qualified name if applicable
+ String rawConfigKey = client.getKey();
+ Map shortNamesToQualifiedNamesMapping = support.getShortNamesToQualifiedNamesMapping();
+ String configKey = shortNamesToQualifiedNamesMapping != null &&
+ shortNamesToQualifiedNamesMapping.containsKey(rawConfigKey)
+ ? shortNamesToQualifiedNamesMapping.get(rawConfigKey)
+ : rawConfigKey;
+
+ GraphQLClientConfig quarkusConfig = client.getValue();
+ // if SmallRye configuration does not contain this client, simply use it
+ if (!upstreamConfiguration.getClients().containsKey(configKey)) {
+ GraphQLClientConfiguration transformed = new GraphQLClientConfiguration();
+ transformed.setHeaders(quarkusConfig.headers);
+ transformed.setUrl(quarkusConfig.url);
+ upstreamConfiguration.getClients().put(configKey, transformed);
+ } else {
+ // if SmallRye configuration already contains this client, override it with the Quarkus configuration
+ GraphQLClientConfiguration upstreamConfig = upstreamConfiguration.getClients().get(configKey);
+ if (quarkusConfig.url != null) {
+ upstreamConfig.setUrl(quarkusConfig.url);
+ }
+ // merge the headers
+ if (quarkusConfig.headers != null) {
+ upstreamConfig.getHeaders().putAll(quarkusConfig.headers);
+ }
+ }
+ }
+
+ }
+
+ 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
new file mode 100644
index 0000000000000..2055789e4973f
--- /dev/null
+++ b/extensions/smallrye-graphql-client/runtime/src/main/java/io/quarkus/smallrye/graphql/client/runtime/GraphQLClientSupport.java
@@ -0,0 +1,24 @@
+package io.quarkus.smallrye.graphql.client.runtime;
+
+import java.util.Map;
+
+/**
+ * Items from build time needed to make available at runtime.
+ */
+public class GraphQLClientSupport {
+
+ /**
+ * Allows the optional usage of short class names in GraphQL client configuration rather than
+ * fully qualified names. The configuration merger bean will take it into account
+ * when merging Quarkus configuration with SmallRye-side configuration.
+ */
+ private Map shortNamesToQualifiedNamesMapping;
+
+ public Map getShortNamesToQualifiedNamesMapping() {
+ return shortNamesToQualifiedNamesMapping;
+ }
+
+ public void setShortNamesToQualifiedNamesMapping(Map shortNamesToQualifiedNamesMapping) {
+ this.shortNamesToQualifiedNamesMapping = shortNamesToQualifiedNamesMapping;
+ }
+}
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 4d9f20f24e792..c9a16d643631e 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
@@ -1,9 +1,14 @@
package io.quarkus.smallrye.graphql.client.runtime;
+import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
+import java.util.stream.Collectors;
+import io.quarkus.arc.Arc;
+import io.quarkus.runtime.RuntimeValue;
import io.quarkus.runtime.annotations.Recorder;
+import io.smallrye.graphql.client.GraphQLClientsConfiguration;
import io.smallrye.graphql.client.typesafe.api.TypesafeGraphQLClientBuilder;
@Recorder
@@ -16,21 +21,28 @@ public Supplier typesafeClientSupplier(Class targetClassName) {
};
}
- /**
- * Translates quarkus.* configuration properties to system properties understood by SmallRye GraphQL.
- */
- public void translateClientConfiguration(GraphQLClientsConfig clientsConfig,
- Map shortNamesToQualifiedNames) {
- for (Map.Entry client : clientsConfig.clients.entrySet()) {
- String configKey = client.getKey();
- // the config key can be a short class name, in which case we try to translate it to a fully qualified name
- // and use the FQ name in the set property, because that's what SmallRye GraphQL understands
- String clientName = shortNamesToQualifiedNames.getOrDefault(configKey, configKey);
- GraphQLClientConfig config = client.getValue();
- System.setProperty(clientName + "/mp-graphql/url", config.url);
- for (Map.Entry header : config.headers.entrySet()) {
- System.setProperty(clientName + "/mp-graphql/header/" + header.getKey(), header.getValue());
+ public void setTypesafeApiClasses(List apiClassNames) {
+ GraphQLClientsConfiguration configBean = Arc.container().instance(GraphQLClientsConfiguration.class).get();
+ List> classes = apiClassNames.stream().map(className -> {
+ try {
+ return Class.forName(className, true, Thread.currentThread().getContextClassLoader());
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException(e);
}
- }
+ }).collect(Collectors.toList());
+ configBean.apiClasses(classes);
}
+
+ public RuntimeValue clientSupport(Map shortNamesToQualifiedNames) {
+ GraphQLClientSupport support = new GraphQLClientSupport();
+ support.setShortNamesToQualifiedNamesMapping(shortNamesToQualifiedNames);
+ return new RuntimeValue<>(support);
+ }
+
+ public void initializeConfigurationMergerBean() {
+ GraphQLClientConfigurationMergerBean merger = Arc.container()
+ .instance(GraphQLClientConfigurationMergerBean.class).get();
+ merger.nothing();
+ }
+
}
diff --git a/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersClientApi.java b/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersClientApi.java
index 5589ee327b3a6..bf9ad68d47d1c 100644
--- a/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersClientApi.java
+++ b/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersClientApi.java
@@ -1,6 +1,9 @@
package io.quarkus.io.smallrye.graphql.client;
+import java.util.List;
+
import org.eclipse.microprofile.graphql.Mutation;
+import org.eclipse.microprofile.graphql.NonNull;
import org.eclipse.microprofile.graphql.Query;
import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
@@ -13,4 +16,7 @@ public interface LuckyNumbersClientApi {
@Mutation(value = "set")
Integer setLuckyNumber(Integer newLuckyNumber);
+
+ @Query(value = "echoList")
+ List echoList(@NonNull List list);
}
diff --git a/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersResource.java b/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersResource.java
index 7d01ab7534e84..492d3305a5213 100644
--- a/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersResource.java
+++ b/integration-tests/smallrye-graphql-client/src/main/java/io/quarkus/io/smallrye/graphql/client/LuckyNumbersResource.java
@@ -1,9 +1,12 @@
package io.quarkus.io.smallrye.graphql.client;
+import java.util.List;
+
import javax.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.graphql.GraphQLApi;
import org.eclipse.microprofile.graphql.Mutation;
+import org.eclipse.microprofile.graphql.NonNull;
import org.eclipse.microprofile.graphql.Query;
import io.smallrye.graphql.api.Subscription;
@@ -31,4 +34,9 @@ public Multi primeNumbers() {
return Multi.createFrom().items(2, 3, 5, 7, 11, 13);
}
+ @Query(value = "echoList")
+ public List echoList(@NonNull List list) {
+ return list;
+ }
+
}
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 0b600d16e3fa0..b31034e7202f7 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
@@ -3,6 +3,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.net.URL;
+import java.util.List;
import org.junit.jupiter.api.Test;
@@ -25,4 +26,17 @@ public void testTypesafeClient() throws Exception {
client.setLuckyNumber(21);
assertEquals(21, client.luckyNumber());
}
+
+ /**
+ * Test a method that contains a `@NonNull` parameter.
+ */
+ @Test
+ public void testNonNull() throws Exception {
+ LuckyNumbersClientApi client = TypesafeGraphQLClientBuilder.newBuilder()
+ .endpoint(url.toString() + "/graphql")
+ .build(LuckyNumbersClientApi.class);
+ List someNumbers = List.of(12, 33);
+ List returned = client.echoList(someNumbers);
+ assertEquals(someNumbers, returned);
+ }
}