diff --git a/.github/project.yml b/.github/project.yml
index 3f3ffe844..24246efc7 100644
--- a/.github/project.yml
+++ b/.github/project.yml
@@ -1,4 +1,4 @@
name: SmallRye GraphQL
release:
- current-version: 2.8.1
- next-version: 2.8.2-SNAPSHOT
+ current-version: 2.9.1
+ next-version: 2.9.2-SNAPSHOT
diff --git a/.github/release/maven-settings.xml.gpg b/.github/release/maven-settings.xml.gpg
deleted file mode 100644
index d25987610..000000000
Binary files a/.github/release/maven-settings.xml.gpg and /dev/null differ
diff --git a/.github/release/smallrye-sign.asc.gpg b/.github/release/smallrye-sign.asc.gpg
deleted file mode 100644
index ffd7b0f07..000000000
Binary files a/.github/release/smallrye-sign.asc.gpg and /dev/null differ
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index bab32ebb1..df8ecd023 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -96,8 +96,8 @@ jobs:
- name: checkout Quarkus repository
uses: actions/checkout@v2
with:
- repository: quarkusio/quarkus
- ref: main
+ repository: mskacelik/quarkus
+ ref: srgql-2.9.1
path: quarkus
- uses: actions/setup-java@v3.10.0
@@ -120,7 +120,7 @@ jobs:
cd quarkus && \
mvn install -Dsmallrye-graphql.version=$SMALLRYE_VERSION -Dquickly && \
mvn verify -Dsmallrye-graphql.version=$SMALLRYE_VERSION -Dnative \
- -pl extensions/smallrye-graphql-client/deployment,extensions/smallrye-graphql-client/runtime,extensions/smallrye-graphql/deployment,extensions/smallrye-graphql/runtime,integration-tests/smallrye-graphql,integration-tests/smallrye-graphql-client,integration-tests/hibernate-orm-graphql-panache,extensions/oidc-client-graphql/deployment,extensions/oidc-client-graphql/runtime
+ -pl extensions/smallrye-graphql-client/deployment,extensions/smallrye-graphql-client/runtime,extensions/smallrye-graphql/deployment,extensions/smallrye-graphql/runtime,integration-tests/smallrye-graphql,integration-tests/smallrye-graphql-client,integration-tests/hibernate-orm-graphql-panache,extensions/oidc-client-graphql/deployment,extensions/oidc-client-graphql/runtime,tcks/microprofile-graphql -Ptcks
quality:
needs: [build]
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 99198173d..d0528e6fa 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -29,16 +29,22 @@ jobs:
- uses: actions/setup-java@v1.4.3
with:
java-version: 11
+ server-id: 'oss.sonatype'
+ server-username: 'MAVEN_DEPLOY_USERNAME'
+ server-password: 'MAVEN_DEPLOY_TOKEN'
+ gpg-private-key: ${{secrets.MAVEN_GPG_PRIVATE_KEY}}
+ gpg-passphrase: 'MAVEN_GPG_PASSPHRASE'
- name: Install graphviz
run: sudo apt install graphviz
- name: maven release ${{steps.metadata.outputs.current-version}}
+ env:
+ MAVEN_DEPLOY_USERNAME: ${{secrets.MAVEN_DEPLOY_USERNAME}}
+ MAVEN_DEPLOY_TOKEN: ${{secrets.MAVEN_DEPLOY_TOKEN}}
+ MAVEN_GPG_PASSPHRASE: ${{secrets.MAVEN_GPG_PASSPHRASE}}
run: |
java -version
- gpg --quiet --batch --yes --decrypt --passphrase="${{secrets.SECRET_PASSPHRASE}}" --output smallrye-sign.asc .github/release/smallrye-sign.asc.gpg
- gpg --quiet --batch --yes --decrypt --passphrase="${{secrets.SECRET_PASSPHRASE}}" --output maven-settings.xml .github/release/maven-settings.xml.gpg
- gpg --fast-import --no-tty --batch --yes smallrye-sign.asc
git config --global user.name "SmallRye CI"
git config --global user.email "smallrye@googlegroups.com"
git checkout -b release
@@ -47,10 +53,10 @@ jobs:
git add tools/gradle-plugin/gradle.properties
git commit -m "Update Gradle plugin version"
# make sure the server/integration-tests-jdk16 gets its version updated too: include the jdk16plus profile
- mvn -X -e -B release:prepare -Prelease -Pjdk16plus -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}} -s maven-settings.xml
+ mvn -X -e -B release:prepare -Prelease -Pjdk16plus -DreleaseVersion=${{steps.metadata.outputs.current-version}} -DdevelopmentVersion=${{steps.metadata.outputs.next-version}}
git checkout ${{github.base_ref}}
git rebase release
- mvn -X -e -B release:perform -Prelease -Pjdk16plus -s maven-settings.xml
+ mvn -X -e -B release:perform -Prelease -Pjdk16plus
git push
git push --tags
diff --git a/README.adoc b/README.adoc
index 8ea2ae909..c49434ca0 100644
--- a/README.adoc
+++ b/README.adoc
@@ -3,6 +3,7 @@
:subscriptions-transport-ws: https://github.com/apollographql/subscriptions-transport-ws
:graphql-ws: https://github.com/enisdenjo/graphql-ws/blob/master/PROTOCOL.md
:graphql-federation: https://www.apollographql.com/docs/federation
+:vertx: https://vertx.io/
image:https://github.com/smallrye/smallrye-graphql/workflows/SmallRye%20Build/badge.svg?branch=main[link=https://github.com/smallrye/smallrye-graphql/actions?query=workflow%3A%22SmallRye+Build%22]
image:https://sonarcloud.io/api/project_badges/measure?project=smallrye_smallrye-graphql&metric=alert_status["Quality Gate Status", link="https://sonarcloud.io/dashboard?id=smallrye_smallrye-graphql"]
@@ -62,17 +63,22 @@ The 1.5.x branch will be maintained for the `javax` namespace, and the main (2.x
* link:server/api[API] (pulling in the MicroProfile API) and allowing us to experiment with api feature not yet in the spec. Code from here might move the the spec at some point.
* link:server/implementation[Implementation] of the Eclipse MicroProfile GraphQL API.
-* link:server/implementation-cdi[CDI] Module that allows lookup of GraphQL Endpoints via CDI
-* link:server/implementation-servlet[Servlet] Making the implementation available via Servlet
+* link:server/implementation-cdi[CDI] Module that allows lookup of GraphQL Endpoints via CDI.
+* link:server/implementation-servlet[Servlet] Making the implementation available via Servlet.
* link:server/tck[TCK] Test suite to run the implementation against the {microprofile-graphql}[Eclipse MicroProfile GraphQL] TCK.
-* link:server/runner[Runner] Manual TCK testing with GraphiQL
-* link:server/integration-tests[IT] To run some Integration tests
+* link:server/runner[Runner] Manual TCK testing with GraphiQL.
+* link:server/integration-tests[IT] To run some Integration tests.
==== Client
-* link:client/api[Client API] (pulling in the MicroProfile Client API) and allowing us to experiment with api feature not yet in the spec. Code from here might move the the spec at some point.
+* link:client/api[Client API] (pulling in the MicroProfile Client API) and allowing us to experiment with API feature not yet in the spec. Code from here might move the spec at some point.
* link:client/implementation[Client Implementation] of the Eclipse MicroProfile GraphQL Client API.
* link:client/tck[Client TCK] Test suite to run the client-implementation against the {microprofile-graphql}[Eclipse MicroProfile GraphQL] Client TCK.
+* link:client/implementation-vertx[Client Implementation Vert.x] Module for {vertx}[Vert.x] handling for the client.
+* link:client/generator[Client Generator] Module generating Type-Safe Client APIs with annotation processing.
+* link:client/generator-test[Client Generator Test] Module containing tests for client generators.
+* link:client/model[Client Model] defines Type-Safe's client model (GraphQL operations).
+* link:client/model-builder[Client Model Builder] that generates Client Model from Jandex.
==== Tools
diff --git a/client/api/pom.xml b/client/api/pom.xml
index a6b1525ab..9b3429195 100644
--- a/client/api/pom.xml
+++ b/client/api/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-api
@@ -30,18 +30,12 @@
microprofile-config-api
provided
-
io.smallrye.reactive
mutiny
provided
-
- io.smallrye
- smallrye-graphql-client-model
-
-
org.junit.jupiter
junit-jupiter
diff --git a/client/api/src/main/java/io/smallrye/graphql/client/core/utils/validation/NameValidation.java b/client/api/src/main/java/io/smallrye/graphql/client/core/utils/validation/NameValidation.java
index 0c5747f6c..aff32c1c7 100644
--- a/client/api/src/main/java/io/smallrye/graphql/client/core/utils/validation/NameValidation.java
+++ b/client/api/src/main/java/io/smallrye/graphql/client/core/utils/validation/NameValidation.java
@@ -12,7 +12,8 @@ public class NameValidation {
/**
* The regular expression patterns for a valid GraphQL names.
*/
- private static final String _NAME_REGEX = "[a-zA-Z_][a-zA-Z0-9_]*";
+ private static final String _IGNORED_TOKENS_REGEX = "[,\\s]*"; // for now, whitespaces and commas
+ private static final String _NAME_REGEX = _IGNORED_TOKENS_REGEX + "[a-zA-Z_][a-zA-Z0-9_]*" + _IGNORED_TOKENS_REGEX;
private static final String _FIELD_NAME_REGEX = "^" + _NAME_REGEX + "(:" + _NAME_REGEX + ")?$";
private static final Pattern NAME_PATTERN = Pattern.compile(_NAME_REGEX);
private static final Pattern FIELD_NAME_PATTERN = Pattern.compile(_FIELD_NAME_REGEX);
diff --git a/client/generator-test/pom.xml b/client/generator-test/pom.xml
index 39fb5bbd8..ebb5c2d95 100644
--- a/client/generator-test/pom.xml
+++ b/client/generator-test/pom.xml
@@ -4,7 +4,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-generator-test
diff --git a/client/generator/pom.xml b/client/generator/pom.xml
index acd3fd384..e51bd337e 100644
--- a/client/generator/pom.xml
+++ b/client/generator/pom.xml
@@ -4,7 +4,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-generator
diff --git a/client/implementation-vertx/pom.xml b/client/implementation-vertx/pom.xml
index e543ac90f..d243619ed 100644
--- a/client/implementation-vertx/pom.xml
+++ b/client/implementation-vertx/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-implementation-vertx
@@ -18,7 +18,7 @@
io.smallrye
- smallrye-graphql-client-model-builder
+ smallrye-graphql-client-model
io.smallrye.config
@@ -29,6 +29,16 @@
microprofile-config-api
provided
+
+ jakarta.json
+ jakarta.json-api
+ provided
+
+
+ jakarta.json.bind
+ jakarta.json.bind-api
+ provided
+
@@ -50,5 +60,15 @@
mockito-junit-jupiter
test
+
+ io.smallrye
+ smallrye-graphql-client-model-builder
+ test
+
+
+ org.eclipse
+ yasson
+ test
+
diff --git a/client/implementation-vertx/src/test/java/test/tck/TypesafeTckClientModelSuite.java b/client/implementation-vertx/src/test/java/test/tck/TypesafeTckClientModelSuite.java
index 010ae825a..4957dda6d 100644
--- a/client/implementation-vertx/src/test/java/test/tck/TypesafeTckClientModelSuite.java
+++ b/client/implementation-vertx/src/test/java/test/tck/TypesafeTckClientModelSuite.java
@@ -17,7 +17,6 @@
import org.junit.jupiter.api.Test;
import org.junit.platform.suite.api.ExcludeClassNamePatterns;
-import io.smallrye.graphql.client.GraphQLClient;
import io.smallrye.graphql.client.model.ClientModelBuilder;
import io.smallrye.graphql.client.model.ClientModels;
import tck.graphql.typesafe.Animal;
@@ -87,7 +86,6 @@ private static Index createIndexExcludingClasses(File directory, String classNam
// SOME OTHER CLASSES TO BE ADDED TO INDEX
try {
indexer.indexClass(Input.class);
- indexer.indexClass(GraphQLClient.class);
indexer.indexClass(Closeable.class);
indexer.indexClass(AutoCloseable.class);
} catch (IOException e) {
diff --git a/client/implementation/pom.xml b/client/implementation/pom.xml
index 6a2765d59..d3da8003e 100644
--- a/client/implementation/pom.xml
+++ b/client/implementation/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client
@@ -56,6 +56,11 @@
jakarta.json-api
provided
+
+ jakarta.json.bind
+ jakarta.json.bind-api
+ provided
+
io.smallrye.config
smallrye-config
@@ -66,18 +71,14 @@
jakarta.enterprise.cdi-api
-
- jakarta.json
- jakarta.json-api
-
-
- org.eclipse
- yasson
-
io.smallrye.reactive
mutiny
+
+ io.smallrye
+ smallrye-graphql-client-model
+
@@ -118,5 +119,10 @@
mockito-junit-jupiter
test
+
+ org.eclipse
+ yasson
+ test
+
diff --git a/client/implementation/src/main/java/io/smallrye/graphql/client/impl/RequestImpl.java b/client/implementation/src/main/java/io/smallrye/graphql/client/impl/RequestImpl.java
index 4085634a1..f9cddb323 100644
--- a/client/implementation/src/main/java/io/smallrye/graphql/client/impl/RequestImpl.java
+++ b/client/implementation/src/main/java/io/smallrye/graphql/client/impl/RequestImpl.java
@@ -1,5 +1,6 @@
package io.smallrye.graphql.client.impl;
+import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
@@ -8,18 +9,15 @@
import jakarta.json.JsonBuilderFactory;
import jakarta.json.JsonObject;
import jakarta.json.JsonObjectBuilder;
-import jakarta.json.JsonStructure;
import jakarta.json.JsonValue;
import jakarta.json.bind.Jsonb;
import jakarta.json.bind.JsonbBuilder;
-import org.eclipse.yasson.internal.JsonBinding;
-
import io.smallrye.graphql.client.Request;
public class RequestImpl implements Request {
-
- private static final JsonBuilderFactory jsonBuilderFactory = Json.createBuilderFactory(null);
+ private static final JsonBuilderFactory JSON = Json.createBuilderFactory(null);
+ private static final Jsonb JSONB = JsonbBuilder.create();
private final String document;
private Map variables;
@@ -32,7 +30,7 @@ public RequestImpl(String document) {
@Override
public String toJson() {
- JsonObjectBuilder queryBuilder = jsonBuilderFactory.createObjectBuilder().add("query", document);
+ JsonObjectBuilder queryBuilder = JSON.createObjectBuilder().add("query", document);
if (!variables.isEmpty()) {
queryBuilder.add("variables", _formatJsonVariables());
}
@@ -44,7 +42,7 @@ public String toJson() {
@Override
public JsonObject toJsonObject() {
- JsonObjectBuilder queryBuilder = jsonBuilderFactory.createObjectBuilder().add("query", document);
+ JsonObjectBuilder queryBuilder = JSON.createObjectBuilder().add("query", document);
if (!variables.isEmpty()) {
queryBuilder.add("variables", _formatJsonVariables());
}
@@ -55,7 +53,7 @@ public JsonObject toJsonObject() {
}
private JsonObject _formatJsonVariables() {
- JsonObjectBuilder varBuilder = jsonBuilderFactory.createObjectBuilder();
+ JsonObjectBuilder varBuilder = JSON.createObjectBuilder();
variables.forEach((k, v) -> {
// Other types to process here
@@ -69,14 +67,12 @@ private JsonObject _formatJsonVariables() {
varBuilder.add(k, (Boolean) v);
} else if (v instanceof Long) {
varBuilder.add(k, (Long) v);
+ } else if (v instanceof Double) {
+ varBuilder.add(k, (Double) v);
} else if (v == null) {
varBuilder.addNull(k);
} else {
- try (Jsonb jsonb = JsonbBuilder.create()) {
- JsonStructure struct = ((JsonBinding) jsonb).toJsonStructure(v);
- varBuilder.add(k, struct);
- } catch (Exception ignore) {
- }
+ varBuilder.add(k, Json.createReader(new StringReader(JSONB.toJson(v))).read());
}
});
diff --git a/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java b/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java
index b5ec2ef80..b4e2f8485 100644
--- a/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java
+++ b/client/implementation/src/main/java/io/smallrye/graphql/client/impl/typesafe/reflection/MethodInvocation.java
@@ -54,7 +54,7 @@ public String getKey() {
}
public MethodKey getMethodKey() {
- return new MethodKey(method.getReturnType(), method.getName(), method.getParameterTypes());
+ return new MethodKey(method.getName(), method.getParameterTypes());
}
public OperationType getOperationType() {
diff --git a/client/implementation/src/test/java/io/smallrye/graphql/client/RequestImplTest.java b/client/implementation/src/test/java/io/smallrye/graphql/client/RequestImplTest.java
index 71b0cd443..6500ce896 100644
--- a/client/implementation/src/test/java/io/smallrye/graphql/client/RequestImplTest.java
+++ b/client/implementation/src/test/java/io/smallrye/graphql/client/RequestImplTest.java
@@ -29,6 +29,12 @@ public void testPrimitiveTypesToJson() {
request.setVariable("key", "foo");
assertEquals("{\"query\":\"example\",\"variables\":{\"key\":\"foo\"}}", request.toJson());
+
+ request.setVariable("key", 5L);
+ assertEquals("{\"query\":\"example\",\"variables\":{\"key\":5}}", request.toJson());
+
+ request.setVariable("key", 5.5);
+ assertEquals("{\"query\":\"example\",\"variables\":{\"key\":5.5}}", request.toJson());
}
@Test
diff --git a/client/model-builder/pom.xml b/client/model-builder/pom.xml
index ab7e3a064..de2a4900b 100644
--- a/client/model-builder/pom.xml
+++ b/client/model-builder/pom.xml
@@ -4,7 +4,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-model-builder
@@ -26,6 +26,7 @@
jboss-logging
provided
+
io.smallrye
smallrye-graphql-client-api
@@ -38,16 +39,17 @@
io.smallrye.reactive
mutiny
-
- io.smallrye
- smallrye-graphql-client
-
org.junit.jupiter
junit-jupiter
test
+
+ io.smallrye
+ smallrye-graphql-api
+ test
+
diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/ClientModelBuilder.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/ClientModelBuilder.java
index f639d4c62..ea7731e2b 100644
--- a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/ClientModelBuilder.java
+++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/ClientModelBuilder.java
@@ -22,6 +22,8 @@
* Builder class for generating client models from Jandex index.
* It scans for classes annotated with {@link Annotations#GRAPHQL_CLIENT_API} and generates client models based on the annotated
* methods.
+ *
+ * @author mskacelik
*/
public class ClientModelBuilder {
@@ -60,9 +62,9 @@ private ClientModels generateClientModels() {
List methods = getAllMethodsIncludingFromSuperClasses(apiClass);
methods.stream().forEach(method -> {
String query = new QueryBuilder(method).build();
- LOG.debugf("[%s] - Query created: %s", apiClass.name().toString(), query);
+ LOG.debugf("[%s] – Query created: %s", apiClass.name().toString(), query);
operationMap.getOperationMap()
- .put(OperationModel.of(method).getMethodKey(), query);
+ .putIfAbsent(OperationModel.of(method).getMethodKey(), query);
});
clientModelMap.put(
(graphQLApiAnnotation.value("configKey") == null) ? apiClass.name().toString()
diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/QueryBuilder.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/QueryBuilder.java
index a7bb86edf..791e27dc6 100644
--- a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/QueryBuilder.java
+++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/QueryBuilder.java
@@ -11,6 +11,8 @@
/**
* A utility class for building GraphQL queries based on a given {@link MethodInfo} which will be scanned thanks
* to Jandex during build-time.
+ *
+ * @author mskacelik
*/
public class QueryBuilder {
private final OperationModel method;
diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/OperationModel.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/OperationModel.java
index c9793ede2..4add9f429 100644
--- a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/OperationModel.java
+++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/OperationModel.java
@@ -21,12 +21,13 @@
import org.jboss.jandex.MethodInfo;
import io.smallrye.graphql.client.core.OperationType;
-import io.smallrye.graphql.client.impl.SmallRyeGraphQLClientMessages;
import io.smallrye.graphql.client.model.MethodKey;
/**
* Represents a model for a GraphQL operation method, providing methods to generate GraphQL query fields,
* handle parameter bindings, and extract operation-related information.
+ *
+ * @author mskacelik
*/
public class OperationModel implements NamedElement {
private final MethodInfo method;
@@ -69,7 +70,7 @@ public static OperationModel of(MethodInfo method) {
*/
public String fields(TypeModel type) {
if (typeStack.contains(type.getName()))
- throw SmallRyeGraphQLClientMessages.msg.fieldRecursionFound();
+ throw new IllegalStateException("field recursion found");
try {
typeStack.push(type.getName());
return recursionCheckedFields(type);
@@ -222,11 +223,10 @@ public Optional mutationName() {
}
/**
- * Gets the name of the GraphQL subscription, considering any {@link io.smallrye.graphql.api.Subscription} annotation.
+ * Gets the name of the GraphQL subscription, considering any io.smallrye.graphql.api.Subscription annotation.
*
* @return An optional containing the subscription name if specified, otherwise empty.
*/
-
public Optional subscriptionName() {
Optional subscriptionAnnotation = getMethodAnnotation(SUBCRIPTION);
if (subscriptionAnnotation.isPresent() && subscriptionAnnotation.orElseThrow().value() != null)
@@ -343,10 +343,10 @@ public boolean isSingle() {
/**
* Gets the key for identifying the GraphQL operation method.
*
- * @return The {@link MethodKey} representing the key for the operation method (return type, name, parameters types).
+ * @return The {@link MethodKey} representing the key for the operation method (name, parameters types).
*/
public MethodKey getMethodKey() {
- return new MethodKey(JandexReflection.loadRawType(method.returnType()), method.name(), method.parameters().stream()
+ return new MethodKey(method.name(), method.parameters().stream()
.map(methodParameterInfo -> JandexReflection.loadRawType(methodParameterInfo.type())).toArray(Class>[]::new));
}
diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/ParameterModel.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/ParameterModel.java
index 2b53a7f94..be3b24204 100644
--- a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/ParameterModel.java
+++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/ParameterModel.java
@@ -27,6 +27,8 @@ public class ParameterModel implements NamedElement {
private TypeModel type;
private List directives;
+ private final static String PARAMETER_NAME_PLACEHOLDER = "arg";
+
/**
* Constructs a new {@code ParameterModel} instance based on the provided Jandex {@link MethodParameterInfo}.
*
@@ -107,7 +109,8 @@ public String getName() {
}
public String getRawName() {
- return parameter.name();
+ String rawName = parameter.name();
+ return (rawName != null) ? rawName : PARAMETER_NAME_PLACEHOLDER + parameter.position();
}
@Override
diff --git a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/TypeModel.java b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/TypeModel.java
index 2de3a784f..93bdd0c80 100644
--- a/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/TypeModel.java
+++ b/client/model-builder/src/main/java/io/smallrye/graphql/client/model/helper/TypeModel.java
@@ -7,9 +7,7 @@
import static io.smallrye.graphql.client.model.Classes.OBJECT;
import static io.smallrye.graphql.client.model.Classes.OPTIONAL;
import static io.smallrye.graphql.client.model.Classes.TYPESAFE_RESPONSE;
-import static io.smallrye.graphql.client.model.Classes.isParameterized;
import static io.smallrye.graphql.client.model.ScanningContext.getIndex;
-import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import java.lang.reflect.Modifier;
@@ -384,7 +382,7 @@ public Stream fields() {
* @return A stream of {@link FieldModel} instances.
*/
private Stream fields(ClassInfo clazz) {
- return (clazz == null) ? Stream.of()
+ return (clazz == null || clazz.superClassType() == null) ? Stream.of()
: Stream.concat(
fields(getIndex().getClassByName(clazz.superClassType().name())), // to superClass
fieldsHelper(clazz)
diff --git a/client/model-builder/src/test/java/io/smallrye/graphql/client/model/ClientModelBuilderTest.java b/client/model-builder/src/test/java/io/smallrye/graphql/client/model/ClientModelBuilderTest.java
index fbbd65a7f..5b04a8a3d 100644
--- a/client/model-builder/src/test/java/io/smallrye/graphql/client/model/ClientModelBuilderTest.java
+++ b/client/model-builder/src/test/java/io/smallrye/graphql/client/model/ClientModelBuilderTest.java
@@ -19,6 +19,8 @@
import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
/**
+ * Testing query building using the client model implementation.
+ *
* @author mskacelik
*/
public class ClientModelBuilderTest {
@@ -43,15 +45,15 @@ void sclarClientModelTest() throws IOException {
ClientModel clientModel = clientModels.getClientModelByConfigKey(configKey);
assertEquals(3, clientModel.getOperationMap().size());
assertOperation(clientModel,
- new MethodKey(Integer.class, "returnInteger", new Class>[] { Integer.class }),
+ new MethodKey("returnInteger", new Class>[] { Integer.class }),
"query returnInteger($someNumber: Int) { returnInteger(someNumber: $someNumber) }");
assertOperation(clientModel,
- new MethodKey(String.class, "returnString", new Class>[] { String.class }),
+ new MethodKey("returnString", new Class>[] { String.class }),
"mutation returnString($someString: String) { returnString(someString: $someString) }");
assertOperation(clientModel,
- new MethodKey(float.class, "returnNonNullFloat", new Class>[] { float.class }),
+ new MethodKey("returnNonNullFloat", new Class>[] { float.class }),
"subscription returnNonNullFloat($someFloat: Float!) { returnNonNullFloat(someFloat: $someFloat) }");
}
@@ -75,15 +77,15 @@ void collectionClientModelTest() throws IOException {
ClientModel clientModel = clientModels.getClientModelByConfigKey(configKey);
assertEquals(3, clientModel.getOperationMap().size());
assertOperation(clientModel,
- new MethodKey(Collection.class, "returnIntegerCollection", new Class>[] { Integer[].class }),
+ new MethodKey("returnIntegerCollection", new Class>[] { Integer[].class }),
"query returnIntegerCollection($someNumbers: [Int]) { returnIntegerCollection(someNumbers: $someNumbers) }");
assertOperation(clientModel,
- new MethodKey(List.class, "returnStringList", new Class>[] { Set.class }),
+ new MethodKey("returnStringList", new Class>[] { Set.class }),
"mutation returnStringList($someStrings: [String]) { returnStringList(someStrings: $someStrings) }");
assertOperation(clientModel,
- new MethodKey(ArrayList.class, "returnFloatArrayList", new Class>[] { HashSet.class }),
+ new MethodKey("returnFloatArrayList", new Class>[] { HashSet.class }),
"subscription returnFloatArrayList($someFloats: [Float]) { returnFloatArrayList(someFloats: $someFloats) }");
}
@@ -96,7 +98,7 @@ void simpleObjectClientModelTest() throws IOException {
ClientModel clientModel = clientModels.getClientModelByConfigKey(configKey);
assertEquals(1, clientModel.getOperationMap().size());
assertOperation(clientModel,
- new MethodKey(SimpleObjectClientApi.SomeObject.class, "returnSomeObject",
+ new MethodKey("returnSomeObject",
new Class>[] { SimpleObjectClientApi.SomeObject.class }),
"query returnSomeObject($someObject: SomeObjectInput) { returnSomeObject(someObject: $someObject) {name innerObject {someInt}} }");
}
@@ -130,7 +132,7 @@ void complexObjectClientModelTest() throws IOException {
ClientModel clientModel = clientModels.getClientModelByConfigKey(configKey);
assertEquals(1, clientModel.getOperationMap().size());
assertOperation(clientModel,
- new MethodKey(List.class, "returnSomeGenericObject", new Class>[] { List.class }),
+ new MethodKey("returnSomeGenericObject", new Class>[] { List.class }),
"query returnSomeGenericObject($someObject: [SomeGenericClassInput])" +
" { returnSomeGenericObject(someObject: $someObject) {somethingParent {number}" +
" field1 {number} field2 {innerField {number} something {someObject {someThingElse" +
@@ -174,6 +176,40 @@ class SomeObjectFive {
}
}
+ @GraphQLClientApi(configKey = "string-api")
+ interface StringApiChild extends StringApiParent {
+ @Query("strings")
+ List allStrings();
+
+ List allStrings0();
+
+ }
+
+ interface StringApiParent {
+ List allStrings();
+
+ List allStrings2();
+ }
+
+ @Test
+ void inheritedOperationsClientModelTest() throws IOException {
+ String configKey = "string-api";
+ ClientModels clientModels = ClientModelBuilder
+ .build(Index.of(StringApiChild.class, StringApiParent.class));
+ assertNotNull(clientModels.getClientModelByConfigKey(configKey));
+ ClientModel clientModel = clientModels.getClientModelByConfigKey(configKey);
+ assertEquals(3, clientModel.getOperationMap().size());
+ assertOperation(clientModel,
+ new MethodKey("allStrings", new Class[0]),
+ "query strings { strings }");
+ assertOperation(clientModel,
+ new MethodKey("allStrings2", new Class[0]),
+ "query allStrings2 { allStrings2 }");
+ assertOperation(clientModel,
+ new MethodKey("allStrings0", new Class[0]),
+ "query allStrings0 { allStrings0 }");
+ }
+
private void assertOperation(ClientModel clientModel, MethodKey methodKey, String expectedQuery) {
String actualQuery = clientModel.getOperationMap().get(methodKey);
assertEquals(expectedQuery, actualQuery);
diff --git a/client/model/pom.xml b/client/model/pom.xml
index 1c0bc5b96..62e646d51 100644
--- a/client/model/pom.xml
+++ b/client/model/pom.xml
@@ -4,7 +4,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-model
diff --git a/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModel.java b/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModel.java
index de8a49886..fb485c539 100644
--- a/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModel.java
+++ b/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModel.java
@@ -3,6 +3,12 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * Represents a model object used by Quarkus during both build-time and runtime.
+ * This class encapsulates all of the operation queries for a single client API.
+ *
+ * @author mskacelik
+ */
public class ClientModel {
private Map operationQueryMap;
@@ -11,6 +17,7 @@ public ClientModel() {
operationQueryMap = new HashMap<>();
};
+ // bytecode recording
public Map getOperationMap() {
return operationQueryMap;
}
@@ -19,6 +26,7 @@ public void setOperationMap(Map operationQueryMap) {
this.operationQueryMap = operationQueryMap;
}
+ // for testing purposes...
@Override
public String toString() {
return "ClientModel{" +
diff --git a/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModels.java b/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModels.java
index 5430fa33f..35a13a30b 100644
--- a/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModels.java
+++ b/client/model/src/main/java/io/smallrye/graphql/client/model/ClientModels.java
@@ -3,6 +3,13 @@
import java.util.HashMap;
import java.util.Map;
+/**
+ * Represents an object for all `ClientModel` (based on their configuration keys)
+ * instances within Quarkus application.
+ *
+ * @author mskacelik
+ */
+
public class ClientModels {
private Map clientModelMap;
@@ -10,11 +17,11 @@ public ClientModel getClientModelByConfigKey(String configKey) {
return clientModelMap.get(configKey);
}
- // bytecode recording
public ClientModels() {
clientModelMap = new HashMap<>();
}
+ // bytecode recording
public void setClientModelMap(Map clientModelMap) {
this.clientModelMap = clientModelMap;
}
diff --git a/client/model/src/main/java/io/smallrye/graphql/client/model/MethodKey.java b/client/model/src/main/java/io/smallrye/graphql/client/model/MethodKey.java
index 3dbc06c20..b420c202c 100644
--- a/client/model/src/main/java/io/smallrye/graphql/client/model/MethodKey.java
+++ b/client/model/src/main/java/io/smallrye/graphql/client/model/MethodKey.java
@@ -3,13 +3,18 @@
import java.util.Arrays;
import java.util.Objects;
+/**
+ * Represents a unique identifier for an Client API operation (method). This class
+ * combines the method name and its parameter types to create a key that can
+ * be identified by both Jandex and Java reflection.
+ *
+ * @author mskacelik
+ */
public class MethodKey {
- private Class> declaringClass;
private String methodName;
private Class>[] parameterTypes;
- public MethodKey(Class> declaringClass, String methodName, Class>[] parameterTypes) {
- this.declaringClass = declaringClass;
+ public MethodKey(String methodName, Class>[] parameterTypes) {
this.methodName = methodName;
this.parameterTypes = parameterTypes;
}
@@ -24,25 +29,17 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass())
return false;
MethodKey methodKey = (MethodKey) o;
- return Objects.equals(declaringClass, methodKey.declaringClass) && Objects.equals(methodName, methodKey.methodName)
- && Arrays.equals(parameterTypes, methodKey.parameterTypes);
+ return Objects.equals(methodName, methodKey.methodName) && Arrays.equals(parameterTypes, methodKey.parameterTypes);
}
@Override
public int hashCode() {
- int result = Objects.hash(declaringClass, methodName);
+ int result = Objects.hash(methodName);
result = 31 * result + Arrays.hashCode(parameterTypes);
return result;
}
- public Class> getDeclaringClass() {
- return declaringClass;
- }
-
- public void setDeclaringClass(Class> declaringClass) {
- this.declaringClass = declaringClass;
- }
-
+ // set methods for bytecode recording
public String getMethodName() {
return methodName;
}
@@ -62,8 +59,7 @@ public void setParameterTypes(Class>[] parameterTypes) {
@Override
public String toString() {
return "MethodKey{" +
- "declaringClass=" + declaringClass +
- ", methodName='" + methodName + '\'' +
+ "methodName='" + methodName + '\'' +
", parameterTypes=" + Arrays.toString(parameterTypes) +
'}';
}
diff --git a/client/pom.xml b/client/pom.xml
index 50bab80fe..66f3f0bb4 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-parent
diff --git a/client/tck/pom.xml b/client/tck/pom.xml
index 77ab94267..b4220a7ec 100644
--- a/client/tck/pom.xml
+++ b/client/tck/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-client-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-client-tck
@@ -39,6 +39,14 @@
jakarta.enterprise
jakarta.enterprise.cdi-api
+
+ jakarta.json
+ jakarta.json-api
+
+
+ jakarta.json.bind
+ jakarta.json.bind-api
+
com.graphql-java
@@ -79,5 +87,10 @@
io.smallrye
smallrye-graphql-client
+
+ org.eclipse
+ yasson
+ compile
+
diff --git a/client/tck/src/main/java/tck/graphql/dynamic/core/FieldsTest.java b/client/tck/src/main/java/tck/graphql/dynamic/core/FieldsTest.java
index cf987d021..dd0d57b29 100644
--- a/client/tck/src/main/java/tck/graphql/dynamic/core/FieldsTest.java
+++ b/client/tck/src/main/java/tck/graphql/dynamic/core/FieldsTest.java
@@ -58,6 +58,11 @@ public void fieldShouldNotThrowExceptionForValidNameTest() {
assertDoesNotThrow(() -> field("_:v1"));
assertDoesNotThrow(() -> field("___"));
assertDoesNotThrow(() -> field("o"));
+ assertDoesNotThrow(() -> field(" valid_name "));
+ assertDoesNotThrow(() -> field("o1: b"));
+ assertDoesNotThrow(() -> field("o12 ,,, : ,,, bbbee1"));
+ assertDoesNotThrow(() -> field(",,,,valid_name,,,,"));
+ assertDoesNotThrow(() -> field("foo:\tbar"));
}
@Test
@@ -66,7 +71,6 @@ public void fieldShouldThrowExceptionForInvalidNameTest() {
assertThrows(IllegalArgumentException.class, () -> field(""));
assertThrows(IllegalArgumentException.class, () -> field("invalid_nam&e"));
assertThrows(IllegalArgumentException.class, () -> field("1invalid_name"));
- assertThrows(IllegalArgumentException.class, () -> field(" invalid_name"));
assertThrows(IllegalArgumentException.class, () -> field(":invalid_name"));
assertThrows(IllegalArgumentException.class, () -> field("invalid name"));
assertThrows(IllegalArgumentException.class, () -> field("invalid_name:"));
@@ -74,5 +78,7 @@ public void fieldShouldThrowExceptionForInvalidNameTest() {
assertThrows(IllegalArgumentException.class, () -> field("a:1"));
assertThrows(IllegalArgumentException.class, () -> field("invalid::name"));
assertThrows(IllegalArgumentException.class, () -> field("field:name:name2"));
+ assertThrows(IllegalArgumentException.class, () -> field(" invalid,name"));
+ assertThrows(IllegalArgumentException.class, () -> field("invalid\tname"));
}
}
diff --git a/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentReferencesTest.java b/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentReferencesTest.java
index 0c23fe069..ed59834be 100644
--- a/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentReferencesTest.java
+++ b/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentReferencesTest.java
@@ -20,6 +20,7 @@ public void fragmentReferencesShouldNotThrowExceptionForValidName() {
assertDoesNotThrow(() -> fragmentRef("_"));
assertDoesNotThrow(() -> fragmentRef("frag_ment"));
assertDoesNotThrow(() -> fragmentRef("one"));
+ assertDoesNotThrow(() -> fragmentRef(" two,, ,"));
}
@Test
@@ -33,5 +34,6 @@ public void fragmentReferencesShouldThrowExceptionForInvalidName() {
assertThrows(IllegalArgumentException.class, () -> fragmentRef("in:valid"));
assertThrows(IllegalArgumentException.class, () -> fragmentRef("inv@lid"));
assertThrows(IllegalArgumentException.class, () -> fragmentRef("on"));
+ assertThrows(IllegalArgumentException.class, () -> fragmentRef(" invalid_frag,ment"));
}
}
diff --git a/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentsTest.java b/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentsTest.java
index 8cd3f6048..ae20b3c17 100644
--- a/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentsTest.java
+++ b/client/tck/src/main/java/tck/graphql/dynamic/core/FragmentsTest.java
@@ -46,6 +46,7 @@ public void fragmentsShouldNotThrowExceptionForValidNameTest() {
assertDoesNotThrow(() -> fragment("_"));
assertDoesNotThrow(() -> fragment("frag_ment"));
assertDoesNotThrow(() -> fragment("one"));
+ assertDoesNotThrow(() -> fragment(",,,two "));
}
@Test
@@ -60,5 +61,6 @@ public void fragmentsShouldThrowExceptionForInvalidNameTest() {
assertThrows(IllegalArgumentException.class, () -> fragment("...fragmentinvalid"));
assertThrows(IllegalArgumentException.class, () -> fragment("inv@lid"));
assertThrows(IllegalArgumentException.class, () -> fragment("on"));
+ assertThrows(IllegalArgumentException.class, () -> fragment("frag,ment"));
}
}
diff --git a/client/tck/src/main/java/tck/graphql/dynamic/core/OperationsTest.java b/client/tck/src/main/java/tck/graphql/dynamic/core/OperationsTest.java
index 7899c587b..f09f320b3 100644
--- a/client/tck/src/main/java/tck/graphql/dynamic/core/OperationsTest.java
+++ b/client/tck/src/main/java/tck/graphql/dynamic/core/OperationsTest.java
@@ -16,6 +16,7 @@ public void operationsShouldNotThrowExceptionForValidNameTest() {
assertDoesNotThrow(() -> operation("_myOperation"));
assertDoesNotThrow(() -> operation("my_operation"));
assertDoesNotThrow(() -> operation("my123Operation"));
+ assertDoesNotThrow(() -> operation(",, ,myOperation ,"));
assertDoesNotThrow(() -> operation("o"));
assertDoesNotThrow(() -> operation("_"));
assertDoesNotThrow(() -> operation("op_eration"));
@@ -35,6 +36,7 @@ public void operationsShouldThrowExceptionForInvalidNameTest() {
assertThrows(IllegalArgumentException.class, () -> operation("InvalidName::"));
assertThrows(IllegalArgumentException.class, () -> operation("@InvalidName"));
assertThrows(IllegalArgumentException.class, () -> operation("my.Operation"));
+ assertThrows(IllegalArgumentException.class, () -> operation("my,Operation"));
assertThrows(IllegalArgumentException.class, () -> operation("my-Operation"));
}
}
diff --git a/client/tck/src/main/java/tck/graphql/dynamic/core/VariableTypesTest.java b/client/tck/src/main/java/tck/graphql/dynamic/core/VariableTypesTest.java
index 894b6b6bc..76b2f9310 100644
--- a/client/tck/src/main/java/tck/graphql/dynamic/core/VariableTypesTest.java
+++ b/client/tck/src/main/java/tck/graphql/dynamic/core/VariableTypesTest.java
@@ -18,12 +18,14 @@ public void varTypesShouldNotThrowExceptionForValidName() {
assertDoesNotThrow(() -> varType("v"));
assertDoesNotThrow(() -> varType("_"));
assertDoesNotThrow(() -> varType("va_lid"));
+ assertDoesNotThrow(() -> varType(" ,va_lid,, "));
assertDoesNotThrow(() -> varType("_valid"));
}
@Test
public void varTypesShouldThrowExceptionForInvalidName() {
assertThrows(IllegalArgumentException.class, () -> varType("Invalid:Name"));
+ assertThrows(IllegalArgumentException.class, () -> varType("Invalid,Name"));
assertThrows(IllegalArgumentException.class, () -> varType("123InvalidName"));
assertThrows(IllegalArgumentException.class, () -> varType("invalid-Name"));
assertThrows(IllegalArgumentException.class, () -> varType("invalid name"));
@@ -39,12 +41,14 @@ public void nonNullsShouldNotThrowExceptionForValidName() {
assertDoesNotThrow(() -> nonNull("v"));
assertDoesNotThrow(() -> nonNull("_"));
assertDoesNotThrow(() -> nonNull("va_lid"));
+ assertDoesNotThrow(() -> nonNull(" ,va_lid,, "));
assertDoesNotThrow(() -> nonNull("_valid"));
}
@Test
public void nonNullsShouldThrowExceptionForInvalidName() {
assertThrows(IllegalArgumentException.class, () -> nonNull("Invalid:Name"));
+ assertThrows(IllegalArgumentException.class, () -> nonNull("Invalid,Name"));
assertThrows(IllegalArgumentException.class, () -> nonNull("123InvalidName"));
assertThrows(IllegalArgumentException.class, () -> nonNull("invalid-Name"));
assertThrows(IllegalArgumentException.class, () -> nonNull("invalid name"));
@@ -60,12 +64,14 @@ public void listsShouldNotThrowExceptionForValidName() {
assertDoesNotThrow(() -> list("v"));
assertDoesNotThrow(() -> list("_"));
assertDoesNotThrow(() -> list("va_lid"));
+ assertDoesNotThrow(() -> list(" ,va_lid,, "));
assertDoesNotThrow(() -> list("_valid"));
}
@Test
public void listsShouldThrowExceptionForInvalidName() {
assertThrows(IllegalArgumentException.class, () -> list("Invalid:Name"));
+ assertThrows(IllegalArgumentException.class, () -> list("Invalid,Name"));
assertThrows(IllegalArgumentException.class, () -> list("123InvalidName"));
assertThrows(IllegalArgumentException.class, () -> list("invalidName-"));
assertThrows(IllegalArgumentException.class, () -> list("invalid name"));
diff --git a/common/pom.xml b/common/pom.xml
index d5104a18e..032b6d736 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-common-parent
diff --git a/common/schema-builder/pom.xml b/common/schema-builder/pom.xml
index adc385a6b..f6c96bdd3 100644
--- a/common/schema-builder/pom.xml
+++ b/common/schema-builder/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-common-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-schema-builder
@@ -19,6 +19,10 @@
${project.groupId}
smallrye-graphql-api
+
+ com.graphql-java
+ graphql-java
+
diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java
index 5dbce5505..3123a0d64 100644
--- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java
+++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/Annotations.java
@@ -512,7 +512,6 @@ private static Map getTypeUseAnnotations(org.jboss.
private static Map getAnnotations(org.jboss.jandex.Type type) {
Map annotationMap = new HashMap<>();
-
if (type.kind().equals(org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE)) {
org.jboss.jandex.Type typeInCollection = type.asParameterizedType().arguments().get(0);
annotationMap.putAll(getAnnotations(typeInCollection));
@@ -522,7 +521,6 @@ private static Map getAnnotations(org.jboss.jandex.
annotationMap.put(annotationInstance.name(), annotationInstance);
}
}
-
return annotationMap;
}
diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/WrapperCreator.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/WrapperCreator.java
index 08743268d..2ad27f988 100644
--- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/WrapperCreator.java
+++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/WrapperCreator.java
@@ -86,18 +86,16 @@ private static boolean markParameterizedTypeNonNull(Type fieldType, Type methodT
Annotations annotationsInParameterizedType = Annotations.getAnnotationsForArray(typeInCollection,
methodTypeInCollection);
- return NonNullHelper.markAsNonNull(typeInCollection, annotationsInParameterizedType, true);
+ return NonNullHelper.markAsNonNull(typeInCollection, annotationsInParameterizedType, false);
}
return false;
}
private static Type getTypeInCollection(Type type) {
if (Classes.isArray(type)) {
- Type typeInArray = type.asArrayType().component();
- return getTypeInCollection(typeInArray);
+ return type.asArrayType().componentType();
} else if (Classes.isParameterized(type)) {
- Type typeInCollection = type.asParameterizedType().arguments().get(0);
- return getTypeInCollection(typeInCollection);
+ return type.asParameterizedType().arguments().get(0);
}
return type;
}
diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/AbstractCreator.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/AbstractCreator.java
index b4e6ccac7..fe4209572 100644
--- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/AbstractCreator.java
+++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/AbstractCreator.java
@@ -12,6 +12,7 @@
import io.smallrye.graphql.schema.Annotations;
import io.smallrye.graphql.schema.ScanningContext;
+import io.smallrye.graphql.schema.SchemaBuilderException;
import io.smallrye.graphql.schema.creator.OperationCreator;
import io.smallrye.graphql.schema.creator.ReferenceCreator;
import io.smallrye.graphql.schema.helper.DescriptionHelper;
@@ -112,8 +113,8 @@ protected void addOperations(Type type, ClassInfo classInfo) {
SourceOperationHelper sourceOperationHelper = new SourceOperationHelper();
Map> sourceFields = sourceOperationHelper.getSourceAnnotations();
Map> batchedFields = sourceOperationHelper.getSourceListAnnotations();
- type.setOperations(toOperations(sourceFields, type, classInfo));
- type.setBatchOperations(toOperations(batchedFields, type, classInfo));
+ type.setOperations(validateOperationNames(toOperations(sourceFields, type, classInfo), type));
+ type.setBatchOperations(validateOperationNames(toOperations(batchedFields, type, classInfo), type));
}
protected Map toOperations(Map> sourceFields, Type type,
@@ -152,4 +153,21 @@ protected Map toOperations(Map validateOperationNames(Map operations, Type type) {
+ operations.keySet().forEach(fieldName -> {
+ if (type.getFields().keySet().contains(fieldName)) {
+ throw new SchemaBuilderException(String.format("Type '%s' already contains field named '%s'" +
+ " so source field, with the same name, cannot be applied. You can resolve this conflict using @Ignore on the type's field.",
+ type.getName(),
+ fieldName));
+ }
+ });
+ return operations;
+ }
}
diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/TypeCreator.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/TypeCreator.java
index 46f36b86f..df97861cd 100644
--- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/TypeCreator.java
+++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/creator/type/TypeCreator.java
@@ -1,11 +1,6 @@
package io.smallrye.graphql.schema.creator.type;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
+import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -45,28 +40,34 @@ public TypeCreator(ReferenceCreator referenceCreator, FieldCreator fieldCreator,
@Override
protected void addFields(Type type, ClassInfo classInfo, Reference reference) {
// Fields
- List allMethods = new ArrayList<>();
+ List> allMethods = new LinkedList<>();
Map allFields = new HashMap<>();
// Find all methods and properties up the tree
for (ClassInfo c = classInfo; c != null; c = ScanningContext.getIndex().getClassByName(c.superName())) {
+ List listMethods = new ArrayList<>();
+
if (InterfaceCreator.canAddInterfaceIntoScheme(c.toString())) { // Not java objects
List classMethods = filterOutBridgeMethod(c.methods());
- allMethods.addAll(classMethods);
- allMethods.addAll(getAllInterfaceMethods(c, classMethods
+ listMethods.addAll(getAllInterfaceMethods(c, classMethods
.stream()
.map(MethodInfo::toString)
.collect(Collectors.toSet())));
+ listMethods.addAll(classMethods);
for (FieldInfo fieldInfo : c.fields()) {
allFields.putIfAbsent(fieldInfo.name(), fieldInfo);
}
}
+
+ allMethods.add(0, listMethods);
}
- for (MethodInfo methodInfo : allMethods) {
- if (MethodHelper.isPropertyMethod(Direction.OUT, methodInfo)) {
- String fieldName = MethodHelper.getPropertyName(Direction.OUT, methodInfo.name());
- FieldInfo fieldInfo = allFields.remove(fieldName);
- fieldCreator.createFieldForPojo(Direction.OUT, fieldInfo, methodInfo, reference).ifPresent(type::addField);
+ for (List listMethods : allMethods) {
+ for (MethodInfo methodInfo : listMethods) {
+ if (MethodHelper.isPropertyMethod(Direction.OUT, methodInfo)) {
+ String fieldName = MethodHelper.getPropertyName(Direction.OUT, methodInfo.name());
+ FieldInfo fieldInfo = allFields.remove(fieldName);
+ fieldCreator.createFieldForPojo(Direction.OUT, fieldInfo, methodInfo, reference).ifPresent(type::addField);
+ }
}
}
diff --git a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/helper/Directives.java b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/helper/Directives.java
index e8c43cf0b..aeeccbedd 100644
--- a/common/schema-builder/src/main/java/io/smallrye/graphql/schema/helper/Directives.java
+++ b/common/schema-builder/src/main/java/io/smallrye/graphql/schema/helper/Directives.java
@@ -18,10 +18,13 @@
import org.jboss.jandex.MethodInfo;
import org.jboss.logging.Logger;
+import graphql.language.StringValue;
+import io.smallrye.graphql.api.federation.Key;
import io.smallrye.graphql.api.federation.policy.Policy;
import io.smallrye.graphql.api.federation.requiresscopes.RequiresScopes;
import io.smallrye.graphql.schema.Annotations;
import io.smallrye.graphql.schema.ScanningContext;
+import io.smallrye.graphql.schema.SchemaBuilderException;
import io.smallrye.graphql.schema.model.DirectiveInstance;
import io.smallrye.graphql.schema.model.DirectiveType;
@@ -56,16 +59,14 @@ public List buildDirectiveInstances(Annotations annotations,
return directiveTypes.keySet().stream()
.flatMap(annotations::resolve)
.map(this::toDirectiveInstance)
- .filter(directiveInstance -> {
+ .peek(directiveInstance -> {
if (!directiveInstance.getType().getLocations().contains(directiveLocation)) {
- LOG.warnf(
+ throw new SchemaBuilderException(String.format(
"Directive instance: '%s' assigned to '%s' cannot be applied." +
" The directive is allowed on locations '%s' but on '%s'",
directiveInstance.getType().getClassName(), referenceName,
- directiveInstance.getType().getLocations(), directiveLocation);
- return false;
+ directiveInstance.getType().getLocations(), directiveLocation));
}
- return true;
})
.collect(toList());
}
@@ -82,7 +83,12 @@ private DirectiveInstance toDirectiveInstance(AnnotationInstance annotationInsta
// For both of these directives, we need to process the annotation values as nested arrays of strings
directiveInstance.setValue(annotationValueName, valueObjectNestedList(annotationValue));
} else {
- directiveInstance.setValue(annotationValueName, valueObject(annotationValue));
+ if (directiveType.getClassName().equals(Key.class.getName()) && annotationValueName.equals("fields")) {
+ directiveInstance.setValue(annotationValueName,
+ new StringValue((String) valueObject(annotationValue.asNested().value())));
+ } else {
+ directiveInstance.setValue(annotationValueName, valueObject(annotationValue));
+ }
}
}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/SchemaBuilderTest.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/SchemaBuilderTest.java
index 693ff0f28..a12a058cf 100644
--- a/common/schema-builder/src/test/java/io/smallrye/graphql/index/SchemaBuilderTest.java
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/SchemaBuilderTest.java
@@ -118,20 +118,81 @@ public void testConcurrentSchemaBuilding() throws Exception {
* Test a schema where two Java classes map to the same GraphQL type. Such schema should not be allowed to create.
*/
@Test
- public void testSchemaWithDuplicates() {
+ public void testSchemaWithTypeNameDuplicates() {
try {
Indexer indexer = new Indexer();
- indexDirectory(indexer, "io/smallrye/graphql/index/duplicates");
- indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/a");
- indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/b");
+ indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/typename");
+ indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/typename/a");
+ indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/typename/b");
IndexView index = indexer.complete();
SchemaBuilder.build(index);
Assertions.fail("Schema should not build when there are multiple classes mapped to the same type");
} catch (SchemaBuilderException e) {
// ok
+ assertEquals("Classes io.smallrye.graphql.index.duplicates.typename.a.Animal " +
+ "and io.smallrye.graphql.index.duplicates.typename.b.Animal map to the same GraphQL type " +
+ "'Animal', consider using the @Name annotation or a different naming strategy to " +
+ "distinguish between them",
+ e.getMessage());
}
}
+ @Test
+ public void testSchemaWithSourceFieldNameDuplicates() {
+ try {
+ Indexer indexer = new Indexer();
+ indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/source");
+ indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/source/sourcefield");
+ IndexView index = indexer.complete();
+ SchemaBuilder.build(index);
+ Assertions.fail("Schema should not build when there are both field and source field with the same defined name");
+ } catch (SchemaBuilderException e) {
+ // ok
+ assertEquals("Type 'SomeClass' already contains field named 'password' so source field, " +
+ "with the same name, cannot be applied. You can resolve this conflict using @Ignore on the type's field.",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSchemaWithBatchSourceFieldNameDuplicates() {
+ try {
+ Indexer indexer = new Indexer();
+ indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/source");
+ indexDirectory(indexer, "io/smallrye/graphql/index/duplicates/source/batch");
+ IndexView index = indexer.complete();
+ SchemaBuilder.build(index);
+ Assertions.fail("Schema should not build when there are both field and source field with the same defined name");
+ } catch (SchemaBuilderException e) {
+ // ok
+ assertEquals(
+ "Type 'SomeClass' already contains field named 'password' so source field, with the same name, cannot be applied. You can resolve this conflict using @Ignore on the type's field.",
+ e.getMessage());
+ }
+ }
+
+ @Test
+ public void testSchemaWithInheritFieldBySubtype() {
+ Indexer indexer = new Indexer();
+ indexDirectory(indexer, "io/smallrye/graphql/index/inherit/");
+
+ IndexView index = indexer.complete();
+ Schema schema = SchemaBuilder.build(index);
+ // Get Types
+ Map types = schema.getTypes();
+ Map interfaces = schema.getInterfaces();
+ Type containerType = types.get("ContainerType");
+ Field containerTypeField = containerType.getFields().get("inheritField");
+ Type containerInterface = interfaces.get("ContainerInterface");
+ Field containerInterfaceField = containerInterface.getFields().get("inheritField");
+
+ // Should be FieldType according to GraphQL Spec: https://spec.graphql.org/October2021/#sec-Objects.Type-Validation
+ assertEquals("io.smallrye.graphql.index.inherit.FieldType",
+ containerTypeField.getReference().getClassName());
+ assertEquals("io.smallrye.graphql.index.inherit.FieldInterface",
+ containerInterfaceField.getReference().getClassName());
+ }
+
@Test
public void testSchemaWithDirectives() throws IOException {
Indexer indexer = new Indexer();
@@ -155,7 +216,8 @@ public void testSchemaWithDirectives() throws IOException {
assertEquals("someDirective", someDirective.getName());
assertEquals(SomeDirective.class.getName(), someDirective.getClassName());
assertEquals(singleton("value"), someDirective.argumentNames());
- assertEquals(new HashSet<>(asList("INTERFACE", "FIELD_DEFINITION", "OBJECT")), someDirective.getLocations());
+ assertEquals(new HashSet<>(asList("INTERFACE", "FIELD_DEFINITION", "OBJECT", "INPUT_OBJECT", "INPUT_FIELD_DEFINITION")),
+ someDirective.getLocations());
// check directive instances on type
Type movie = schema.getTypes().get("Movie");
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/app/SomeDirective.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/app/SomeDirective.java
index e25826c0a..1695e268a 100644
--- a/common/schema-builder/src/test/java/io/smallrye/graphql/index/app/SomeDirective.java
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/app/SomeDirective.java
@@ -1,6 +1,8 @@
package io.smallrye.graphql.index.app;
import static io.smallrye.graphql.api.DirectiveLocation.FIELD_DEFINITION;
+import static io.smallrye.graphql.api.DirectiveLocation.INPUT_FIELD_DEFINITION;
+import static io.smallrye.graphql.api.DirectiveLocation.INPUT_OBJECT;
import static io.smallrye.graphql.api.DirectiveLocation.INTERFACE;
import static io.smallrye.graphql.api.DirectiveLocation.OBJECT;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@@ -9,7 +11,7 @@
import io.smallrye.graphql.api.Directive;
-@Directive(on = { OBJECT, INTERFACE, FIELD_DEFINITION })
+@Directive(on = { OBJECT, INTERFACE, FIELD_DEFINITION, INPUT_OBJECT, INPUT_FIELD_DEFINITION })
@Retention(RUNTIME)
public @interface SomeDirective {
String[] value();
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/ApiWithDuplicates.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/ApiWithDuplicates.java
deleted file mode 100644
index 4e95b788f..000000000
--- a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/ApiWithDuplicates.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package io.smallrye.graphql.index.duplicates;
-
-import org.eclipse.microprofile.graphql.GraphQLApi;
-import org.eclipse.microprofile.graphql.Query;
-
-@GraphQLApi
-public class ApiWithDuplicates {
-
- @Query
- public io.smallrye.graphql.index.duplicates.a.Animal queryA() {
- return null;
- }
-
- @Query
- public io.smallrye.graphql.index.duplicates.b.Animal queryB() {
- return null;
- }
-}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/SomeClass.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/SomeClass.java
new file mode 100644
index 000000000..36206151e
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/SomeClass.java
@@ -0,0 +1,30 @@
+package io.smallrye.graphql.index.duplicates.source;
+
+public class SomeClass {
+ private String name;
+ private String password;
+
+ public SomeClass() {
+ }
+
+ public SomeClass(String name, String password) {
+ this.name = name;
+ this.password = password;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getPassword() {
+ return password;
+ }
+
+ public void setPassword(String password) {
+ this.password = password;
+ }
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/batch/ApiWithBatchSourceFieldNameDuplicates.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/batch/ApiWithBatchSourceFieldNameDuplicates.java
new file mode 100644
index 000000000..249598fb7
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/batch/ApiWithBatchSourceFieldNameDuplicates.java
@@ -0,0 +1,23 @@
+package io.smallrye.graphql.index.duplicates.source.batch;
+
+import java.util.Collection;
+import java.util.List;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+import org.eclipse.microprofile.graphql.Source;
+
+import io.smallrye.graphql.index.duplicates.source.SomeClass;
+
+@GraphQLApi
+public class ApiWithBatchSourceFieldNameDuplicates {
+
+ public Collection getPassword(@Source Collection someClasses) {
+ return List.of("my new password 1", "my new password 2");
+ }
+
+ @Query
+ public Collection getSomeClass() {
+ return List.of(new SomeClass("hello1", "password1"), new SomeClass("hello2", "password2"));
+ }
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/sourcefield/ApiWithSourceFieldNameDuplicates.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/sourcefield/ApiWithSourceFieldNameDuplicates.java
new file mode 100644
index 000000000..7728af2c5
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/source/sourcefield/ApiWithSourceFieldNameDuplicates.java
@@ -0,0 +1,20 @@
+package io.smallrye.graphql.index.duplicates.source.sourcefield;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+import org.eclipse.microprofile.graphql.Source;
+
+import io.smallrye.graphql.index.duplicates.source.SomeClass;
+
+@GraphQLApi
+public class ApiWithSourceFieldNameDuplicates {
+
+ public String getPassword(@Source SomeClass someClass) {
+ return "new Password";
+ }
+
+ @Query
+ public SomeClass getSomeClass() {
+ return new SomeClass("hello", "password");
+ }
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/ApiWithTypeNameDuplicates.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/ApiWithTypeNameDuplicates.java
new file mode 100644
index 000000000..57b86248f
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/ApiWithTypeNameDuplicates.java
@@ -0,0 +1,18 @@
+package io.smallrye.graphql.index.duplicates.typename;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+
+@GraphQLApi
+public class ApiWithTypeNameDuplicates {
+
+ @Query
+ public io.smallrye.graphql.index.duplicates.typename.a.Animal queryA() {
+ return null;
+ }
+
+ @Query
+ public io.smallrye.graphql.index.duplicates.typename.b.Animal queryB() {
+ return null;
+ }
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/b/Animal.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/a/Animal.java
similarity index 76%
rename from common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/b/Animal.java
rename to common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/a/Animal.java
index c7f67c6eb..1ff16f9a6 100644
--- a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/b/Animal.java
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/a/Animal.java
@@ -1,4 +1,4 @@
-package io.smallrye.graphql.index.duplicates.b;
+package io.smallrye.graphql.index.duplicates.typename.a;
public class Animal {
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/a/Animal.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/b/Animal.java
similarity index 76%
rename from common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/a/Animal.java
rename to common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/b/Animal.java
index e15f71741..5a9a5a8ba 100644
--- a/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/a/Animal.java
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/duplicates/typename/b/Animal.java
@@ -1,4 +1,4 @@
-package io.smallrye.graphql.index.duplicates.a;
+package io.smallrye.graphql.index.duplicates.typename.b;
public class Animal {
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/ContainerInterface.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/ContainerInterface.java
new file mode 100644
index 000000000..0305f0b49
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/ContainerInterface.java
@@ -0,0 +1,5 @@
+package io.smallrye.graphql.index.inherit;
+
+public interface ContainerInterface {
+ FieldInterface getInheritField();
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/ContainerType.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/ContainerType.java
new file mode 100644
index 000000000..a418fe7c9
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/ContainerType.java
@@ -0,0 +1,8 @@
+package io.smallrye.graphql.index.inherit;
+
+public class ContainerType implements ContainerInterface {
+
+ public FieldType getInheritField() {
+ return new FieldType();
+ }
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/FieldInterface.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/FieldInterface.java
new file mode 100644
index 000000000..573debaa4
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/FieldInterface.java
@@ -0,0 +1,5 @@
+package io.smallrye.graphql.index.inherit;
+
+public interface FieldInterface {
+ String getVal();
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/FieldType.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/FieldType.java
new file mode 100644
index 000000000..57bdbae01
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/FieldType.java
@@ -0,0 +1,7 @@
+package io.smallrye.graphql.index.inherit;
+
+public class FieldType implements FieldInterface {
+ public String getVal() {
+ return "";
+ }
+}
diff --git a/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/InheritAPI.java b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/InheritAPI.java
new file mode 100644
index 000000000..d3cb21a94
--- /dev/null
+++ b/common/schema-builder/src/test/java/io/smallrye/graphql/index/inherit/InheritAPI.java
@@ -0,0 +1,12 @@
+package io.smallrye.graphql.index.inherit;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+
+@GraphQLApi
+public class InheritAPI {
+ @Query
+ public ContainerInterface getContainer() {
+ return null;
+ }
+}
diff --git a/common/schema-model/pom.xml b/common/schema-model/pom.xml
index 04900be50..fbea1478d 100644
--- a/common/schema-model/pom.xml
+++ b/common/schema-model/pom.xml
@@ -4,7 +4,7 @@
io.smallrye
smallrye-graphql-common-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-schema-model
diff --git a/docs/dynamic-client-usage.md b/docs/dynamic-client-usage.md
index 96fcb8d26..8def9a784 100644
--- a/docs/dynamic-client-usage.md
+++ b/docs/dynamic-client-usage.md
@@ -31,7 +31,7 @@ DynamicGraphQLClient client;
```
The above example assumes that configuration for the client is present in system properties. For a full list of
-supported configuration properties, see [Client configuration reference](/client_configuration)
+supported configuration properties, see [Client configuration reference](client_configuration.md)
The other way to build a client is programmatically using a builder:
diff --git a/docs/pom.xml b/docs/pom.xml
index d71f0a00b..efaf44d67 100644
--- a/docs/pom.xml
+++ b/docs/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-documentation
diff --git a/mkdocs.yml b/mkdocs.yml
index 7520e2998..8f27a0029 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -5,7 +5,7 @@ edit_uri: edit/main/docs/
nav:
- Overview: 'index.md'
- Server side features:
- - Customizing JSON deserializers: 'custom-json-deserializers.md'
+ - Customizing JSON (de)serializers: 'custom-json-serializers-deserializers.md'
- Directives: 'directives.md'
- Federation: 'federation.md'
- Custom error extensions: 'custom-error-extensions.md'
diff --git a/pom.xml b/pom.xml
index c06e8105b..1d2c65f95 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,11 +5,11 @@
io.smallrye
smallrye-parent
- 42
+ 43
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
pom
SmallRye: GraphQL Parent
@@ -37,7 +37,7 @@
6.0.0
2.0.0
4.4.0
- 21.3
+ 22.1
21.0
1.11.3
4.5.4
diff --git a/release/pom.xml b/release/pom.xml
index b61b13987..8c882471e 100644
--- a/release/pom.xml
+++ b/release/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-release
diff --git a/server/api/pom.xml b/server/api/pom.xml
index 7aebf7b4a..5d29354f9 100644
--- a/server/api/pom.xml
+++ b/server/api/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-server-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-api
diff --git a/server/implementation-cdi/pom.xml b/server/implementation-cdi/pom.xml
index e21c4a71d..89ac1b3bb 100644
--- a/server/implementation-cdi/pom.xml
+++ b/server/implementation-cdi/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-server-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-cdi
diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java
index 1cb692e60..0dea8aca6 100644
--- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java
+++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/context/CDISmallRyeContext.java
@@ -262,11 +262,6 @@ public Map getAddedExtensions() {
return SmallRyeContextManager.getCurrentSmallRyeContext().getAddedExtensions();
}
- @Override
- public void setAddedExtensions(Map addedExtensions) {
- SmallRyeContextManager.getCurrentSmallRyeContext().setAddedExtensions(addedExtensions);
- }
-
@Override
public void addExtension(String key, Object value) {
SmallRyeContextManager.getCurrentSmallRyeContext().addExtension(key, value);
diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java
index b1246e902..dafa255e3 100644
--- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java
+++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MPMetricsService.java
@@ -2,7 +2,9 @@
import java.time.Duration;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
import jakarta.enterprise.inject.spi.CDI;
import jakarta.enterprise.util.AnnotationLiteral;
@@ -13,6 +15,7 @@
import org.jboss.logging.Logger;
import io.smallrye.graphql.api.Context;
+import io.smallrye.graphql.schema.model.OperationType;
import io.smallrye.graphql.spi.MetricsService;
/**
@@ -24,8 +27,10 @@
public class MPMetricsService implements MetricsService {
private MetricRegistry metricRegistry;
- private final Map metricsMemory = new ConcurrentHashMap<>();
+ private final Map> metricsMemory = new ConcurrentHashMap<>();
+ private final Map subscriptions = new ConcurrentHashMap<>();
private static final String METRIC_NAME = "mp_graphql";
+ private static final String METRIC_SUBSCRIPTIONS = "mp_graphql_subscription";
private Logger LOG = Logger.getLogger(MPMetricsService.class);
public MPMetricsService() {
@@ -41,7 +46,7 @@ private MetricRegistry getMetricRegistry() {
return metricRegistry;
}
- private Tag[] getTags(MetricMeasurement metricMeasurement) {
+ private Tag[] getTags(MetricMeasurement metricMeasurement) {
return new Tag[] {
new Tag("name", metricMeasurement.getName()),
new Tag("type", metricMeasurement.getOperationType()),
@@ -51,7 +56,7 @@ private Tag[] getTags(MetricMeasurement metricMeasurement) {
@Override
public void start(Long measurementId, Context context) {
- metricsMemory.put(measurementId, new MetricMeasurement(context.getFieldName(),
+ metricsMemory.put(measurementId, new MetricMeasurement<>(context.getFieldName(),
context.hasSource(),
context.getOperationType(),
System.nanoTime()));
@@ -60,13 +65,34 @@ public void start(Long measurementId, Context context) {
@Override
public void end(Long measurementId) {
- MetricMeasurement metricMeasurement = metricsMemory.remove(measurementId);
- long duration = System.nanoTime() - metricMeasurement.getTimeStarted();
+ MetricMeasurement metricMeasurement = metricsMemory.remove(measurementId);
+ long duration = System.nanoTime() - metricMeasurement.getMetric();
getMetricRegistry().simpleTimer(METRIC_NAME, getTags(metricMeasurement))
.update(Duration.ofNanos(duration));
LOG.tracef("(" + measurementId + ") Finished recording metrics for: %s", metricMeasurement.getName());
}
+ @Override
+ public void subscriptionStart(Context context) {
+ if (!OperationType.SUBSCRIPTION.name().equals(context.getOperationType())) {
+ return;
+ }
+ subscriptions.computeIfAbsent(context.getFieldName(), k -> new AtomicLong(0));
+ subscriptions.get(context.getFieldName()).incrementAndGet();
+ getMetricRegistry().gauge(
+ METRIC_SUBSCRIPTIONS,
+ () -> subscriptions.get(context.getFieldName()).get(),
+ new Tag("name", context.getFieldName()));
+ }
+
+ @Override
+ public void subscriptionEnd(Context context) {
+ if (!OperationType.SUBSCRIPTION.name().equals(context.getOperationType())) {
+ return;
+ }
+ Optional.ofNullable(subscriptions.get(context.getFieldName())).ifPresent(AtomicLong::decrementAndGet);
+ }
+
class VendorType extends AnnotationLiteral implements RegistryType {
@Override
public MetricRegistry.Type type() {
diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricMeasurement.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricMeasurement.java
index b531e1d0b..385b90e48 100644
--- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricMeasurement.java
+++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MetricMeasurement.java
@@ -1,16 +1,16 @@
package io.smallrye.graphql.cdi.metrics;
-public class MetricMeasurement {
+public class MetricMeasurement {
private String name;
private boolean source;
private String operationType;
- private long timeStarted;
+ private M metric;
- public MetricMeasurement(String name, boolean source, String operationType, long timeStarted) {
+ public MetricMeasurement(String name, boolean source, String operationType, M metric) {
this.name = name;
this.source = source;
this.operationType = operationType;
- this.timeStarted = timeStarted;
+ this.metric = metric;
}
public String getName() {
@@ -25,7 +25,7 @@ public String getOperationType() {
return operationType;
}
- public long getTimeStarted() {
- return timeStarted;
+ public M getMetric() {
+ return metric;
}
}
diff --git a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java
index f9e8487c8..6cfc2b4cd 100644
--- a/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java
+++ b/server/implementation-cdi/src/main/java/io/smallrye/graphql/cdi/metrics/MicrometerMetricsService.java
@@ -1,21 +1,27 @@
package io.smallrye.graphql.cdi.metrics;
-import java.time.Duration;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.atomic.AtomicLong;
import org.jboss.logging.Logger;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Metrics;
import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.Timer;
import io.smallrye.graphql.api.Context;
+import io.smallrye.graphql.schema.model.OperationType;
import io.smallrye.graphql.spi.MetricsService;
public class MicrometerMetricsService implements MetricsService {
private final MeterRegistry meterRegistry = Metrics.globalRegistry;
- private final Map metricsMemory = new ConcurrentHashMap<>();
+ private final Map> metricsMemory = new ConcurrentHashMap<>();
+ private final Map subscriptions = new ConcurrentHashMap<>();
+
private static final String METRIC_NAME = "mp_graphql";
+ private static final String METRIC_SUBSCRIPTIONS = "mp_graphql_subscription";
private Logger LOG = Logger.getLogger(MicrometerMetricsService.class);
public MicrometerMetricsService() {
@@ -24,7 +30,7 @@ public MicrometerMetricsService() {
meterRegistry.getMeters();
}
- private Tags getTags(MetricMeasurement metricMeasurement) {
+ private Tags getTags(MetricMeasurement metricMeasurement) {
return Tags.of("name", metricMeasurement.getName())
.and("type", metricMeasurement.getOperationType())
.and("source", String.valueOf(metricMeasurement.isSource()));
@@ -33,20 +39,41 @@ private Tags getTags(MetricMeasurement metricMeasurement) {
@Override
public void start(Long measurementId, Context context) {
metricsMemory.put(measurementId,
- new MetricMeasurement(
+ new MetricMeasurement<>(
context.getFieldName(),
context.hasSource(),
context.getOperationType(),
- System.nanoTime()));
+ Timer.start()));
LOG.tracef("(" + measurementId + ") Started recording metrics for: %s", context.getFieldName());
}
@Override
public void end(Long measurementId) {
- MetricMeasurement metricMeasurement = metricsMemory.remove(measurementId);
- long duration = System.nanoTime() - metricMeasurement.getTimeStarted();
- meterRegistry.timer(METRIC_NAME, getTags(metricMeasurement))
- .record(Duration.ofNanos(duration));
+ MetricMeasurement metricMeasurement = metricsMemory.remove(measurementId);
+ Timer timer = meterRegistry.timer(METRIC_NAME, getTags(metricMeasurement));
+ metricMeasurement.getMetric().stop(timer);
LOG.tracef("(" + measurementId + ") Finished recording metrics for: %s", metricMeasurement.getName());
}
+
+ @Override
+ public void subscriptionStart(Context context) {
+ if (!OperationType.SUBSCRIPTION.name().equals(context.getOperationType())) {
+ return;
+ }
+ subscriptions.computeIfAbsent(context.getFieldName(), k -> new AtomicLong(0));
+ subscriptions.get(context.getFieldName()).incrementAndGet();
+ meterRegistry.gauge(
+ METRIC_SUBSCRIPTIONS,
+ Tags.of("name", context.getFieldName()),
+ subscriptions.get(context.getFieldName()),
+ AtomicLong::get);
+ }
+
+ @Override
+ public void subscriptionEnd(Context context) {
+ if (!OperationType.SUBSCRIPTION.name().equals(context.getOperationType())) {
+ return;
+ }
+ Optional.ofNullable(subscriptions.get(context.getFieldName())).ifPresent(AtomicLong::decrementAndGet);
+ }
}
diff --git a/server/implementation-servlet/pom.xml b/server/implementation-servlet/pom.xml
index ea2f9661a..9b0393bcb 100644
--- a/server/implementation-servlet/pom.xml
+++ b/server/implementation-servlet/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-server-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-servlet
diff --git a/server/implementation/pom.xml b/server/implementation/pom.xml
index 7aee194fe..a89c420b6 100644
--- a/server/implementation/pom.xml
+++ b/server/implementation/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-server-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java
index bc3dcfe95..5d50df3e6 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/bootstrap/Bootstrap.java
@@ -18,6 +18,7 @@
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
+import java.util.Stack;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -727,6 +728,12 @@ private GraphQLDirective createGraphQLDirectiveFrom(DirectiveInstance directiveI
directiveBuilder.argument(argumentBuilder.build());
}
}
+ directiveBuilder.validLocations(directiveInstance
+ .getType()
+ .getLocations()
+ .stream()
+ .map(location -> DirectiveLocation.valueOf(DirectiveLocation.class, location))
+ .toArray(DirectiveLocation[]::new));
return directiveBuilder.build();
}
@@ -904,23 +911,25 @@ private GraphQLInputObjectField createGraphQLInputObjectFieldFromField(Field fie
}
private GraphQLInputType createGraphQLInputType(Field field) {
- GraphQLInputType graphQLInputType = referenceGraphQLInputType(field);
+ GraphQLInputType graphQLInputType = getGraphQLInputType(field.getReference());
Wrapper wrapper = dataFetcherFactory.unwrap(field, false);
// Field can have a wrapper, like List
if (wrapper != null && wrapper.isCollectionOrArrayOrMap()) {
+ Stack stackOfWrappers = new Stack<>();
+ for (Wrapper currentWrapper = wrapper; currentWrapper != null; currentWrapper = currentWrapper.getWrapper()) {
+ stackOfWrappers.add(currentWrapper);
+ }
// Loop as long as there is a wrapper
do {
+ wrapper = stackOfWrappers.pop();
if (wrapper.isCollectionOrArrayOrMap()) {
if (wrapper.isWrappedTypeNotNull()) {
graphQLInputType = GraphQLNonNull.nonNull(graphQLInputType);
}
graphQLInputType = list(graphQLInputType);
- wrapper = wrapper.getWrapper();
- } else {
- wrapper = null;
}
- } while (wrapper != null);
+ } while (!stackOfWrappers.empty());
}
// Check if field is mandatory
@@ -937,18 +946,20 @@ private GraphQLOutputType createGraphQLOutputType(Field field, boolean isBatch)
Wrapper wrapper = dataFetcherFactory.unwrap(field, isBatch);
// Field can have a wrapper, like List
if (wrapper != null && wrapper.isCollectionOrArrayOrMap()) {
+ Stack stackOfWrappers = new Stack<>();
+ for (Wrapper currentWrapper = wrapper; currentWrapper != null; currentWrapper = currentWrapper.getWrapper()) {
+ stackOfWrappers.add(currentWrapper);
+ }
// Loop as long as there is a wrapper
do {
+ wrapper = stackOfWrappers.pop();
if (wrapper.isCollectionOrArrayOrMap()) {
if (wrapper.isWrappedTypeNotNull()) {
graphQLOutputType = GraphQLNonNull.nonNull(graphQLOutputType);
}
graphQLOutputType = list(graphQLOutputType);
- wrapper = wrapper.getWrapper();
- } else {
- wrapper = null;
}
- } while (wrapper != null);
+ } while (!stackOfWrappers.empty());
}
// Check if field is mandatory
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java
index 3963b2007..db07c72df 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/ExecutionService.java
@@ -265,8 +265,7 @@ private void notifyAndWrite(SmallRyeContext smallRyeContext,
smallRyeContext.setExecutionResult(executionResult);
// Notify after
eventEmitter.fireAfterExecute(smallRyeContext);
-
- ExecutionResponse executionResponse = new ExecutionResponse(executionResult,
+ ExecutionResponse executionResponse = new ExecutionResponse(smallRyeContext.unwrap(ExecutionResult.class),
smallRyeContext.getAddedExtensions());
if (!payloadOption.equals(LogPayloadOption.off)) {
log.payloadOut(executionResponse.toString());
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/QueryCache.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/QueryCache.java
index 1afec0ca4..93041094f 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/QueryCache.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/QueryCache.java
@@ -10,6 +10,7 @@
import graphql.ExecutionInput;
import graphql.execution.instrumentation.InstrumentationContext;
+import graphql.execution.instrumentation.InstrumentationState;
import graphql.execution.instrumentation.SimpleInstrumentation;
import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters;
import graphql.execution.preparsed.PreparsedDocumentEntry;
@@ -26,7 +27,7 @@ public class QueryCache extends SimpleInstrumentation implements PreparsedDocume
private final LRUCache cache = new LRUCache<>(MAX_CACHE_SIZE);
@Override
- public PreparsedDocumentEntry getDocument(ExecutionInput executionInput,
+ public CompletableFuture getDocumentAsync(ExecutionInput executionInput,
Function computeFunction) {
String query = executionInput.getQuery();
PreparsedDocumentEntry entry = cache.get(query);
@@ -37,19 +38,19 @@ public PreparsedDocumentEntry getDocument(ExecutionInput executionInput,
} else {
log.retrievedFromCache(query);
}
- return entry;
+ return CompletableFuture.completedFuture(entry);
}
@Override
public InstrumentationContext> beginValidation(
- InstrumentationValidationParameters parameters) {
+ InstrumentationValidationParameters parameters, InstrumentationState state) {
ExecutionFunction executionFunction = executionFunctionTL.get();
executionFunctionTL.remove();
if (executionFunction != null) {
return new ValidationInstrumentationContext(executionFunction);
}
- return super.beginValidation(parameters);
+ return super.beginValidation(parameters, state);
}
private static class ExecutionFunction implements Function {
@@ -79,7 +80,7 @@ private class ValidationInstrumentationContext implements InstrumentationContext
}
@Override
- public void onDispatched(CompletableFuture> result) {
+ public void onDispatched() {
// no-op
}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/DocumentSupplier.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/DocumentSupplier.java
index 7d19cb422..274df7fa4 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/DocumentSupplier.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/DocumentSupplier.java
@@ -1,5 +1,6 @@
package io.smallrye.graphql.execution.context;
+import java.util.concurrent.ExecutionException;
import java.util.function.Supplier;
import graphql.ExecutionInput;
@@ -25,12 +26,17 @@ public Document get() {
ParseAndValidateResult parse = ParseAndValidate.parse(executionInput);
return parse.isFailure() ? null : parse.getDocument();
} else {
- PreparsedDocumentEntry documentEntry = queryCache.getDocument(executionInput, ei -> {
- ParseAndValidateResult parse = ParseAndValidate.parse(ei);
- return parse.isFailure() ? new PreparsedDocumentEntry(parse.getErrors())
- : new PreparsedDocumentEntry(parse.getDocument());
- });
+ PreparsedDocumentEntry documentEntry = null;
+ try {
+ documentEntry = queryCache.getDocumentAsync(executionInput, ei -> {
+ ParseAndValidateResult parse = ParseAndValidate.parse(ei);
+ return parse.isFailure() ? new PreparsedDocumentEntry(parse.getErrors())
+ : new PreparsedDocumentEntry(parse.getDocument());
+ }).get();
+ } catch (InterruptedException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
return documentEntry.hasErrors() ? null : documentEntry.getDocument();
}
}
-}
\ No newline at end of file
+}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java
index 49188a66a..fe26aac8c 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContext.java
@@ -2,10 +2,10 @@
import static io.smallrye.graphql.SmallRyeGraphQLServerMessages.msg;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
import jakarta.json.JsonArray;
import jakarta.json.JsonObject;
@@ -21,6 +21,17 @@
/**
* Implements the Context from MicroProfile API.
*
+ * WARNING: This class has to be used as semi-immutable.
+ * When propagating this to a new execution, it has to be cloned.
+ *
+ * A clone is a deep copy WITH THE EXCEPTION OF:
+ * - Added extensions
+ * - ExecutionResult
+ * - DataFetchingEnvironment (this actually should be rewritten after cloning for each new fetcher)
+ *
+ * These above things get shared between all clones to enable applications to write their own data
+ * into them.
+ *
* @author Phillip Kruger (phillip.kruger@redhat.com)
*/
public class SmallRyeContext implements Context {
@@ -44,7 +55,32 @@ public class SmallRyeContext implements Context {
private QueryCache queryCache;
private DocumentSupplier documentSupplier;
private ExecutionResult executionResult;
- private Map addedExtensions = new HashMap<>();
+ private Map addedExtensions = new ConcurrentHashMap<>();
+
+ public SmallRyeContext clone() {
+ SmallRyeContext clone = new SmallRyeContext(createdBy);
+ clone.fetchId = fetchId;
+ clone.request = request;
+ clone.executionId = executionId;
+ clone.field = field;
+ clone.fieldName = fieldName;
+ clone.arguments = arguments;
+ clone.source = source;
+ clone.path = path;
+ clone.selectedFields = selectedFields;
+ clone.selectedAndSourceFields = selectedAndSourceFields;
+ clone.operationType = operationType;
+ clone.requestedOperationTypes = requestedOperationTypes;
+ clone.parentTypeName = parentTypeName;
+ clone.operationName = operationName;
+ clone.dataFetchingEnvironment = dataFetchingEnvironment;
+ clone.executionInput = executionInput;
+ clone.queryCache = queryCache;
+ clone.documentSupplier = documentSupplier;
+ clone.executionResult = executionResult;
+ clone.addedExtensions = addedExtensions;
+ return clone;
+ }
public Map getAddedExtensions() {
return addedExtensions;
@@ -52,10 +88,12 @@ public Map getAddedExtensions() {
/**
* Sets the entire map of extension(s) into the context.
+ * Note: this is private, for adding extensions, it is necessary to use getAddedExtensions().put(...)
+ * to avoid changing the map reference.
*
* @param addedExtensions The Map object containing extension(s).
*/
- public void setAddedExtensions(Map addedExtensions) {
+ private void setAddedExtensions(Map addedExtensions) {
this.addedExtensions = addedExtensions;
}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java
index 09ed035f7..334decdb6 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/context/SmallRyeContextManager.java
@@ -125,25 +125,26 @@ public static SmallRyeContext populateFromDataFetchingEnvironment(
smallRyeContext = restoreSmallRyeContext(dataFetchingEnvironment);
if (!smallRyeContext.hasRequest())
throw new RuntimeException("Invalid context provided, can not populate data from Data Fetching Environment");
- smallRyeContext.setDataFetchingEnvironment(dataFetchingEnvironment);
- smallRyeContext.setField(field);
- smallRyeContext.setArguments(dataFetchingEnvironment.getArguments());
- smallRyeContext.setPath(dataFetchingEnvironment.getExecutionStepInfo().getPath().toString());
- smallRyeContext.setExecutionId(dataFetchingEnvironment.getExecutionId().toString());
- smallRyeContext.setFieldName(dataFetchingEnvironment.getField().getName());
- smallRyeContext.setSource(dataFetchingEnvironment.getSource());
- smallRyeContext.setSelectedFields(buildSelectedFields(type, dataFetchingEnvironment, field, false));
- smallRyeContext.setSelectedAndSourceFields(buildSelectedFields(type, dataFetchingEnvironment, field, true));
- smallRyeContext.setOperationType(getOperationTypeFromDefinition(dataFetchingEnvironment.getOperationDefinition()));
- smallRyeContext.setParentTypeName(getGraphQLTypeName(dataFetchingEnvironment.getParentType()).orElse(null));
- if (smallRyeContext.getOperationName().isEmpty()) {
- smallRyeContext.setOperationName(getOperationName(dataFetchingEnvironment));
+ SmallRyeContext clone = smallRyeContext.clone();
+ clone.setDataFetchingEnvironment(dataFetchingEnvironment);
+ clone.setField(field);
+ clone.setArguments(dataFetchingEnvironment.getArguments());
+ clone.setPath(dataFetchingEnvironment.getExecutionStepInfo().getPath().toString());
+ clone.setExecutionId(dataFetchingEnvironment.getExecutionId().toString());
+ clone.setFieldName(dataFetchingEnvironment.getField().getName());
+ clone.setSource(dataFetchingEnvironment.getSource());
+ clone.setSelectedFields(buildSelectedFields(type, dataFetchingEnvironment, field, false));
+ clone.setSelectedAndSourceFields(buildSelectedFields(type, dataFetchingEnvironment, field, true));
+ clone.setOperationType(getOperationTypeFromDefinition(dataFetchingEnvironment.getOperationDefinition()));
+ clone.setParentTypeName(getGraphQLTypeName(dataFetchingEnvironment.getParentType()).orElse(null));
+ if (clone.getOperationName().isEmpty()) {
+ clone.setOperationName(getOperationName(dataFetchingEnvironment));
}
GraphQLContext graphQLContext = dataFetchingEnvironment.getGraphQlContext();
- graphQLContext.put(CONTEXT, smallRyeContext);
+ graphQLContext.put(CONTEXT, clone);
- current.set(smallRyeContext);
- return smallRyeContext;
+ current.set(clone);
+ return clone;
}
private static Optional getGraphQLTypeName(GraphQLType graphQLType) {
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractStreamingDataFetcher.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractStreamingDataFetcher.java
index f319e4749..7cc6670a3 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractStreamingDataFetcher.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/datafetcher/AbstractStreamingDataFetcher.java
@@ -51,6 +51,8 @@ protected O invokeAndTransform(
return (O) resultBuilder.build();
}
})
+ .onSubscription().invoke(() -> metricsEmitter.subscriptionStart(context))
+ .onTermination().invoke(() -> metricsEmitter.subscriptionEnd(context))
.onFailure().recoverWithItem(new Function() {
@Override
public O apply(Throwable throwable) {
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java
index 551da1fc5..ba8992284 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/event/EventEmitter.java
@@ -1,6 +1,7 @@
package io.smallrye.graphql.execution.event;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
@@ -37,11 +38,13 @@
*/
public class EventEmitter {
private static final Logger LOG = Logger.getLogger(EventEmitter.class);
- private static final ThreadLocal eventEmitters = ThreadLocal.withInitial(EventEmitter::new);
+
+ private static final EventEmitter INSTANCE = new EventEmitter();
+
private final List enabledServices;
public static EventEmitter getInstance() {
- return eventEmitters.get();
+ return INSTANCE;
}
private EventEmitter() {
@@ -70,7 +73,7 @@ private EventEmitter() {
}
}
enabledServices.sort(Comparator.comparing(this::getPriority));
- this.enabledServices = enabledServices;
+ this.enabledServices = Collections.unmodifiableList(enabledServices);
}
private int getPriority(EventingService es) {
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/execution/metrics/MetricsEmitter.java b/server/implementation/src/main/java/io/smallrye/graphql/execution/metrics/MetricsEmitter.java
index 7f7755089..11900c5dd 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/execution/metrics/MetricsEmitter.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/execution/metrics/MetricsEmitter.java
@@ -16,11 +16,11 @@
public class MetricsEmitter {
private static final Logger LOG = Logger.getLogger(MetricsEmitter.class);
- private static final ThreadLocal metricsEmitters = ThreadLocal.withInitial(MetricsEmitter::new);
+ private static final MetricsEmitter METRICS_EMITTER = new MetricsEmitter();
private final List enabledServices;
public static MetricsEmitter getInstance() {
- return metricsEmitters.get();
+ return METRICS_EMITTER;
}
private MetricsEmitter() {
@@ -43,6 +43,14 @@ private MetricsEmitter() {
this.enabledServices = enabledServices;
}
+ public void subscriptionStart(Context context) {
+ enabledServices.forEach(metricsService -> metricsService.subscriptionStart(context));
+ }
+
+ public void subscriptionEnd(Context context) {
+ enabledServices.forEach(metricsService -> metricsService.subscriptionEnd(context));
+ }
+
public Long start(Context context) {
Long measurementId = ThreadLocalRandom.current().nextLong();
enabledServices.forEach(metricsService -> metricsService.start(measurementId, context));
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/scalar/federation/FieldSetCoercing.java b/server/implementation/src/main/java/io/smallrye/graphql/scalar/federation/FieldSetCoercing.java
index 4397dc8d4..901232982 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/scalar/federation/FieldSetCoercing.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/scalar/federation/FieldSetCoercing.java
@@ -26,6 +26,10 @@ private String convertImpl(Object input) {
} else {
throw new RuntimeException("Can not parse a String from [" + typeName(value) + "]");
}
+ } else if (input instanceof String) {
+ return (String) input;
+ } else if (input instanceof StringValue) {
+ return ((StringValue) input).getValue();
} else {
throw new RuntimeException("Can not parse a FieldSet from [" + typeName(input) + "]");
}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/spi/ClassloadingService.java b/server/implementation/src/main/java/io/smallrye/graphql/spi/ClassloadingService.java
index 80221e225..b091678f0 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/spi/ClassloadingService.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/spi/ClassloadingService.java
@@ -19,8 +19,6 @@
*/
public interface ClassloadingService {
- ServiceLoader classloadingServices = ServiceLoader.load(ClassloadingService.class);
-
ClassloadingService classloadingService = load();
static ClassloadingService get() {
@@ -30,7 +28,7 @@ static ClassloadingService get() {
static ClassloadingService load() {
ClassloadingService cls;
try {
- cls = classloadingServices.iterator().next();
+ cls = ServiceLoader.load(ClassloadingService.class).iterator().next();
} catch (Exception ex) {
cls = new DefaultClassloadingService();
}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/spi/MetricsService.java b/server/implementation/src/main/java/io/smallrye/graphql/spi/MetricsService.java
index 1e1cb2393..9587d3205 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/spi/MetricsService.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/spi/MetricsService.java
@@ -6,4 +6,20 @@ public interface MetricsService {
void start(Long measurementId, Context context);
void end(Long measurementId);
+
+ /**
+ * Tracks the number of active connections to GraphQL Subscriptions
+ * Indicates to backing Metrics Service that a new connection has been established.
+ *
+ * @param context GraphQL Subscription Context
+ */
+ void subscriptionStart(Context context);
+
+ /**
+ * Tracks the number of active connections to GraphQL Subscriptions
+ * Indicates to backing Metrics Service that a connection has been terminated
+ *
+ * @param context GraphQL Subscription Context
+ */
+ void subscriptionEnd(Context context);
}
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java b/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java
index 2e8ac467c..7de1a0584 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/spi/config/Config.java
@@ -18,13 +18,12 @@
*/
public interface Config {
static Logger LOG = Logger.getLogger(Config.class);
- ServiceLoader configs = ServiceLoader.load(Config.class);
Config config = init();
static Config init() {
Config c;
try {
- c = configs.iterator().next();
+ c = ServiceLoader.load(Config.class).iterator().next();
} catch (Exception ex) {
c = new Config() {
@Override
diff --git a/server/implementation/src/main/java/io/smallrye/graphql/transformation/TransformException.java b/server/implementation/src/main/java/io/smallrye/graphql/transformation/TransformException.java
index 4ad317cf8..3a3b8bac6 100644
--- a/server/implementation/src/main/java/io/smallrye/graphql/transformation/TransformException.java
+++ b/server/implementation/src/main/java/io/smallrye/graphql/transformation/TransformException.java
@@ -47,10 +47,13 @@ public DataFetcherResult.Builder
+
+ io.micrometer
+ micrometer-core
+ test
+
org.eclipse.microprofile.context-propagation
microprofile-context-propagation-api
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/extensions/DynamicClientExtensionsTest.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/extensions/DynamicClientExtensionsTest.java
index 42b77830e..de6026a05 100644
--- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/extensions/DynamicClientExtensionsTest.java
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/dynamic/extensions/DynamicClientExtensionsTest.java
@@ -86,7 +86,7 @@ public Pool poolWithoutExtensions() {
@Query
public Pool poolWithExtensions() {
- smallRyeContext.setAddedExtensions(getMap());
+ smallRyeContext.getAddedExtensions().putAll(getMap());
return new Pool(23);
}
}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ClientApi.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ClientApi.java
index b3fb0df1a..1147b602f 100644
--- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ClientApi.java
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ClientApi.java
@@ -3,7 +3,7 @@
import org.eclipse.microprofile.graphql.Query;
import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
-import io.smallrye.graphql.tests.client.typesafe.directives.model.SomeClass;
+import io.smallrye.graphql.tests.client.typesafe.directives.model.SomeClassClient;
@GraphQLClientApi
public interface ClientApi {
@@ -12,7 +12,8 @@ public interface ClientApi {
@FieldDirective(fields = 1)
@FieldDirective(fields = { 2, 3 })
@VariableDefinitionDirective(fields = "should ignore")
- SomeClass getQuerySomeClass(@FieldDirective(fields = 999) /* will ignore */ SomeClass someObject,
+ SomeClassClient getQuerySomeClass(
+ @FieldDirective(fields = 999) /* will ignore */ SomeClassClient someObject,
@VariableDefinitionDirective @VariableDefinitionDirective(fields = "a") boolean simpleType);
}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ServerApi.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ServerApi.java
index 544e3cbb9..143b48299 100644
--- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ServerApi.java
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/ServerApi.java
@@ -6,17 +6,17 @@
import org.eclipse.microprofile.graphql.Query;
import io.smallrye.graphql.execution.context.SmallRyeContext;
-import io.smallrye.graphql.tests.client.typesafe.directives.model.SomeClass;
+import io.smallrye.graphql.tests.client.typesafe.directives.model.SomeClassServer;
@GraphQLApi
public class ServerApi {
- private static String EXPECTED_QUERY = "query querySomeClass($someObject: SomeClassInput, $simpleType: Boolean! @variableDefinitionDirective @variableDefinitionDirective(fields: \"a\")) { querySomeClass(someObject: $someObject, simpleType: $simpleType) @fieldDirective(fields: [1]) @fieldDirective(fields: [2, 3]) {id @fieldDirective(fields: [4]) number} }";
+ private final static String EXPECTED_QUERY = "query querySomeClass($someObject: SomeClassServerInput, $simpleType: Boolean! @variableDefinitionDirective @variableDefinitionDirective(fields: \"a\")) { querySomeClass(someObject: $someObject, simpleType: $simpleType) @fieldDirective(fields: [1]) @fieldDirective(fields: [2, 3]) {id @fieldDirective(fields: [4]) number} }";
@Inject
SmallRyeContext context;
@Query
- public SomeClass getQuerySomeClass(SomeClass someObject, boolean simpleType) {
+ public SomeClassServer getQuerySomeClass(SomeClassServer someObject, boolean simpleType) {
if (!context.getQuery().equals(EXPECTED_QUERY)) {
throw new RuntimeException("Queries do not match");
}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/TypesafeStaticDirectivesClientModelTest.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/TypesafeStaticDirectivesClientModelTest.java
index 624bad092..88b80bd7d 100644
--- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/TypesafeStaticDirectivesClientModelTest.java
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/TypesafeStaticDirectivesClientModelTest.java
@@ -6,6 +6,7 @@
import java.io.IOException;
import java.net.URL;
+import org.eclipse.microprofile.graphql.Name;
import org.jboss.arquillian.container.test.api.Deployment;
import org.jboss.arquillian.container.test.api.RunAsClient;
import org.jboss.arquillian.junit.Arquillian;
@@ -17,9 +18,9 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import io.smallrye.graphql.client.typesafe.api.GraphQLClientApi;
import io.smallrye.graphql.client.vertx.typesafe.VertxTypesafeGraphQLClientBuilder;
-import io.smallrye.graphql.tests.client.typesafe.directives.model.SomeClass;
+import io.smallrye.graphql.tests.client.typesafe.directives.model.SomeClassClient;
+import io.smallrye.graphql.tests.client.typesafe.directives.model.SomeClassServer;
@RunWith(Arquillian.class)
@RunAsClient
@@ -28,8 +29,8 @@ public class TypesafeStaticDirectivesClientModelTest {
@Deployment
public static WebArchive deployment() {
return ShrinkWrap.create(WebArchive.class, "typesafe-directive-client-model.war")
- .addClasses(SomeClass.class, ServerApi.class,
- // needed for the server-side (java-graphql) validation
+ .addClasses(SomeClassServer.class, ServerApi.class,
+ // needed for the server-side (graphql-java) validation
FieldDirective.class,
VariableDefinitionDirective.class);
}
@@ -45,12 +46,12 @@ public void prepare() {
if (!onlyOnce) {
Index index = null;
try {
- index = Index.of(SomeClass.class, ClientApi.class, ServerApi.class,
+ index = Index.of(SomeClassClient.class, ClientApi.class,
+ Name.class,
FieldDirective.class,
FieldDirective.FieldDirectives.class,
VariableDefinitionDirective.class,
- VariableDefinitionDirective.VariableDefinitionDirectives.class,
- GraphQLClientApi.class);
+ VariableDefinitionDirective.VariableDefinitionDirectives.class);
} catch (IOException e) {
throw new RuntimeException(e);
}
@@ -64,9 +65,10 @@ public void prepare() {
@Test
public void singleQueryDirectiveTest() {
- SomeClass queryInput = new SomeClass("a", 1);
+ final SomeClassClient queryInput = new SomeClassClient("a", 1);
// query checking is on the server side API
- assertEquals(queryInput, client.getQuerySomeClass(queryInput, false));
+ final SomeClassClient queryResult = client.getQuerySomeClass(queryInput, false);
+ assertEquals(queryInput.getId(), queryResult.getId());
+ assertEquals(queryInput.getNumber(), queryResult.getNumber());
}
-
}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClassClient.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClassClient.java
new file mode 100644
index 000000000..4c16fa719
--- /dev/null
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClassClient.java
@@ -0,0 +1,36 @@
+package io.smallrye.graphql.tests.client.typesafe.directives.model;
+
+import org.eclipse.microprofile.graphql.Name;
+
+import io.smallrye.graphql.tests.client.typesafe.directives.FieldDirective;
+
+@Name("SomeClassServerInput")
+public class SomeClassClient {
+ @FieldDirective(fields = 4)
+ String id;
+ int number;
+
+ public SomeClassClient(String id, int number) {
+ this.id = id;
+ this.number = number;
+ }
+
+ public SomeClassClient() {
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ public void setNumber(int number) {
+ this.number = number;
+ }
+}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClass.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClassServer.java
similarity index 76%
rename from server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClass.java
rename to server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClassServer.java
index e44eaf46d..8aa3f958b 100644
--- a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClass.java
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/client/typesafe/directives/model/SomeClassServer.java
@@ -2,17 +2,14 @@
import java.util.Objects;
-import io.smallrye.graphql.tests.client.typesafe.directives.FieldDirective;
-
-public class SomeClass {
- @FieldDirective(fields = 4)
+public class SomeClassServer {
private String id;
private int number;
- public SomeClass() {
+ public SomeClassServer() {
}
- public SomeClass(String id, int number) {
+ public SomeClassServer(String id, int number) {
this.id = id;
this.number = number;
}
@@ -39,7 +36,7 @@ public boolean equals(Object o) {
return true;
if (o == null || getClass() != o.getClass())
return false;
- SomeClass someClass = (SomeClass) o;
+ SomeClassServer someClass = (SomeClassServer) o;
return number == someClass.number && Objects.equals(id, someClass.id);
}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/context/AfterExecutionErrorTest.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/context/AfterExecutionErrorTest.java
new file mode 100644
index 000000000..cff615c00
--- /dev/null
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/context/AfterExecutionErrorTest.java
@@ -0,0 +1,92 @@
+package io.smallrye.graphql.tests.context;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.URL;
+import java.util.List;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import graphql.ErrorClassification;
+import graphql.ExecutionResult;
+import graphql.ExecutionResultImpl;
+import graphql.GraphQLError;
+import graphql.language.SourceLocation;
+import io.smallrye.graphql.api.Context;
+import io.smallrye.graphql.execution.context.SmallRyeContext;
+import io.smallrye.graphql.spi.EventingService;
+import io.smallrye.graphql.tests.GraphQLAssured;
+
+@RunWith(Arquillian.class)
+public class AfterExecutionErrorTest {
+
+ @GraphQLApi
+ public static class SomeApi {
+
+ @Query
+ public String foo() {
+ return "bar";
+ }
+ }
+
+ public static class Events implements EventingService {
+
+ @Override
+ public String getConfigKey() {
+ return null;
+ }
+
+ @Override
+ public void afterExecute(Context context) {
+ ExecutionResult executionResult = ((SmallRyeContext) context).unwrap(ExecutionResult.class);
+ ExecutionResult newExecutionResult = ExecutionResultImpl
+ .newExecutionResult()
+ .from(executionResult)
+ .errors(List.of(new GraphQLError() {
+ @Override
+ public String getMessage() {
+ return "Error occurred in afterExecute hook";
+ }
+
+ @Override
+ public List getLocations() {
+ return List.of();
+ }
+
+ @Override
+ public ErrorClassification getErrorType() {
+ return null;
+ }
+ })).build();
+ ((SmallRyeContext) context).setExecutionResult(newExecutionResult);
+ }
+ }
+
+ @Deployment
+ public static WebArchive deployment() {
+ return ShrinkWrap.create(WebArchive.class, "after-execute-test.war")
+ .addAsResource(new StringAsset(Events.class.getName()),
+ "META-INF/services/io.smallrye.graphql.spi.EventingService")
+ .addClasses(SomeApi.class);
+ }
+
+ @ArquillianResource
+ URL testingURL;
+
+ @Test
+ public void executionResultContainsErrorTest() {
+ GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);
+ String response = graphQLAssured.post("{ foo }");
+ assertEquals("{\"errors\":[{\"message\":\"Error occurred in afterExecute hook\",\"locations\":[]}]" +
+ ",\"data\":{\"foo\":\"bar\"}}", response);
+ }
+}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/context/ContextTest.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/context/ContextTest.java
new file mode 100644
index 000000000..0952ddd7c
--- /dev/null
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/context/ContextTest.java
@@ -0,0 +1,158 @@
+package io.smallrye.graphql.tests.context;
+
+import static org.junit.Assert.assertEquals;
+
+import java.net.URL;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.logging.Logger;
+
+import jakarta.inject.Inject;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+import org.eclipse.microprofile.graphql.Source;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import io.smallrye.graphql.api.Context;
+import io.smallrye.graphql.execution.context.SmallRyeContext;
+import io.smallrye.graphql.spi.EventingService;
+import io.smallrye.graphql.tests.GraphQLAssured;
+
+/**
+ * Verifies that the correct SmallRyeContext is properly propagated to field executions
+ * during more complex execution scenarios, including an EventingService.
+ */
+@RunWith(Arquillian.class)
+public class ContextTest {
+
+ @GraphQLApi
+ public static class ComplexApi {
+
+ @Inject
+ SmallRyeContext ctx;
+
+ @Query
+ public Dummy query1() {
+ if (!ctx.getField().getName().equals("query1")) {
+ throw new RuntimeException("ERROR: Wrong selected fields: " + ctx.getSelectedFields());
+ }
+ return new Dummy("ok");
+ }
+
+ @Query
+ public Dummy query2() {
+ if (!ctx.getField().getName().equals("query2")) {
+ throw new RuntimeException("ERROR: Wrong selected fields: " + ctx.getSelectedFields());
+ }
+ return new Dummy("ok");
+ }
+
+ @Query
+ public Dummy query3() {
+ if (!ctx.getField().getName().equals("query3")) {
+ throw new RuntimeException("ERROR: Wrong selected fields: " + ctx.getSelectedFields());
+ }
+ return new Dummy("ok");
+ }
+
+ public String source(@Source Dummy dummy) {
+ return ctx.getPath();
+ }
+
+ }
+
+ public static class Events implements EventingService {
+
+ private static final Map requestContexts = new ConcurrentHashMap<>();
+ private static final Map fetchContexts = new ConcurrentHashMap<>();
+ private static Logger LOG = Logger.getLogger(Events.class.getName());
+
+ private static String getFieldNameExecutionId(Context context) {
+ return context.getFieldName() + " " + context.getExecutionId();
+ }
+
+ @Override
+ public void afterDataFetch(Context context) {
+ String fieldNameExecutionId = getFieldNameExecutionId(context);
+ String remove = fetchContexts.remove(fieldNameExecutionId);
+ LOG.info("afterDataFetch " + fieldNameExecutionId + " " + remove);
+ if (remove == null) {
+ LOG.warning(fieldNameExecutionId + " twice");
+ throw new NullPointerException(fieldNameExecutionId);
+ }
+ }
+
+ @Override
+ public void beforeDataFetch(Context context) {
+ String fieldNameExecutionId = getFieldNameExecutionId(context);
+ LOG.info("beforeDataFetch " + fieldNameExecutionId);
+ fetchContexts.put(fieldNameExecutionId, fieldNameExecutionId);
+ }
+
+ @Override
+ public void afterExecute(Context context) {
+ String remove = requestContexts.remove(context.getExecutionId());
+ LOG.info("afterExecute " + context.getExecutionId() + " " + remove);
+ if (remove == null) {
+ LOG.warning(context.getExecutionId() + " twice");
+ throw new NullPointerException(context.getExecutionId());
+ }
+ }
+
+ @Override
+ public void beforeExecute(Context context) {
+ LOG.info("beforeExecute " + context.getExecutionId());
+ requestContexts.put(context.getExecutionId(), context.getExecutionId());
+ }
+
+ @Override
+ public String getConfigKey() {
+ return null;
+ }
+ }
+
+ public static class Dummy {
+
+ private String ok;
+
+ public Dummy(String ok) {
+ this.ok = ok;
+ }
+
+ public String getOk() {
+ return ok;
+ }
+
+ public void setOk(String ok) {
+ this.ok = ok;
+ }
+ }
+
+ @Deployment
+ public static WebArchive deployment() {
+ return ShrinkWrap.create(WebArchive.class, "context-test.war")
+ .addAsResource(new StringAsset(Events.class.getName()),
+ "META-INF/services/io.smallrye.graphql.spi.EventingService")
+ .addClasses(ComplexApi.class, Dummy.class);
+ }
+
+ @ArquillianResource
+ URL testingURL;
+
+ @Test
+ public void test() {
+ GraphQLAssured graphQLAssured = new GraphQLAssured(testingURL);
+ String response1 = graphQLAssured.post("{query1 {ok source} query2 {ok source} query3 {ok source}}");
+ assertEquals("{\"data\":{\"query1\":{\"ok\":\"ok\",\"source\":\"/query1/source\"}," +
+ "\"query2\":{\"ok\":\"ok\",\"source\":\"/query2/source\"}," +
+ "\"query3\":{\"ok\":\"ok\",\"source\":\"/query3/source\"}}}", response1);
+ }
+}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MPMetricsTestCase.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MPMetricsTestCase.java
new file mode 100644
index 000000000..de8bfa2a5
--- /dev/null
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MPMetricsTestCase.java
@@ -0,0 +1,114 @@
+package io.smallrye.graphql.tests.metrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URL;
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import jakarta.inject.Inject;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+import org.eclipse.microprofile.metrics.Gauge;
+import org.eclipse.microprofile.metrics.MetricID;
+import org.eclipse.microprofile.metrics.MetricRegistry;
+import org.eclipse.microprofile.metrics.Tag;
+import org.eclipse.microprofile.metrics.annotation.RegistryType;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.smallrye.graphql.api.Subscription;
+import io.smallrye.graphql.client.Response;
+import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient;
+import io.smallrye.graphql.client.vertx.dynamic.VertxDynamicGraphQLClientBuilder;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.subscription.Cancellable;
+import io.smallrye.mutiny.unchecked.Unchecked;
+
+/**
+ * Verify that metrics are produced as expected.
+ */
+@RunWith(Arquillian.class)
+public class MPMetricsTestCase {
+
+ @Deployment
+ public static WebArchive deployment() {
+ return ShrinkWrap.create(WebArchive.class, "metrics-test.war")
+ .addAsResource(new StringAsset("smallrye.graphql.metrics.enabled=true"),
+ "META-INF/microprofile-config.properties")
+ .addClasses(Foo.class);
+ }
+
+ @ArquillianResource
+ URL testingURL;
+
+ @GraphQLApi
+ public static class Foo {
+
+ static CountDownLatch CANCELLED = new CountDownLatch(1);
+
+ @Subscription
+ public Multi counting() {
+ return Multi.createFrom()
+ .ticks()
+ .every(Duration.ofSeconds(1L))
+ .onCancellation().invoke(() -> CANCELLED.countDown());
+ }
+
+ @Query
+ public Long count() {
+ return CANCELLED.getCount();
+ }
+
+ }
+
+ MeterRegistry meterRegistry = Metrics.globalRegistry;
+
+ @Inject
+ @RegistryType(type = MetricRegistry.Type.VENDOR)
+ MetricRegistry metricRegistry;
+
+ DynamicGraphQLClient client;
+
+ @Before
+ public void before() {
+ client = new VertxDynamicGraphQLClientBuilder()
+ .url(testingURL.toString() + "graphql")
+ .build();
+ }
+
+ @Test
+ public void testSubscriptionMetrics() throws Exception {
+ Multi multi = client.subscription("subscription {counting}");
+ CountDownLatch items = new CountDownLatch(2);
+ Cancellable cancellable = multi.subscribe().with(
+ response -> items.countDown(),
+ Unchecked.consumer(error -> {
+ throw new AssertionError("Subscription failed", error);
+ }));
+
+ assertTrue(items.await(10, TimeUnit.SECONDS));
+
+ assertThat(metricRegistry.getGauges()).hasSize(1);
+ MetricID metricID = new MetricID("mp_graphql_subscription", new Tag("name", "counting"));
+ Gauge> gauge = metricRegistry.getGauge(metricID);
+ assertThat(gauge).isNotNull();
+ assertThat(gauge.getValue()).isEqualTo(1L);
+
+ cancellable.cancel();
+ assertTrue(Foo.CANCELLED.await(10, TimeUnit.SECONDS));
+ assertThat(gauge.getValue()).isEqualTo(0L);
+ }
+}
diff --git a/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MicrometerMetricsTestCase.java b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MicrometerMetricsTestCase.java
new file mode 100644
index 000000000..03a2f6dd2
--- /dev/null
+++ b/server/integration-tests/src/test/java/io/smallrye/graphql/tests/metrics/MicrometerMetricsTestCase.java
@@ -0,0 +1,105 @@
+package io.smallrye.graphql.tests.metrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import java.net.URL;
+import java.time.Duration;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+import org.jboss.arquillian.container.test.api.Deployment;
+import org.jboss.arquillian.junit.Arquillian;
+import org.jboss.arquillian.test.api.ArquillianResource;
+import org.jboss.shrinkwrap.api.ShrinkWrap;
+import org.jboss.shrinkwrap.api.asset.StringAsset;
+import org.jboss.shrinkwrap.api.spec.WebArchive;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import io.micrometer.core.instrument.Gauge;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import io.smallrye.graphql.api.Subscription;
+import io.smallrye.graphql.client.Response;
+import io.smallrye.graphql.client.dynamic.api.DynamicGraphQLClient;
+import io.smallrye.graphql.client.vertx.dynamic.VertxDynamicGraphQLClientBuilder;
+import io.smallrye.mutiny.Multi;
+import io.smallrye.mutiny.subscription.Cancellable;
+import io.smallrye.mutiny.unchecked.Unchecked;
+
+/**
+ * Verify that metrics are produced as expected.
+ */
+@RunWith(Arquillian.class)
+public class MicrometerMetricsTestCase {
+
+ @Deployment
+ public static WebArchive deployment() {
+ return ShrinkWrap.create(WebArchive.class, "metrics-test.war")
+ .addAsResource(new StringAsset("smallrye.graphql.metrics.enabled=true"),
+ "META-INF/microprofile-config.properties")
+ .addClasses(Foo.class);
+ }
+
+ @ArquillianResource
+ URL testingURL;
+
+ @GraphQLApi
+ public static class Foo {
+
+ static CountDownLatch CANCELLED = new CountDownLatch(1);
+
+ @Subscription
+ public Multi counting() {
+ return Multi.createFrom()
+ .ticks()
+ .every(Duration.ofSeconds(1L))
+ .onCancellation().invoke(() -> CANCELLED.countDown());
+ }
+
+ @Query
+ public Long count() {
+ return CANCELLED.getCount();
+ }
+
+ }
+
+ MeterRegistry meterRegistry = Metrics.globalRegistry;
+
+ DynamicGraphQLClient client;
+
+ @Before
+ public void before() {
+ client = new VertxDynamicGraphQLClientBuilder()
+ .url(testingURL.toString() + "graphql")
+ .build();
+ //Needs to add this registry in order to maintain count state across timer counts
+ Metrics.addRegistry(new SimpleMeterRegistry());
+ }
+
+ @Test
+ public void testSubscriptionMetrics() throws Exception {
+ Multi multi = client.subscription("subscription {counting}");
+ CountDownLatch items = new CountDownLatch(2);
+ Cancellable cancellable = multi.subscribe().with(
+ response -> items.countDown(),
+ Unchecked.consumer(error -> {
+ throw new AssertionError("Subscription failed", error);
+ }));
+
+ assertTrue(items.await(10, TimeUnit.SECONDS));
+ assertThat(meterRegistry.getMeters()).hasSizeGreaterThanOrEqualTo(1);
+ Gauge gauge = meterRegistry.get("mp_graphql_subscription").tags(Tags.of("name", "counting")).gauge();
+ assertThat(gauge).isNotNull();
+ assertThat(gauge.value()).isEqualTo(1L);
+ cancellable.cancel();
+ assertTrue(Foo.CANCELLED.await(10, TimeUnit.SECONDS));
+ assertThat(gauge.value()).isEqualTo(0L);
+ }
+}
diff --git a/server/pom.xml b/server/pom.xml
index dcc8c5234..e037d78e2 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-server-parent
diff --git a/server/runner/pom.xml b/server/runner/pom.xml
index b293cd7a1..26206db81 100644
--- a/server/runner/pom.xml
+++ b/server/runner/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-server-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-runner
diff --git a/server/tck/pom.xml b/server/tck/pom.xml
index b568915e6..de2fd2518 100644
--- a/server/tck/pom.xml
+++ b/server/tck/pom.xml
@@ -4,7 +4,7 @@
io.smallrye
smallrye-graphql-server-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-tck
diff --git a/tools/gradle-plugin/gradle.properties b/tools/gradle-plugin/gradle.properties
index 2725eb44e..fe686ba3a 100644
--- a/tools/gradle-plugin/gradle.properties
+++ b/tools/gradle-plugin/gradle.properties
@@ -1 +1 @@
-version=2.8.1
+version=2.9.1
diff --git a/tools/gradle-plugin/pom.xml b/tools/gradle-plugin/pom.xml
index 99131c32c..e4c0fd874 100644
--- a/tools/gradle-plugin/pom.xml
+++ b/tools/gradle-plugin/pom.xml
@@ -4,7 +4,7 @@
io.smallrye
smallrye-graphql-tools-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
4.0.0
diff --git a/tools/maven-plugin-tests/pom.xml b/tools/maven-plugin-tests/pom.xml
index 6cae409fa..bcec73d43 100644
--- a/tools/maven-plugin-tests/pom.xml
+++ b/tools/maven-plugin-tests/pom.xml
@@ -3,7 +3,7 @@
io.smallrye
smallrye-graphql-tools-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
4.0.0
diff --git a/tools/maven-plugin/pom.xml b/tools/maven-plugin/pom.xml
index ba6aa2050..b893500a1 100644
--- a/tools/maven-plugin/pom.xml
+++ b/tools/maven-plugin/pom.xml
@@ -3,7 +3,7 @@
io.smallrye
smallrye-graphql-tools-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
4.0.0
diff --git a/tools/pom.xml b/tools/pom.xml
index bf4686790..38ad82cf7 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-tools-parent
diff --git a/ui/graphiql/README.asciidoc b/ui/graphiql/README.asciidoc
index 25aa04252..476f315c4 100644
--- a/ui/graphiql/README.asciidoc
+++ b/ui/graphiql/README.asciidoc
@@ -1,6 +1,6 @@
= SmallRye Graph__i__QL UI
-This allows you you add GraphiQL UI with your project.
+This allows you to add GraphiQL UI with your project.
image:/ui/graphiql/graphiql.png[link="/main/ui/graphiql/graphiql.png"]
diff --git a/ui/graphiql/pom.xml b/ui/graphiql/pom.xml
index 313494fa7..5292c05a4 100644
--- a/ui/graphiql/pom.xml
+++ b/ui/graphiql/pom.xml
@@ -5,7 +5,7 @@
io.smallrye
smallrye-graphql-ui-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-ui-graphiql
@@ -16,7 +16,7 @@
graphql-ui
18.2.0
- 2.4.7
+ 3.2.0
diff --git a/ui/pom.xml b/ui/pom.xml
index a757f46f7..8e5065b20 100644
--- a/ui/pom.xml
+++ b/ui/pom.xml
@@ -5,14 +5,14 @@
io.smallrye
smallrye-graphql-parent
- 2.8.2-SNAPSHOT
+ 2.9.2-SNAPSHOT
smallrye-graphql-ui-parent
pom
SmallRye: GraphQL UI
- UI Tools effectivly repackaged
+ UI Tools effectively repackaged
graphiql