diff --git a/.github/workflows/master.yml b/.github/workflows/master.yml index 74651d8847..ad7765ce1a 100644 --- a/.github/workflows/master.yml +++ b/.github/workflows/master.yml @@ -12,11 +12,13 @@ jobs: env: MAVEN_CENTRAL_USER: ${{ secrets.MAVEN_CENTRAL_USER }} MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_CENTRAL_USER_NEW: ${{ secrets.MAVEN_CENTRAL_USER_NEW }} + MAVEN_CENTRAL_PASSWORD_NEW: ${{ secrets.MAVEN_CENTRAL_PASSWORD_NEW }} MAVEN_CENTRAL_PGP_KEY: ${{ secrets.MAVEN_CENTRAL_PGP_KEY }} steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v3 + - uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 11 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 8a1bbfe82c..61f24f3d28 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -7,6 +7,7 @@ on: pull_request: branches: - master + - 24.x - 23.x - 22.x - 21.x @@ -20,7 +21,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v3 + - uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 11 uses: actions/setup-java@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 21b31d32ee..dd2ca3893e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -14,11 +14,13 @@ jobs: MAVEN_CENTRAL_PGP_KEY: ${{ secrets.MAVEN_CENTRAL_PGP_KEY }} MAVEN_CENTRAL_USER: ${{ secrets.MAVEN_CENTRAL_USER }} MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + MAVEN_CENTRAL_USER_NEW: ${{ secrets.MAVEN_CENTRAL_USER_NEW }} + MAVEN_CENTRAL_PASSWORD_NEW: ${{ secrets.MAVEN_CENTRAL_PASSWORD_NEW }} RELEASE_VERSION: ${{ github.event.inputs.version }} steps: - uses: actions/checkout@v4 - - uses: gradle/wrapper-validation-action@v3 + - uses: gradle/actions/wrapper-validation@v4 - name: Set up JDK 11 uses: actions/setup-java@v4 with: diff --git a/build.gradle b/build.gradle index 6cbf7cb9fa..8eab937efa 100644 --- a/build.gradle +++ b/build.gradle @@ -105,7 +105,7 @@ tasks.withType(GroovyCompile) { } dependencies { implementation 'org.antlr:antlr4-runtime:' + antlrVersion - api 'com.graphql-java:java-dataloader:4.0.0' + api 'com.graphql-java:java-dataloader:5.0.2' api 'org.reactivestreams:reactive-streams:' + reactiveStreamsVersion api "org.jspecify:jspecify:1.0.0" antlr 'org.antlr:antlr4:' + antlrVersion @@ -178,7 +178,7 @@ shadowJar { bnd(''' -exportcontents: graphql.* -removeheaders: Private-Package -Import-Package: !android.os.*,!com.google.*,!org.checkerframework.*,!javax.annotation.*,!graphql.com.google.*,!org.antlr.*,!graphql.org.antlr.*,!sun.misc.*,* +Import-Package: !android.os.*,!com.google.*,!org.checkerframework.*,!javax.annotation.*,!graphql.com.google.*,!org.antlr.*,!graphql.org.antlr.*,!sun.misc.*,org.jspecify.annotations;resolution:=optional,* ''') } @@ -352,8 +352,12 @@ publishing { nexusPublishing { repositories { sonatype { - username = System.env.MAVEN_CENTRAL_USER - password = System.env.MAVEN_CENTRAL_PASSWORD + username = System.env.MAVEN_CENTRAL_USER_NEW + password = System.env.MAVEN_CENTRAL_PASSWORD_NEW + // https://central.sonatype.org/publish/publish-portal-ossrh-staging-api/#configuration + nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/")) + // GraphQL Java does not publish snapshots, but adding this URL for completeness + snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/")) } } } diff --git a/src/jmh/java/graphql/execution/ExecutionStepInfoBenchmark.java b/src/jmh/java/graphql/execution/ExecutionStepInfoBenchmark.java new file mode 100644 index 0000000000..e2678c67f8 --- /dev/null +++ b/src/jmh/java/graphql/execution/ExecutionStepInfoBenchmark.java @@ -0,0 +1,86 @@ +package graphql.execution; + +import graphql.Scalars; +import graphql.language.Field; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.TearDown; +import org.openjdk.jmh.annotations.Warmup; +import org.openjdk.jmh.infra.Blackhole; +import org.openjdk.jmh.profile.GCProfiler; +import org.openjdk.jmh.runner.Runner; +import org.openjdk.jmh.runner.options.Options; +import org.openjdk.jmh.runner.options.OptionsBuilder; + +import java.util.concurrent.TimeUnit; + +import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; + +@State(Scope.Benchmark) +@Warmup(iterations = 2, time = 5) +@Measurement(iterations = 2) +@Fork(2) +public class ExecutionStepInfoBenchmark { + @Param({"1000000", "2000000"}) + int howManyItems = 1000000; + + @Setup(Level.Trial) + public void setUp() { + } + + @TearDown(Level.Trial) + public void tearDown() { + } + + + MergedField mergedField = MergedField.newMergedField().addField(Field.newField("f").build()).build(); + + ResultPath path = ResultPath.rootPath().segment("f"); + ExecutionStepInfo rootStepInfo = newExecutionStepInfo() + .path(path).type(Scalars.GraphQLString) + .field(mergedField) + .build(); + + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void benchMarkDirectConstructorThroughput(Blackhole blackhole) { + for (int i = 0; i < howManyItems; i++) { + ResultPath newPath = path.segment(1); + ExecutionStepInfo newOne = rootStepInfo.transform(Scalars.GraphQLInt, rootStepInfo, newPath); + blackhole.consume(newOne); + } + } + + @Benchmark + @BenchmarkMode(Mode.Throughput) + @OutputTimeUnit(TimeUnit.SECONDS) + public void benchMarkBuilderThroughput(Blackhole blackhole) { + for (int i = 0; i < howManyItems; i++) { + ResultPath newPath = path.segment(1); + ExecutionStepInfo newOne = newExecutionStepInfo(rootStepInfo).parentInfo(rootStepInfo) + .type(Scalars.GraphQLInt).path(newPath).build(); + blackhole.consume(newOne); + } + } + + public static void main(String[] args) throws Exception { + Options opt = new OptionsBuilder() + .include("graphql.execution.ExecutionStepInfoBenchmark") + .addProfiler(GCProfiler.class) + .build(); + + new Runner(opt).run(); + } + +} diff --git a/src/main/java/graphql/GraphqlErrorHelper.java b/src/main/java/graphql/GraphqlErrorHelper.java index 35a20d03f7..901c25b5a9 100644 --- a/src/main/java/graphql/GraphqlErrorHelper.java +++ b/src/main/java/graphql/GraphqlErrorHelper.java @@ -1,13 +1,13 @@ package graphql; import graphql.language.SourceLocation; +import graphql.util.FpKit; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.stream.Collectors; import static graphql.collect.ImmutableKit.mapAndDropNulls; @@ -73,12 +73,18 @@ public static Object location(SourceLocation location) { if (line < 1 || column < 1) { return null; } - return Map.of("line", line, "column", column); + LinkedHashMap map = new LinkedHashMap<>(2); + map.put("line", line); + map.put("column", column); + return map; } static List fromSpecification(List> specificationMaps) { - return specificationMaps.stream() - .map(GraphqlErrorHelper::fromSpecification).collect(Collectors.toList()); + List list = FpKit.arrayListSizedTo(specificationMaps); + for (Map specificationMap : specificationMaps) { + list.add(fromSpecification(specificationMap)); + } + return list; } static GraphQLError fromSpecification(Map specificationMap) { diff --git a/src/main/java/graphql/analysis/values/ValueTraverser.java b/src/main/java/graphql/analysis/values/ValueTraverser.java index 1cf7745aaa..be5c37326a 100644 --- a/src/main/java/graphql/analysis/values/ValueTraverser.java +++ b/src/main/java/graphql/analysis/values/ValueTraverser.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; import graphql.PublicApi; +import graphql.collect.ImmutableKit; import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingEnvironmentImpl; import graphql.schema.GraphQLAppliedDirective; @@ -22,7 +23,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import static graphql.Assert.assertShouldNeverHappen; import static graphql.Assert.assertTrue; @@ -62,13 +62,12 @@ private InputElements(GraphQLInputSchemaElement startElement) { private InputElements(ImmutableList inputElements) { this.inputElements = inputElements; - this.unwrappedInputElements = inputElements.stream() - .filter(it -> !(it instanceof GraphQLNonNull || it instanceof GraphQLList)) - .collect(ImmutableList.toImmutableList()); + this.unwrappedInputElements = ImmutableKit.filter(inputElements, + it -> !(it instanceof GraphQLNonNull || it instanceof GraphQLList)); - List inputValDefs = unwrappedInputElements.stream() - .filter(it -> it instanceof GraphQLInputValueDefinition) - .map(GraphQLInputValueDefinition.class::cast).collect(Collectors.toList()); + List inputValDefs = ImmutableKit.filterAndMap(unwrappedInputElements, + it -> it instanceof GraphQLInputValueDefinition, + GraphQLInputValueDefinition.class::cast); this.lastElement = inputValDefs.isEmpty() ? null : inputValDefs.get(inputValDefs.size() - 1); } diff --git a/src/main/java/graphql/collect/ImmutableKit.java b/src/main/java/graphql/collect/ImmutableKit.java index 6fc66280c1..99ba867493 100644 --- a/src/main/java/graphql/collect/ImmutableKit.java +++ b/src/main/java/graphql/collect/ImmutableKit.java @@ -4,23 +4,26 @@ import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import graphql.Internal; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.function.Function; +import java.util.function.Predicate; import static graphql.Assert.assertNotNull; @Internal -@SuppressWarnings({"UnstableApiUsage"}) +@NullMarked public final class ImmutableKit { public static ImmutableList emptyList() { return ImmutableList.of(); } - public static ImmutableList nonNullCopyOf(Collection collection) { + public static ImmutableList nonNullCopyOf(@Nullable Collection collection) { return collection == null ? emptyList() : ImmutableList.copyOf(collection); } @@ -41,9 +44,9 @@ public static ImmutableList concatLists(List l1, List l2) { * for the flexible style. Benchmarking has shown this to outperform `stream()`. * * @param collection the collection to map - * @param mapper the mapper function - * @param for two - * @param for result + * @param mapper the mapper function + * @param for two + * @param for result * * @return a map immutable list of results */ @@ -58,15 +61,66 @@ public static ImmutableList map(Collection collection, Fu return builder.build(); } + /** + * This is more efficient than `c.stream().filter().collect()` because it does not create the intermediate objects needed + * for the flexible style. Benchmarking has shown this to outperform `stream()`. + * + * @param collection the collection to map + * @param filter the filter predicate + * @param for two + * + * @return a map immutable list of results + */ + public static ImmutableList filter(Collection collection, Predicate filter) { + assertNotNull(collection); + assertNotNull(filter); + return filterAndMap(collection, filter, Function.identity()); + } + + /** + * This is more efficient than `c.stream().filter().map().collect()` because it does not create the intermediate objects needed + * for the flexible style. Benchmarking has shown this to outperform `stream()`. + * + * @param collection the collection to map + * @param filter the filter predicate + * @param mapper the mapper function + * @param for two + * @param for result + * + * @return a map immutable list of results + */ + public static ImmutableList filterAndMap(Collection collection, Predicate filter, Function mapper) { + assertNotNull(collection); + assertNotNull(mapper); + assertNotNull(filter); + ImmutableList.Builder builder = ImmutableList.builderWithExpectedSize(collection.size()); + for (T t : collection) { + if (filter.test(t)) { + R r = mapper.apply(t); + builder.add(r); + } + } + return builder.build(); + } + + public static ImmutableList flatMapList(Collection> listLists) { + ImmutableList.Builder builder = ImmutableList.builder(); + for (List t : listLists) { + builder.addAll(t); + } + return builder.build(); + } + + /** * This will map a collection of items but drop any that are null from the input. * This is more efficient than `c.stream().map().collect()` because it does not create the intermediate objects needed * for the flexible style. Benchmarking has shown this to outperform `stream()`. * * @param collection the collection to map - * @param mapper the mapper function - * @param for two - * @param for result + * @param mapper the mapper function + * @param for two + * @param for result * * @return a map immutable list of results */ diff --git a/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java b/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java index 545d0fb0a9..98c6ce478b 100644 --- a/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java +++ b/src/main/java/graphql/execution/AsyncSerialExecutionStrategy.java @@ -52,11 +52,9 @@ public CompletableFuture execute(ExecutionContext executionCont CompletableFuture> resultsFuture = Async.eachSequentially(fieldNames, (fieldName, prevResults) -> { MergedField currentField = fields.getSubField(fieldName); ResultPath fieldPath = parameters.getPath().segment(mkNameForPath(currentField)); - ExecutionStrategyParameters newParameters = parameters - .transform(builder -> builder.field(currentField).path(fieldPath)); + ExecutionStrategyParameters newParameters = parameters.transform(currentField, fieldPath); - Object resolveSerialField = resolveSerialField(executionContext, dataLoaderDispatcherStrategy, newParameters); - return resolveSerialField; + return resolveSerialField(executionContext, dataLoaderDispatcherStrategy, newParameters); }); CompletableFuture overallResult = new CompletableFuture<>(); diff --git a/src/main/java/graphql/execution/DataFetcherResult.java b/src/main/java/graphql/execution/DataFetcherResult.java index cbf0a03639..cfe6337d0c 100644 --- a/src/main/java/graphql/execution/DataFetcherResult.java +++ b/src/main/java/graphql/execution/DataFetcherResult.java @@ -10,6 +10,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.function.Consumer; import java.util.function.Function; @@ -24,10 +25,19 @@ * This also allows you to pass down new local context objects between parent and child fields. If you return a * {@link #getLocalContext()} value then it will be passed down into any child fields via * {@link graphql.schema.DataFetchingEnvironment#getLocalContext()} - * + *

* You can also have {@link DataFetcher}s contribute to the {@link ExecutionResult#getExtensions()} by returning * extensions maps that will be merged together via the {@link graphql.extensions.ExtensionsBuilder} and its {@link graphql.extensions.ExtensionsMerger} * in place. + *

+ * This provides {@link #hashCode()} and {@link #equals(Object)} methods that afford comparison with other {@link DataFetcherResult} object.s + * However, to function correctly, this relies on the values provided in the following fields in turn also implementing {@link #hashCode()}} and {@link #equals(Object)} as appropriate: + *

    + *
  • The data returned in {@link #getData()}. + *
  • The individual errors returned in {@link #getErrors()}. + *
  • The context returned in {@link #getLocalContext()}. + *
  • The keys/values in the {@link #getExtensions()} {@link Map}. + *
* * @param The type of the data fetched */ @@ -123,6 +133,35 @@ public DataFetcherResult map(Function transformation) { .build(); } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + + DataFetcherResult that = (DataFetcherResult) o; + return Objects.equals(data, that.data) + && errors.equals(that.errors) + && Objects.equals(localContext, that.localContext) + && Objects.equals(extensions, that.extensions); + } + + @Override + public int hashCode() { + return Objects.hash(data, errors, localContext, extensions); + } + + @Override + public String toString() { + return "DataFetcherResult{" + + "data=" + data + + ", errors=" + errors + + ", localContext=" + localContext + + ", extensions=" + extensions + + '}'; + } + /** * Creates a new data fetcher result builder * diff --git a/src/main/java/graphql/execution/Execution.java b/src/main/java/graphql/execution/Execution.java index a784325f3d..928580d91a 100644 --- a/src/main/java/graphql/execution/Execution.java +++ b/src/main/java/graphql/execution/Execution.java @@ -180,14 +180,12 @@ private CompletableFuture executeOperation(ExecutionContext exe MergedSelectionSet fields = fieldCollector.collectFields( collectorParameters, operationDefinition.getSelectionSet(), - Optional.ofNullable(executionContext.getGraphQLContext()) - .map(graphqlContext -> graphqlContext.getBoolean(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT)) - .orElse(false) + executionContext.hasIncrementalSupport() ); ResultPath path = ResultPath.rootPath(); ExecutionStepInfo executionStepInfo = newExecutionStepInfo().type(operationRootType).path(path).build(); - NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo); + NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext); ExecutionStrategyParameters parameters = newParameters() .executionStepInfo(executionStepInfo) @@ -255,9 +253,7 @@ private DataLoaderDispatchStrategy createDataLoaderDispatchStrategy(ExecutionCon return DataLoaderDispatchStrategy.NO_OP; } if (!executionContext.isSubscriptionOperation()) { - boolean deferEnabled = Optional.ofNullable(executionContext.getGraphQLContext()) - .map(graphqlContext -> graphqlContext.getBoolean(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT)) - .orElse(false); + boolean deferEnabled = executionContext.hasIncrementalSupport(); // Dedicated strategy for defer support, for safety purposes. return deferEnabled ? diff --git a/src/main/java/graphql/execution/ExecutionContext.java b/src/main/java/graphql/execution/ExecutionContext.java index a53ae621e1..22e2d7b638 100644 --- a/src/main/java/graphql/execution/ExecutionContext.java +++ b/src/main/java/graphql/execution/ExecutionContext.java @@ -360,9 +360,14 @@ public ResultNodesInfo getResultNodesInfo() { return resultNodesInfo; } + @Internal + public boolean hasIncrementalSupport() { + GraphQLContext graphqlContext = getGraphQLContext(); + return graphqlContext != null && graphqlContext.getBoolean(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT); + } + @Internal public EngineRunningState getEngineRunningState() { return engineRunningState; } - } diff --git a/src/main/java/graphql/execution/ExecutionStepInfo.java b/src/main/java/graphql/execution/ExecutionStepInfo.java index 6ecf42c5f7..eefa8a81cc 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfo.java +++ b/src/main/java/graphql/execution/ExecutionStepInfo.java @@ -1,5 +1,6 @@ package graphql.execution; +import graphql.Internal; import graphql.PublicApi; import graphql.collect.ImmutableMapWithNullValues; import graphql.schema.GraphQLFieldDefinition; @@ -77,6 +78,25 @@ private ExecutionStepInfo(Builder builder) { this.fieldContainer = builder.fieldContainer; } + /* + * This constructor allows for a slightly ( 1% ish) faster transformation without an intermediate Builder object + */ + private ExecutionStepInfo(GraphQLOutputType type, + ResultPath path, + ExecutionStepInfo parent, + MergedField field, + GraphQLFieldDefinition fieldDefinition, + GraphQLObjectType fieldContainer, + Supplier> arguments) { + this.type = assertNotNull(type, () -> "you must provide a graphql type"); + this.path = path; + this.parent = parent; + this.field = field; + this.fieldDefinition = fieldDefinition; + this.fieldContainer = fieldContainer; + this.arguments = arguments; + } + /** * The GraphQLObjectType where fieldDefinition is defined. * Note: @@ -193,13 +213,12 @@ public boolean hasParent() { public ExecutionStepInfo changeTypeWithPreservedNonNull(GraphQLOutputType newType) { assertTrue(!GraphQLTypeUtil.isNonNull(newType), () -> "newType can't be non null"); if (isNonNullType()) { - return newExecutionStepInfo(this).type(GraphQLNonNull.nonNull(newType)).build(); + return transform(GraphQLNonNull.nonNull(newType)); } else { - return newExecutionStepInfo(this).type(newType).build(); + return transform(newType); } } - /** * @return the type in graphql SDL format, eg [typeName!]! */ @@ -216,6 +235,16 @@ public String toString() { '}'; } + @Internal + ExecutionStepInfo transform(GraphQLOutputType type) { + return new ExecutionStepInfo(type, path, parent, field, fieldDefinition, fieldContainer, arguments); + } + + @Internal + ExecutionStepInfo transform(GraphQLOutputType type, ExecutionStepInfo parent, ResultPath path) { + return new ExecutionStepInfo(type, path, parent, field, fieldDefinition, fieldContainer, arguments); + } + public ExecutionStepInfo transform(Consumer builderConsumer) { Builder builder = new Builder(this); builderConsumer.accept(builder); diff --git a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java index 1a9f91aa46..ec2716aec3 100644 --- a/src/main/java/graphql/execution/ExecutionStepInfoFactory.java +++ b/src/main/java/graphql/execution/ExecutionStepInfoFactory.java @@ -1,19 +1,92 @@ package graphql.execution; import graphql.Internal; +import graphql.collect.ImmutableMapWithNullValues; +import graphql.language.Argument; +import graphql.schema.GraphQLArgument; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLList; +import graphql.schema.GraphQLObjectType; import graphql.schema.GraphQLOutputType; +import graphql.util.FpKit; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.function.Supplier; + +import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; @Internal +@NullMarked public class ExecutionStepInfoFactory { public ExecutionStepInfo newExecutionStepInfoForListElement(ExecutionStepInfo executionInfo, ResultPath indexedPath) { GraphQLList fieldType = (GraphQLList) executionInfo.getUnwrappedNonNullType(); GraphQLOutputType typeInList = (GraphQLOutputType) fieldType.getWrappedType(); - return executionInfo.transform(builder -> builder - .parentInfo(executionInfo) - .type(typeInList) - .path(indexedPath)); + return executionInfo.transform(typeInList, executionInfo, indexedPath); + } + + /** + * Builds the type info hierarchy for the current field + * + * @param executionContext the execution context in play + * @param parameters contains the parameters holding the fields to be executed and source object + * @param fieldDefinition the field definition to build type info for + * @param fieldContainer the field container + * + * @return a new type info + */ + public ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionContext, + ExecutionStrategyParameters parameters, + GraphQLFieldDefinition fieldDefinition, + @Nullable GraphQLObjectType fieldContainer) { + MergedField field = parameters.getField(); + ExecutionStepInfo parentStepInfo = parameters.getExecutionStepInfo(); + GraphQLOutputType fieldType = fieldDefinition.getType(); + List fieldArgDefs = fieldDefinition.getArguments(); + Supplier> argumentValues = ImmutableMapWithNullValues::emptyMap; + // + // no need to create args at all if there are none on the field def + // + if (!fieldArgDefs.isEmpty()) { + argumentValues = getArgumentValues(executionContext, fieldArgDefs, field.getArguments()); + } + + + return newExecutionStepInfo() + .type(fieldType) + .fieldDefinition(fieldDefinition) + .fieldContainer(fieldContainer) + .field(field) + .path(parameters.getPath()) + .parentInfo(parentStepInfo) + .arguments(argumentValues) + .build(); } + @NonNull + private static Supplier> getArgumentValues(ExecutionContext executionContext, + List fieldArgDefs, + List fieldArgs) { + Supplier> argumentValues; + GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); + Supplier> argValuesSupplier = () -> { + Map resolvedValues = ValuesResolver.getArgumentValues(codeRegistry, + fieldArgDefs, + fieldArgs, + executionContext.getCoercedVariables(), + executionContext.getGraphQLContext(), + executionContext.getLocale()); + + return ImmutableMapWithNullValues.copyOf(resolvedValues); + }; + argumentValues = FpKit.intraThreadMemoize(argValuesSupplier); + return argumentValues; + } + + } diff --git a/src/main/java/graphql/execution/ExecutionStrategy.java b/src/main/java/graphql/execution/ExecutionStrategy.java index f9370d1e91..bc71d5b625 100644 --- a/src/main/java/graphql/execution/ExecutionStrategy.java +++ b/src/main/java/graphql/execution/ExecutionStrategy.java @@ -14,7 +14,6 @@ import graphql.TrivialDataFetcher; import graphql.TypeMismatchError; import graphql.UnresolvedTypeError; -import graphql.collect.ImmutableMapWithNullValues; import graphql.execution.directives.QueryDirectives; import graphql.execution.directives.QueryDirectivesImpl; import graphql.execution.incremental.DeferredExecutionSupport; @@ -29,7 +28,6 @@ import graphql.execution.reactive.ReactiveSupport; import graphql.extensions.ExtensionsBuilder; import graphql.introspection.Introspection; -import graphql.language.Argument; import graphql.language.Field; import graphql.normalized.ExecutableNormalizedField; import graphql.normalized.ExecutableNormalizedOperation; @@ -38,12 +36,10 @@ import graphql.schema.DataFetchingEnvironment; import graphql.schema.DataFetchingFieldSelectionSet; import graphql.schema.DataFetchingFieldSelectionSetImpl; -import graphql.schema.GraphQLArgument; import graphql.schema.GraphQLCodeRegistry; import graphql.schema.GraphQLEnumType; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLObjectType; -import graphql.schema.GraphQLOutputType; import graphql.schema.GraphQLScalarType; import graphql.schema.GraphQLSchema; import graphql.schema.GraphQLType; @@ -64,7 +60,6 @@ import java.util.function.Supplier; import static graphql.execution.Async.exceptionallyCompletedFuture; -import static graphql.execution.ExecutionStepInfo.newExecutionStepInfo; import static graphql.execution.FieldCollectorParameters.newParameters; import static graphql.execution.FieldValueInfo.CompleteValueType.ENUM; import static graphql.execution.FieldValueInfo.CompleteValueType.LIST; @@ -300,9 +295,7 @@ private static Map buildFieldValueMap(List fieldNames, L DeferredExecutionSupport createDeferredExecutionSupport(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { MergedSelectionSet fields = parameters.getFields(); - return Optional.ofNullable(executionContext.getGraphQLContext()) - .map(graphqlContext -> graphqlContext.getBoolean(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT)) - .orElse(false) ? + return executionContext.hasIncrementalSupport() ? new DeferredExecutionSupport.DeferredExecutionSupportImpl( fields, parameters, @@ -329,8 +322,7 @@ DeferredExecutionSupport createDeferredExecutionSupport(ExecutionContext executi MergedField currentField = fields.getSubField(fieldName); ResultPath fieldPath = parameters.getPath().segment(mkNameForPath(currentField)); - ExecutionStrategyParameters newParameters = parameters - .transform(builder -> builder.field(currentField).path(fieldPath).parent(parameters)); + ExecutionStrategyParameters newParameters = parameters.transform(currentField, fieldPath, parameters); if (!deferredExecutionSupport.isDeferredField(currentField)) { Object fieldValueInfo = resolveFieldWithInfo(executionContext, newParameters); @@ -625,20 +617,18 @@ private FieldValueInfo completeField(GraphQLFieldDefinition fieldDef, ExecutionC instrumentationParams, executionContext.getInstrumentationState() )); - NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo); - - ExecutionStrategyParameters newParameters = parameters.transform(builder -> - builder.executionStepInfo(executionStepInfo) - .source(fetchedValue.getFetchedValue()) - .localContext(fetchedValue.getLocalContext()) - .nonNullFieldValidator(nonNullableFieldValidator) - ); + ExecutionStrategyParameters newParameters = parameters.transform(executionStepInfo, + fetchedValue.getLocalContext(), + fetchedValue.getFetchedValue()); FieldValueInfo fieldValueInfo = completeValue(executionContext, newParameters); - - CompletableFuture executionResultFuture = fieldValueInfo.getFieldValueFuture(); ctxCompleteField.onDispatched(); - executionResultFuture.whenComplete(ctxCompleteField::onCompleted); + if (fieldValueInfo.isFutureValue()) { + CompletableFuture executionResultFuture = fieldValueInfo.getFieldValueFuture(); + executionResultFuture.whenComplete(ctxCompleteField::onCompleted); + } else { + ctxCompleteField.onCompleted(fieldValueInfo.getFieldValueObject(), null); + } return fieldValueInfo; } @@ -786,17 +776,13 @@ protected FieldValueInfo completeValueForList(ExecutionContext executionContext, ExecutionStepInfo stepInfoForListElement = executionStepInfoFactory.newExecutionStepInfoForListElement(executionStepInfo, indexedPath); - NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, stepInfoForListElement); - FetchedValue value = unboxPossibleDataFetcherResult(executionContext, parameters, item); - ExecutionStrategyParameters newParameters = parameters.transform(builder -> - builder.executionStepInfo(stepInfoForListElement) - .nonNullFieldValidator(nonNullableFieldValidator) - .localContext(value.getLocalContext()) - .path(indexedPath) - .source(value.getFetchedValue()) - ); + ExecutionStrategyParameters newParameters = parameters.transform(stepInfoForListElement, + indexedPath, + value.getLocalContext(), + value.getFetchedValue()); + fieldValueInfos.add(completeValue(executionContext, newParameters)); index++; } @@ -928,20 +914,14 @@ protected Object completeValueForObject(ExecutionContext executionContext, Execu MergedSelectionSet subFields = fieldCollector.collectFields( collectorParameters, parameters.getField(), - Optional.ofNullable(executionContext.getGraphQLContext()) - .map(graphqlContext -> graphqlContext.getBoolean(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT)) - .orElse(false) + executionContext.hasIncrementalSupport() ); ExecutionStepInfo newExecutionStepInfo = executionStepInfo.changeTypeWithPreservedNonNull(resolvedObjectType); - NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, newExecutionStepInfo); - ExecutionStrategyParameters newParameters = parameters.transform(builder -> - builder.executionStepInfo(newExecutionStepInfo) - .fields(subFields) - .nonNullFieldValidator(nonNullableFieldValidator) - .source(result) - ); + ExecutionStrategyParameters newParameters = parameters.transform(newExecutionStepInfo, + subFields, + result); // Calling this from the executionContext to ensure we shift back from mutation strategy to the query strategy. return executionContext.getQueryStrategy().executeObject(executionContext, newParameters); @@ -1091,48 +1071,10 @@ protected ExecutionStepInfo createExecutionStepInfo(ExecutionContext executionCo ExecutionStrategyParameters parameters, GraphQLFieldDefinition fieldDefinition, GraphQLObjectType fieldContainer) { - MergedField field = parameters.getField(); - ExecutionStepInfo parentStepInfo = parameters.getExecutionStepInfo(); - GraphQLOutputType fieldType = fieldDefinition.getType(); - List fieldArgDefs = fieldDefinition.getArguments(); - Supplier> argumentValues = ImmutableMapWithNullValues::emptyMap; - // - // no need to create args at all if there are none on the field def - // - if (!fieldArgDefs.isEmpty()) { - argumentValues = getArgumentValues(executionContext, fieldArgDefs, field.getArguments()); - } - - - return newExecutionStepInfo() - .type(fieldType) - .fieldDefinition(fieldDefinition) - .fieldContainer(fieldContainer) - .field(field) - .path(parameters.getPath()) - .parentInfo(parentStepInfo) - .arguments(argumentValues) - .build(); - } - - @NonNull - private static Supplier> getArgumentValues(ExecutionContext executionContext, - List fieldArgDefs, - List fieldArgs) { - Supplier> argumentValues; - GraphQLCodeRegistry codeRegistry = executionContext.getGraphQLSchema().getCodeRegistry(); - Supplier> argValuesSupplier = () -> { - Map resolvedValues = ValuesResolver.getArgumentValues(codeRegistry, - fieldArgDefs, - fieldArgs, - executionContext.getCoercedVariables(), - executionContext.getGraphQLContext(), - executionContext.getLocale()); - - return ImmutableMapWithNullValues.copyOf(resolvedValues); - }; - argumentValues = FpKit.intraThreadMemoize(argValuesSupplier); - return argumentValues; + return executionStepInfoFactory.createExecutionStepInfo(executionContext, + parameters, + fieldDefinition, + fieldContainer); } // Errors that result from the execution of deferred fields are kept in the deferred context only. diff --git a/src/main/java/graphql/execution/ExecutionStrategyParameters.java b/src/main/java/graphql/execution/ExecutionStrategyParameters.java index c2e46b9a67..58eb3d1767 100644 --- a/src/main/java/graphql/execution/ExecutionStrategyParameters.java +++ b/src/main/java/graphql/execution/ExecutionStrategyParameters.java @@ -1,5 +1,6 @@ package graphql.execution; +import graphql.Internal; import graphql.PublicApi; import graphql.execution.incremental.DeferredCallContext; import org.jspecify.annotations.Nullable; @@ -37,7 +38,7 @@ private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo, this.localContext = localContext; this.fields = assertNotNull(fields, () -> "fields is null"); this.source = source; - this.nonNullableFieldValidator = nonNullableFieldValidator; + this.nonNullableFieldValidator = assertNotNull(nonNullableFieldValidator, () -> "requires a NonNullValidator");; this.path = path; this.currentField = currentField; this.parent = parent; @@ -115,6 +116,81 @@ public MergedField getField() { return currentField; } + @Internal + ExecutionStrategyParameters transform(MergedField currentField, + ResultPath path) { + return new ExecutionStrategyParameters(executionStepInfo, + source, + localContext, + fields, + nonNullableFieldValidator, + path, + currentField, + parent, + deferredCallContext); + } + + @Internal + ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, + MergedSelectionSet fields, + Object source) { + return new ExecutionStrategyParameters(executionStepInfo, + source, + localContext, + fields, + nonNullableFieldValidator, + path, + currentField, + parent, + deferredCallContext); + } + + @Internal + ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, + ResultPath path, + Object localContext, + Object source) { + return new ExecutionStrategyParameters(executionStepInfo, + source, + localContext, + fields, + nonNullableFieldValidator, + path, + currentField, + parent, + deferredCallContext); + } + + @Internal + ExecutionStrategyParameters transform(ExecutionStepInfo executionStepInfo, + Object localContext, + Object source) { + return new ExecutionStrategyParameters(executionStepInfo, + source, + localContext, + fields, + nonNullableFieldValidator, + path, + currentField, + parent, + deferredCallContext); + } + + @Internal + ExecutionStrategyParameters transform(MergedField currentField, + ResultPath path, + ExecutionStrategyParameters parent) { + return new ExecutionStrategyParameters(executionStepInfo, + source, + localContext, + fields, + nonNullableFieldValidator, + path, + currentField, + parent, + deferredCallContext); + } + public ExecutionStrategyParameters transform(Consumer builderConsumer) { Builder builder = newParameters(this); builderConsumer.accept(builder); diff --git a/src/main/java/graphql/execution/NonNullableFieldValidator.java b/src/main/java/graphql/execution/NonNullableFieldValidator.java index d7e14900a4..b59f633bac 100644 --- a/src/main/java/graphql/execution/NonNullableFieldValidator.java +++ b/src/main/java/graphql/execution/NonNullableFieldValidator.java @@ -14,15 +14,13 @@ public class NonNullableFieldValidator { private final ExecutionContext executionContext; - private final ExecutionStepInfo executionStepInfo; - public NonNullableFieldValidator(ExecutionContext executionContext, ExecutionStepInfo executionStepInfo) { + public NonNullableFieldValidator(ExecutionContext executionContext) { this.executionContext = executionContext; - this.executionStepInfo = executionStepInfo; } /** - * Called to check that a value is non null if the type requires it to be non null + * Called to check that a value is non-null if the type requires it to be non null * * @param parameters the execution strategy parameters * @param result the result to check @@ -34,6 +32,7 @@ public NonNullableFieldValidator(ExecutionContext executionContext, ExecutionSte */ public T validate(ExecutionStrategyParameters parameters, T result) throws NonNullableFieldWasNullException { if (result == null) { + ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo(); if (executionStepInfo.isNonNullType()) { // see https://spec.graphql.org/October2021/#sec-Errors-and-Non-Nullability // diff --git a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java index 464f725946..365e3e3737 100644 --- a/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java +++ b/src/main/java/graphql/execution/SubscriptionExecutionStrategy.java @@ -106,7 +106,7 @@ private boolean keepOrdered(GraphQLContext graphQLContext) { */ private CompletableFuture> createSourceEventStream(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { - ExecutionStrategyParameters newParameters = firstFieldOfSubscriptionSelection(parameters); + ExecutionStrategyParameters newParameters = firstFieldOfSubscriptionSelection(executionContext,parameters); CompletableFuture fieldFetched = Async.toCompletableFuture(fetchField(executionContext, newParameters)); return fieldFetched.thenApply(fetchedValue -> { @@ -139,7 +139,7 @@ private CompletableFuture executeSubscriptionEvent(ExecutionCon .root(eventPayload) .resetErrors() ); - ExecutionStrategyParameters newParameters = firstFieldOfSubscriptionSelection(parameters); + ExecutionStrategyParameters newParameters = firstFieldOfSubscriptionSelection(newExecutionContext, parameters); ExecutionStepInfo subscribedFieldStepInfo = createSubscribedFieldStepInfo(executionContext, newParameters); InstrumentationFieldParameters i13nFieldParameters = new InstrumentationFieldParameters(executionContext, () -> subscribedFieldStepInfo); @@ -179,12 +179,14 @@ private String getRootFieldName(ExecutionStrategyParameters parameters) { return rootField.getResultKey(); } - private ExecutionStrategyParameters firstFieldOfSubscriptionSelection(ExecutionStrategyParameters parameters) { + private ExecutionStrategyParameters firstFieldOfSubscriptionSelection(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { MergedSelectionSet fields = parameters.getFields(); MergedField firstField = fields.getSubField(fields.getKeys().get(0)); ResultPath fieldPath = parameters.getPath().segment(mkNameForPath(firstField.getSingleField())); - return parameters.transform(builder -> builder.field(firstField).path(fieldPath)); + NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext); + return parameters.transform(builder -> builder + .field(firstField).path(fieldPath).nonNullFieldValidator(nonNullableFieldValidator)); } private ExecutionStepInfo createSubscribedFieldStepInfo(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { diff --git a/src/main/java/graphql/execution/ValuesResolverConversion.java b/src/main/java/graphql/execution/ValuesResolverConversion.java index c53eeb64e8..29e2587ab8 100644 --- a/src/main/java/graphql/execution/ValuesResolverConversion.java +++ b/src/main/java/graphql/execution/ValuesResolverConversion.java @@ -602,16 +602,18 @@ private static List externalValueToInternalValueForList( ) throws CoercingParseValueException, NonNullableValueCoercedAsNullException { GraphQLInputType wrappedType = (GraphQLInputType) graphQLList.getWrappedType(); - return FpKit.toListOrSingletonList(value) - .stream() - .map(val -> externalValueToInternalValueImpl( - inputInterceptor, - fieldVisibility, - wrappedType, - val, - graphqlContext, - locale)) - .collect(toList()); + List listOrSingletonList = FpKit.toListOrSingletonList(value); + List list = FpKit.arrayListSizedTo(listOrSingletonList); + for (Object val : listOrSingletonList) { + list.add(externalValueToInternalValueImpl( + inputInterceptor, + fieldVisibility, + wrappedType, + val, + graphqlContext, + locale)); + } + return list; } /** diff --git a/src/main/java/graphql/execution/ValuesResolverLegacy.java b/src/main/java/graphql/execution/ValuesResolverLegacy.java index d5e58f4656..d98a744f7c 100644 --- a/src/main/java/graphql/execution/ValuesResolverLegacy.java +++ b/src/main/java/graphql/execution/ValuesResolverLegacy.java @@ -133,10 +133,11 @@ private static Value handleNumberLegacy(String stringValue) { private static Value handleListLegacy(Object value, GraphQLList type, GraphQLContext graphqlContext, Locale locale) { GraphQLType itemType = type.getWrappedType(); if (FpKit.isIterable(value)) { - List valuesNodes = FpKit.toListOrSingletonList(value) - .stream() - .map(item -> valueToLiteralLegacy(item, itemType, graphqlContext, locale)) - .collect(toList()); + List listOrSingletonList = FpKit.toListOrSingletonList(value); + List valuesNodes = FpKit.arrayListSizedTo(listOrSingletonList); + for (Object item : listOrSingletonList) { + valuesNodes.add(valueToLiteralLegacy(item, itemType, graphqlContext, locale)); + } return ArrayValue.newArrayValue().values(valuesNodes).build(); } return valueToLiteralLegacy(value, itemType, graphqlContext, locale); diff --git a/src/main/java/graphql/execution/incremental/DeferredExecutionSupport.java b/src/main/java/graphql/execution/incremental/DeferredExecutionSupport.java index 3b8e7efe8a..cbe73045b8 100644 --- a/src/main/java/graphql/execution/incremental/DeferredExecutionSupport.java +++ b/src/main/java/graphql/execution/incremental/DeferredExecutionSupport.java @@ -11,12 +11,14 @@ import graphql.execution.FieldValueInfo; import graphql.execution.MergedField; import graphql.execution.MergedSelectionSet; +import graphql.execution.ResultPath; import graphql.execution.instrumentation.Instrumentation; import graphql.incremental.IncrementalPayload; import graphql.util.FpKit; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -24,7 +26,6 @@ import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; import java.util.function.Supplier; -import java.util.stream.Collectors; /** * The purpose of this class hierarchy is to encapsulate most of the logic for deferring field execution, thus @@ -106,9 +107,12 @@ public List getNonDeferredFieldNames(List allFieldNames) { @Override public Set> createCalls(ExecutionStrategyParameters executionStrategyParameters) { - return deferredExecutionToFields.keySet().stream() - .map(deferredExecution -> this.createDeferredFragmentCall(deferredExecution, executionStrategyParameters)) - .collect(Collectors.toSet()); + ImmutableSet deferredExecutions = deferredExecutionToFields.keySet(); + Set> set = new HashSet<>(deferredExecutions.size()); + for (DeferredExecution deferredExecution : deferredExecutions) { + set.add(this.createDeferredFragmentCall(deferredExecution, executionStrategyParameters)); + } + return set; } private DeferredFragmentCall createDeferredFragmentCall(DeferredExecution deferredExecution, ExecutionStrategyParameters executionStrategyParameters) { @@ -116,9 +120,10 @@ private DeferredFragmentCall createDeferredFragmentCall(DeferredExecution deferr List mergedFields = deferredExecutionToFields.get(deferredExecution); - List>> calls = mergedFields.stream() - .map(currentField -> this.createResultSupplier(currentField, deferredCallContext, executionStrategyParameters)) - .collect(Collectors.toList()); + List>> calls = FpKit.arrayListSizedTo(mergedFields); + for (MergedField currentField : mergedFields) { + calls.add(this.createResultSupplier(currentField, deferredCallContext, executionStrategyParameters)); + } return new DeferredFragmentCall( deferredExecution.getLabel(), @@ -139,10 +144,11 @@ private Supplier { MergedSelectionSet mergedSelectionSet = MergedSelectionSet.newMergedSelectionSet().subFields(fields).build(); + ResultPath path = parameters.getPath().segment(currentField.getResultKey()); builder.deferredCallContext(deferredCallContext) .field(currentField) .fields(mergedSelectionSet) - .path(parameters.getPath().segment(currentField.getResultKey())) + .path(path) .parent(null); // this is a break in the parent -> child chain - it's a new start effectively } ); diff --git a/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategyWithDeferAlwaysDispatch.java b/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategyWithDeferAlwaysDispatch.java index 26c847b754..d7c84f4789 100644 --- a/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategyWithDeferAlwaysDispatch.java +++ b/src/main/java/graphql/execution/instrumentation/dataloader/PerLevelDataLoaderDispatchStrategyWithDeferAlwaysDispatch.java @@ -6,6 +6,7 @@ import graphql.execution.ExecutionContext; import graphql.execution.ExecutionStrategyParameters; import graphql.execution.FieldValueInfo; +import graphql.execution.MergedField; import graphql.schema.DataFetcher; import graphql.util.LockKit; import org.dataloader.DataLoaderRegistry; @@ -43,55 +44,81 @@ private static class CallStack { private final LockKit.ReentrantLock lock = new LockKit.ReentrantLock(); private final LevelMap expectedFetchCountPerLevel = new LevelMap(); private final LevelMap fetchCountPerLevel = new LevelMap(); - private final LevelMap expectedStrategyCallsPerLevel = new LevelMap(); - private final LevelMap happenedStrategyCallsPerLevel = new LevelMap(); + + private final LevelMap expectedExecuteObjectCallsPerLevel = new LevelMap(); + private final LevelMap happenedExecuteObjectCallsPerLevel = new LevelMap(); + private final LevelMap happenedOnFieldValueCallsPerLevel = new LevelMap(); private final Set dispatchedLevels = new LinkedHashSet<>(); public CallStack() { - expectedStrategyCallsPerLevel.set(1, 1); + expectedExecuteObjectCallsPerLevel.set(1, 1); } void increaseExpectedFetchCount(int level, int count) { expectedFetchCountPerLevel.increment(level, count); } + void clearExpectedFetchCount() { + expectedFetchCountPerLevel.clear(); + } + void increaseFetchCount(int level) { fetchCountPerLevel.increment(level, 1); } - void increaseExpectedStrategyCalls(int level, int count) { - expectedStrategyCallsPerLevel.increment(level, count); + void clearFetchCount() { + fetchCountPerLevel.clear(); + } + + void increaseExpectedExecuteObjectCalls(int level, int count) { + expectedExecuteObjectCallsPerLevel.increment(level, count); } - void increaseHappenedStrategyCalls(int level) { - happenedStrategyCallsPerLevel.increment(level, 1); + void clearExpectedObjectCalls() { + expectedExecuteObjectCallsPerLevel.clear(); + } + + void increaseHappenedExecuteObjectCalls(int level) { + happenedExecuteObjectCallsPerLevel.increment(level, 1); + } + + void clearHappenedExecuteObjectCalls() { + happenedExecuteObjectCallsPerLevel.clear(); } void increaseHappenedOnFieldValueCalls(int level) { happenedOnFieldValueCallsPerLevel.increment(level, 1); } - boolean allStrategyCallsHappened(int level) { - return happenedStrategyCallsPerLevel.get(level) == expectedStrategyCallsPerLevel.get(level); + void clearHappenedOnFieldValueCalls() { + happenedOnFieldValueCallsPerLevel.clear(); + } + + boolean allExecuteObjectCallsHappened(int level) { + return happenedExecuteObjectCallsPerLevel.get(level) == expectedExecuteObjectCallsPerLevel.get(level); } boolean allOnFieldCallsHappened(int level) { - return happenedOnFieldValueCallsPerLevel.get(level) == expectedStrategyCallsPerLevel.get(level); + return happenedOnFieldValueCallsPerLevel.get(level) == expectedExecuteObjectCallsPerLevel.get(level); } boolean allFetchesHappened(int level) { return fetchCountPerLevel.get(level) == expectedFetchCountPerLevel.get(level); } + void clearDispatchLevels() { + dispatchedLevels.clear(); + } + @Override public String toString() { return "CallStack{" + "expectedFetchCountPerLevel=" + expectedFetchCountPerLevel + ", fetchCountPerLevel=" + fetchCountPerLevel + - ", expectedStrategyCallsPerLevel=" + expectedStrategyCallsPerLevel + - ", happenedStrategyCallsPerLevel=" + happenedStrategyCallsPerLevel + + ", expectedExecuteObjectCallsPerLevel=" + expectedExecuteObjectCallsPerLevel + + ", happenedExecuteObjectCallsPerLevel=" + happenedExecuteObjectCallsPerLevel + ", happenedOnFieldValueCallsPerLevel=" + happenedOnFieldValueCallsPerLevel + ", dispatchedLevels" + dispatchedLevels + '}'; @@ -124,16 +151,14 @@ public void executionStrategy(ExecutionContext executionContext, ExecutionStrate return; } int curLevel = parameters.getExecutionStepInfo().getPath().getLevel() + 1; - increaseCallCounts(curLevel, parameters); + increaseHappenedExecuteObjectAndIncreaseExpectedFetchCount(curLevel, parameters); + } @Override - public void executeObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { - if (this.startedDeferredExecution.get()) { - return; - } - int curLevel = parameters.getExecutionStepInfo().getPath().getLevel() + 1; - increaseCallCounts(curLevel, parameters); + public void executionSerialStrategy(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { + resetCallStack(); + increaseHappenedExecuteObjectAndIncreaseExpectedFetchCount(1, 1); } @Override @@ -144,13 +169,24 @@ public void executionStrategyOnFieldValuesInfo(List fieldValueIn onFieldValuesInfoDispatchIfNeeded(fieldValueInfoList, 1); } - @Override public void executionStrategyOnFieldValuesException(Throwable t) { callStack.lock.runLocked(() -> callStack.increaseHappenedOnFieldValueCalls(1) ); } + + @Override + public void executeObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { + if (this.startedDeferredExecution.get()) { + return; + } + int curLevel = parameters.getExecutionStepInfo().getPath().getLevel() + 1; + increaseHappenedExecuteObjectAndIncreaseExpectedFetchCount(curLevel, parameters); + } + + + @Override public void executeObjectOnFieldValuesInfo(List fieldValueInfoList, ExecutionStrategyParameters parameters) { if (this.startedDeferredExecution.get()) { @@ -169,42 +205,34 @@ public void executeObjectOnFieldValuesException(Throwable t, ExecutionStrategyPa ); } - @Override - public void fieldFetched(ExecutionContext executionContext, - ExecutionStrategyParameters parameters, - DataFetcher dataFetcher, - Object fetchedValue) { - - final boolean dispatchNeeded; - - if (parameters.getField().isDeferred() || this.startedDeferredExecution.get()) { - this.startedDeferredExecution.set(true); - dispatchNeeded = true; - } else { - int level = parameters.getPath().getLevel(); - dispatchNeeded = callStack.lock.callLocked(() -> { - callStack.increaseFetchCount(level); - return dispatchIfNeeded(level); - }); - } - - if (dispatchNeeded) { - dispatch(); + private void increaseHappenedExecuteObjectAndIncreaseExpectedFetchCount(int curLevel, ExecutionStrategyParameters parameters) { + int nonDeferredFields = 0; + for (MergedField field : parameters.getFields().getSubFieldsList()) { + if (!field.isDeferred()) { + nonDeferredFields++; + } } - + increaseHappenedExecuteObjectAndIncreaseExpectedFetchCount(curLevel, nonDeferredFields); } - private void increaseCallCounts(int curLevel, ExecutionStrategyParameters parameters) { - int nonDeferredFieldCount = (int) parameters.getFields().getSubFieldsList().stream() - .filter(field -> !field.isDeferred()) - .count(); - + private void increaseHappenedExecuteObjectAndIncreaseExpectedFetchCount(int curLevel, int fieldCount) { callStack.lock.runLocked(() -> { - callStack.increaseExpectedFetchCount(curLevel, nonDeferredFieldCount); - callStack.increaseHappenedStrategyCalls(curLevel); + callStack.increaseHappenedExecuteObjectCalls(curLevel); + callStack.increaseExpectedFetchCount(curLevel, fieldCount); }); } + private void resetCallStack() { + callStack.lock.runLocked(() -> { + callStack.clearDispatchLevels(); + callStack.clearExpectedObjectCalls(); + callStack.clearExpectedFetchCount(); + callStack.clearFetchCount(); + callStack.clearHappenedExecuteObjectCalls(); + callStack.clearHappenedOnFieldValueCalls(); + callStack.expectedExecuteObjectCallsPerLevel.set(1, 1); + }); + } private void onFieldValuesInfoDispatchIfNeeded(List fieldValueInfoList, int curLevel) { boolean dispatchNeeded = callStack.lock.callLocked(() -> handleOnFieldValuesInfo(fieldValueInfoList, curLevel) @@ -219,23 +247,53 @@ private void onFieldValuesInfoDispatchIfNeeded(List fieldValueIn // private boolean handleOnFieldValuesInfo(List fieldValueInfos, int curLevel) { callStack.increaseHappenedOnFieldValueCalls(curLevel); - int expectedStrategyCalls = getCountForList(fieldValueInfos); - callStack.increaseExpectedStrategyCalls(curLevel + 1, expectedStrategyCalls); + int expectedStrategyCalls = getObjectCountForList(fieldValueInfos); + callStack.increaseExpectedExecuteObjectCalls(curLevel + 1, expectedStrategyCalls); return dispatchIfNeeded(curLevel + 1); } - private int getCountForList(List fieldValueInfos) { + /** + * the amount of (non nullable) objects that will require an execute object call + */ + private int getObjectCountForList(List fieldValueInfos) { int result = 0; for (FieldValueInfo fieldValueInfo : fieldValueInfos) { if (fieldValueInfo.getCompleteValueType() == FieldValueInfo.CompleteValueType.OBJECT) { result += 1; } else if (fieldValueInfo.getCompleteValueType() == FieldValueInfo.CompleteValueType.LIST) { - result += getCountForList(fieldValueInfo.getFieldValueInfos()); + result += getObjectCountForList(fieldValueInfo.getFieldValueInfos()); } } return result; } + + @Override + public void fieldFetched(ExecutionContext executionContext, + ExecutionStrategyParameters executionStrategyParameters, + DataFetcher dataFetcher, + Object fetchedValue) { + + final boolean dispatchNeeded; + + if (executionStrategyParameters.getField().isDeferred() || this.startedDeferredExecution.get()) { + this.startedDeferredExecution.set(true); + dispatchNeeded = true; + } else { + int level = executionStrategyParameters.getPath().getLevel(); + dispatchNeeded = callStack.lock.callLocked(() -> { + callStack.increaseFetchCount(level); + return dispatchIfNeeded(level); + }); + } + + if (dispatchNeeded) { + dispatch(); + } + + } + + // // thread safety : called with callStack.lock // @@ -256,7 +314,7 @@ private boolean levelReady(int level) { return callStack.allFetchesHappened(1); } if (levelReady(level - 1) && callStack.allOnFieldCallsHappened(level - 1) - && callStack.allStrategyCallsHappened(level) && callStack.allFetchesHappened(level)) { + && callStack.allExecuteObjectCallsHappened(level) && callStack.allFetchesHappened(level)) { return true; } diff --git a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationSupport.java b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationSupport.java index 888a012cc4..454fb30677 100644 --- a/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationSupport.java +++ b/src/main/java/graphql/execution/instrumentation/fieldvalidation/FieldValidationSupport.java @@ -7,6 +7,7 @@ import graphql.analysis.QueryTraverser; import graphql.analysis.QueryVisitorFieldEnvironment; import graphql.analysis.QueryVisitorStub; +import graphql.collect.ImmutableKit; import graphql.execution.ExecutionContext; import graphql.execution.ResultPath; import graphql.language.Field; @@ -140,7 +141,7 @@ private static class FieldValidationEnvironmentImpl implements FieldValidationEn FieldValidationEnvironmentImpl(ExecutionContext executionContext, Map> fieldArgumentsMap) { this.executionContext = executionContext; this.fieldArgumentsMap = fieldArgumentsMap; - this.fieldArguments = fieldArgumentsMap.values().stream().flatMap(List::stream).collect(ImmutableList.toImmutableList()); + this.fieldArguments = ImmutableKit.flatMapList(fieldArgumentsMap.values()); } diff --git a/src/main/java/graphql/incremental/DelayedIncrementalPartialResultImpl.java b/src/main/java/graphql/incremental/DelayedIncrementalPartialResultImpl.java index 461d658e7d..3412d77298 100644 --- a/src/main/java/graphql/incremental/DelayedIncrementalPartialResultImpl.java +++ b/src/main/java/graphql/incremental/DelayedIncrementalPartialResultImpl.java @@ -1,12 +1,12 @@ package graphql.incremental; import graphql.ExperimentalApi; +import graphql.util.FpKit; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; @ExperimentalApi public class DelayedIncrementalPartialResultImpl implements DelayedIncrementalPartialResult { @@ -44,10 +44,12 @@ public Map toSpecification() { result.put("extensions", extensions); } - if(incrementalItems != null) { - result.put("incremental", incrementalItems.stream() - .map(IncrementalPayload::toSpecification) - .collect(Collectors.toList())); + if (incrementalItems != null) { + List> list = FpKit.arrayListSizedTo(incrementalItems); + for (IncrementalPayload incrementalItem : incrementalItems) { + list.add(incrementalItem.toSpecification()); + } + result.put("incremental", list); } return result; diff --git a/src/main/java/graphql/incremental/IncrementalExecutionResultImpl.java b/src/main/java/graphql/incremental/IncrementalExecutionResultImpl.java index 8bd8e62a01..2f9470b949 100644 --- a/src/main/java/graphql/incremental/IncrementalExecutionResultImpl.java +++ b/src/main/java/graphql/incremental/IncrementalExecutionResultImpl.java @@ -11,7 +11,6 @@ import java.util.List; import java.util.Map; import java.util.function.Consumer; -import java.util.stream.Collectors; @ExperimentalApi public class IncrementalExecutionResultImpl extends ExecutionResultImpl implements IncrementalExecutionResult { @@ -66,11 +65,11 @@ public Map toSpecification() { map.put("hasNext", hasNext); if (this.incremental != null) { - map.put("incremental", - this.incremental.stream() - .map(IncrementalPayload::toSpecification) - .collect(Collectors.toCollection(LinkedList::new)) - ); + LinkedList> linkedList = new LinkedList<>(); + for (IncrementalPayload incrementalPayload : this.incremental) { + linkedList.add(incrementalPayload.toSpecification()); + } + map.put("incremental", linkedList); } return map; diff --git a/src/main/java/graphql/incremental/IncrementalPayload.java b/src/main/java/graphql/incremental/IncrementalPayload.java index efeba39290..742d857c89 100644 --- a/src/main/java/graphql/incremental/IncrementalPayload.java +++ b/src/main/java/graphql/incremental/IncrementalPayload.java @@ -3,6 +3,7 @@ import graphql.ExperimentalApi; import graphql.GraphQLError; import graphql.execution.ResultPath; +import graphql.util.FpKit; import org.jspecify.annotations.Nullable; import java.util.ArrayList; @@ -11,8 +12,6 @@ import java.util.Map; import java.util.Objects; -import static java.util.stream.Collectors.toList; - /** * Represents a payload that can be resolved after the initial response. */ @@ -80,7 +79,11 @@ public Map toSpecification() { } protected Object errorsToSpec(List errors) { - return errors.stream().map(GraphQLError::toSpecification).collect(toList()); + List> list = FpKit.arrayListSizedTo(errors); + for (GraphQLError error : errors) { + list.add(error.toSpecification()); + } + return list; } public int hashCode() { @@ -88,8 +91,12 @@ public int hashCode() { } public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null || getClass() != obj.getClass()) return false; + if (this == obj) { + return true; + } + if (obj == null || getClass() != obj.getClass()) { + return false; + } IncrementalPayload that = (IncrementalPayload) obj; return Objects.equals(path, that.path) && Objects.equals(label, that.label) && diff --git a/src/main/java/graphql/language/AstSignature.java b/src/main/java/graphql/language/AstSignature.java index 84b1d0e871..f6964305b2 100644 --- a/src/main/java/graphql/language/AstSignature.java +++ b/src/main/java/graphql/language/AstSignature.java @@ -1,6 +1,5 @@ package graphql.language; -import com.google.common.collect.ImmutableList; import graphql.PublicApi; import graphql.collect.ImmutableKit; import graphql.util.TraversalControl; @@ -149,16 +148,15 @@ private Document dropUnusedQueryDefinitions(Document document, final String oper NodeVisitorStub visitor = new NodeVisitorStub() { @Override public TraversalControl visitDocument(Document node, TraverserContext context) { - List wantedDefinitions = node.getDefinitions().stream() - .filter(d -> { + List wantedDefinitions = ImmutableKit.filter(node.getDefinitions(), + d -> { if (d instanceof OperationDefinition) { OperationDefinition operationDefinition = (OperationDefinition) d; return isThisOperation(operationDefinition, operationName); } return d instanceof FragmentDefinition; // SDL in a query makes no sense - its gone should it be present - }) - .collect(ImmutableList.toImmutableList()); + }); Document changedNode = node.transform(builder -> { builder.definitions(wantedDefinitions); diff --git a/src/main/java/graphql/language/Document.java b/src/main/java/graphql/language/Document.java index 3fdd459640..1f613686e4 100644 --- a/src/main/java/graphql/language/Document.java +++ b/src/main/java/graphql/language/Document.java @@ -55,10 +55,9 @@ public List getDefinitions() { * @return a list of definitions of that class or empty list */ public List getDefinitionsOfType(Class definitionClass) { - return definitions.stream() - .filter(d -> definitionClass.isAssignableFrom(d.getClass())) - .map(definitionClass::cast) - .collect(ImmutableList.toImmutableList()); + return ImmutableKit.filterAndMap(definitions, + d -> definitionClass.isAssignableFrom(d.getClass()), + definitionClass::cast); } /** diff --git a/src/main/java/graphql/language/NodeParentTree.java b/src/main/java/graphql/language/NodeParentTree.java index fc78ea093d..a5e51d89fc 100644 --- a/src/main/java/graphql/language/NodeParentTree.java +++ b/src/main/java/graphql/language/NodeParentTree.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableList; import graphql.Internal; import graphql.PublicApi; +import graphql.collect.ImmutableKit; import java.util.ArrayDeque; import java.util.ArrayList; @@ -42,10 +43,9 @@ public NodeParentTree(Deque nodeStack) { } private ImmutableList mkPath(Deque copy) { - return copy.stream() - .filter(node1 -> node1 instanceof NamedNode) - .map(node1 -> ((NamedNode) node1).getName()) - .collect(ImmutableList.toImmutableList()); + return ImmutableKit.filterAndMap(copy, + node1 -> node1 instanceof NamedNode, + node1 -> ((NamedNode) node1).getName()); } diff --git a/src/main/java/graphql/language/SelectionSet.java b/src/main/java/graphql/language/SelectionSet.java index 2ff152657c..8e85bdcdef 100644 --- a/src/main/java/graphql/language/SelectionSet.java +++ b/src/main/java/graphql/language/SelectionSet.java @@ -54,10 +54,9 @@ public List getSelections() { * @return a list of selections of that class or empty list */ public List getSelectionsOfType(Class selectionClass) { - return selections.stream() - .filter(d -> selectionClass.isAssignableFrom(d.getClass())) - .map(selectionClass::cast) - .collect(ImmutableList.toImmutableList()); + return ImmutableKit.filterAndMap(selections, + d -> selectionClass.isAssignableFrom(d.getClass()), + selectionClass::cast); } @Override diff --git a/src/main/java/graphql/schema/SingletonPropertyDataFetcher.java b/src/main/java/graphql/schema/SingletonPropertyDataFetcher.java index 45af96c843..8455963f0f 100644 --- a/src/main/java/graphql/schema/SingletonPropertyDataFetcher.java +++ b/src/main/java/graphql/schema/SingletonPropertyDataFetcher.java @@ -15,7 +15,18 @@ public class SingletonPropertyDataFetcher implements LightDataFetcher { private static final SingletonPropertyDataFetcher SINGLETON_FETCHER = new SingletonPropertyDataFetcher<>(); - private static final DataFetcherFactory SINGLETON_FETCHER_FACTORY = environment -> SINGLETON_FETCHER; + private static final DataFetcherFactory SINGLETON_FETCHER_FACTORY = new DataFetcherFactory() { + @SuppressWarnings("deprecation") + @Override + public DataFetcher get(DataFetcherFactoryEnvironment environment) { + return SINGLETON_FETCHER; + } + + @Override + public DataFetcher get(GraphQLFieldDefinition fieldDefinition) { + return SINGLETON_FETCHER; + } + }; /** * This returns the same singleton {@link LightDataFetcher} that fetches property values diff --git a/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java b/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java index 51ced4aba2..5ff38f5756 100644 --- a/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java +++ b/src/main/java/graphql/schema/fetching/LambdaFetchingSupport.java @@ -2,6 +2,7 @@ import graphql.Internal; import graphql.VisibleForTesting; +import graphql.util.FpKit; import java.lang.invoke.CallSite; import java.lang.invoke.LambdaMetafactory; @@ -17,8 +18,6 @@ import java.util.function.Function; import java.util.function.Predicate; -import static java.util.stream.Collectors.toList; - @Internal public class LambdaFetchingSupport { @@ -69,9 +68,12 @@ private static Method getCandidateMethod(Class sourceClass, String propertyNa Predicate getterPredicate = method -> isGetterNamed(method) && propertyName.equals(mkPropertyNameGetter(method)); List allGetterMethods = findMethodsForProperty(sourceClass, getterPredicate); - List pojoGetterMethods = allGetterMethods.stream() - .filter(LambdaFetchingSupport::isPossiblePojoMethod) - .collect(toList()); + List pojoGetterMethods = FpKit.arrayListSizedTo(allGetterMethods); + for (Method allGetterMethod : allGetterMethods) { + if (isPossiblePojoMethod(allGetterMethod)) { + pojoGetterMethods.add(allGetterMethod); + } + } if (!pojoGetterMethods.isEmpty()) { Method method = pojoGetterMethods.get(0); if (isBooleanGetter(method)) { @@ -97,7 +99,13 @@ private static Method checkForSingleParameterPeer(Method candidateMethod, List methods) { // we prefer isX() over getX() if both happen to be present - Optional isMethod = methods.stream().filter(method -> method.getName().startsWith("is")).findFirst(); + Optional isMethod = Optional.empty(); + for (Method method : methods) { + if (method.getName().startsWith("is")) { + isMethod = Optional.of(method); + break; + } + } return isMethod.orElse(methods.get(0)); } @@ -121,9 +129,9 @@ private static List findMethodsForProperty(Class sourceClass, Predica currentClass = currentClass.getSuperclass(); } - return methods.stream() - .sorted(Comparator.comparing(Method::getName)) - .collect(toList()); + List list = new ArrayList<>(methods); + list.sort(Comparator.comparing(Method::getName)); + return list; } private static boolean isPossiblePojoMethod(Method method) { diff --git a/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java b/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java index 303b81b9c8..325854129c 100644 --- a/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java +++ b/src/main/java/graphql/schema/transform/FieldVisibilitySchemaTransformation.java @@ -62,9 +62,10 @@ public final GraphQLSchema apply(GraphQLSchema schema) { Set markedForRemovalTypes = new HashSet<>(); // query, mutation, and subscription types should not be removed - final Set protectedTypeNames = getOperationTypes(schema).stream() - .map(GraphQLObjectType::getName) - .collect(Collectors.toSet()); + final Set protectedTypeNames = new HashSet<>(); + for (GraphQLObjectType graphQLObjectType : getOperationTypes(schema)) { + protectedTypeNames.add(graphQLObjectType.getName()); + } beforeTransformationHook.run(); diff --git a/src/main/java/graphql/schema/visibility/BlockedFields.java b/src/main/java/graphql/schema/visibility/BlockedFields.java index 57bd555bc5..937d029d83 100644 --- a/src/main/java/graphql/schema/visibility/BlockedFields.java +++ b/src/main/java/graphql/schema/visibility/BlockedFields.java @@ -1,8 +1,8 @@ package graphql.schema.visibility; -import com.google.common.collect.ImmutableList; import graphql.Internal; import graphql.PublicApi; +import graphql.collect.ImmutableKit; import graphql.schema.GraphQLFieldDefinition; import graphql.schema.GraphQLFieldsContainer; import graphql.schema.GraphQLInputFieldsContainer; @@ -35,9 +35,8 @@ private BlockedFields(List patterns) { @Override public List getFieldDefinitions(GraphQLFieldsContainer fieldsContainer) { - return fieldsContainer.getFieldDefinitions().stream() - .filter(fieldDefinition -> !block(mkFQN(fieldsContainer.getName(), fieldDefinition.getName()))) - .collect(ImmutableList.toImmutableList()); + return ImmutableKit.filter(fieldsContainer.getFieldDefinitions(), + fieldDefinition -> !block(mkFQN(fieldsContainer.getName(), fieldDefinition.getName()))); } @Override @@ -53,9 +52,8 @@ public GraphQLFieldDefinition getFieldDefinition(GraphQLFieldsContainer fieldsCo @Override public List getFieldDefinitions(GraphQLInputFieldsContainer fieldsContainer) { - return fieldsContainer.getFieldDefinitions().stream() - .filter(fieldDefinition -> !block(mkFQN(fieldsContainer.getName(), fieldDefinition.getName()))) - .collect(ImmutableList.toImmutableList()); + return ImmutableKit.filter(fieldsContainer.getFieldDefinitions(), + fieldDefinition -> !block(mkFQN(fieldsContainer.getName(), fieldDefinition.getName()))); } @Override diff --git a/src/main/java/graphql/util/FpKit.java b/src/main/java/graphql/util/FpKit.java index a538450ad3..f7f8c00519 100644 --- a/src/main/java/graphql/util/FpKit.java +++ b/src/main/java/graphql/util/FpKit.java @@ -5,10 +5,12 @@ import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import graphql.Internal; +import org.jspecify.annotations.NonNull; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -18,17 +20,13 @@ import java.util.OptionalInt; import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.function.BiFunction; import java.util.function.BinaryOperator; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; -import java.util.stream.Collectors; import java.util.stream.Stream; import static java.util.Collections.singletonList; -import static java.util.function.Function.identity; -import static java.util.stream.Collectors.mapping; @Internal public class FpKit { @@ -36,51 +34,76 @@ public class FpKit { // // From a list of named things, get a map of them by name, merging them according to the merge function public static Map getByName(List namedObjects, Function nameFn, BinaryOperator mergeFunc) { - return namedObjects.stream().collect(Collectors.toMap( - nameFn, - identity(), - mergeFunc, - LinkedHashMap::new) - ); + return toMap(namedObjects, nameFn, mergeFunc); + } + + // + // From a collection of keyed things, get a map of them by key, merging them according to the merge function + public static Map toMap(Collection collection, Function keyFunction, BinaryOperator mergeFunc) { + Map resultMap = new LinkedHashMap<>(); + for (T obj : collection) { + NewKey key = keyFunction.apply(obj); + if (resultMap.containsKey(key)) { + T existingValue = resultMap.get(key); + T mergedValue = mergeFunc.apply(existingValue, obj); + resultMap.put(key, mergedValue); + } else { + resultMap.put(key, obj); + } + } + return resultMap; } // normal groupingBy but with LinkedHashMap public static Map> groupingBy(Collection list, Function function) { - return list.stream().collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); + return filterAndGroupingBy(list, ALWAYS_TRUE, function); } + @SuppressWarnings("unchecked") public static Map> filterAndGroupingBy(Collection list, Predicate predicate, Function function) { - return list.stream().filter(predicate).collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); - } + // + // The cleanest version of this code would have two maps, one of immutable list builders and one + // of the built immutable lists. BUt we are trying to be performant and memory efficient so + // we treat it as a map of objects and cast like its Java 4x + // + Map resutMap = new LinkedHashMap<>(); + for (T item : list) { + if (predicate.test(item)) { + NewKey key = function.apply(item); + // we have to use an immutable list builder as we built it out + ((ImmutableList.Builder) resutMap.computeIfAbsent(key, k -> ImmutableList.builder())) + .add(item); + } + } + if (resutMap.isEmpty()) { + return Collections.emptyMap(); + } + // Convert builders to ImmutableLists in place to avoid an extra allocation + // yes the code is yuck - but its more performant yuck! + resutMap.replaceAll((key, builder) -> + ((ImmutableList.Builder) builder).build()); - public static Map> groupingBy(Stream stream, Function function) { - return stream.collect(Collectors.groupingBy(function, LinkedHashMap::new, mapping(Function.identity(), ImmutableList.toImmutableList()))); + // make it the right shape - like as if generics were never invented + return (Map>) (Map) resutMap; } - public static Map groupingByUniqueKey(Collection list, Function keyFunction) { - return list.stream().collect(Collectors.toMap( - keyFunction, - identity(), - throwingMerger(), - LinkedHashMap::new) - ); + public static Map toMapByUniqueKey(Collection list, Function keyFunction) { + return toMap(list, keyFunction, throwingMerger()); } - public static Map groupingByUniqueKey(Stream stream, Function keyFunction) { - return stream.collect(Collectors.toMap( - keyFunction, - identity(), - throwingMerger(), - LinkedHashMap::new) - ); - } + + private static final Predicate ALWAYS_TRUE = o -> true; + + private static final BinaryOperator THROWING_MERGER_SINGLETON = (u, v) -> { + throw new IllegalStateException(String.format("Duplicate key %s", u)); + }; + private static BinaryOperator throwingMerger() { - return (u, v) -> { - throw new IllegalStateException(String.format("Duplicate key %s", u)); - }; + //noinspection unchecked + return (BinaryOperator) THROWING_MERGER_SINGLETON; } @@ -119,6 +142,19 @@ public static Collection toCollection(Object iterableResult) { return list; } + /** + * Creates an {@link ArrayList} sized appropriately to the collection, typically for copying + * + * @param collection the collection of a certain size + * @param to two + * + * @return a new {@link ArrayList} initially sized to the same as the collection + */ + public static @NonNull List arrayListSizedTo(@NonNull Collection collection) { + return new ArrayList<>(collection.size()); + } + + /** * Converts a value into a list if it's really a collection or array of things * else it turns it into a singleton list containing that one value @@ -240,11 +276,6 @@ public static List valuesToList(Map map) { return new ArrayList<>(map.values()); } - public static List mapEntries(Map map, BiFunction function) { - return map.entrySet().stream().map(entry -> function.apply(entry.getKey(), entry.getValue())).collect(Collectors.toList()); - } - - public static List> transposeMatrix(List> matrix) { int rowCount = matrix.size(); int colCount = matrix.get(0).size(); @@ -261,21 +292,13 @@ public static List> transposeMatrix(List> matrix) return result; } - public static CompletableFuture> flatList(CompletableFuture>> cf) { - return cf.thenApply(FpKit::flatList); - } - - public static List flatList(Collection> listLists) { - return listLists.stream() - .flatMap(List::stream) - .collect(ImmutableList.toImmutableList()); - } - public static Optional findOne(Collection list, Predicate filter) { - return list - .stream() - .filter(filter) - .findFirst(); + for (T t : list) { + if (filter.test(t)) { + return Optional.of(t); + } + } + return Optional.empty(); } public static T findOneOrNull(List list, Predicate filter) { @@ -292,10 +315,13 @@ public static int findIndex(List list, Predicate filter) { } public static List filterList(Collection list, Predicate filter) { - return list - .stream() - .filter(filter) - .collect(Collectors.toList()); + List result = arrayListSizedTo(list); + for (T t : list) { + if (filter.test(t)) { + result.add(t); + } + } + return result; } public static Set filterSet(Collection input, Predicate filter) { @@ -352,9 +378,10 @@ public static Supplier interThreadMemoize(Supplier delegate) { /** * Faster set intersection. * - * @param for two + * @param for two * @param set1 first set * @param set2 second set + * * @return intersection set */ public static Set intersection(Set set1, Set set2) { diff --git a/src/main/java/graphql/util/NodeMultiZipper.java b/src/main/java/graphql/util/NodeMultiZipper.java index fb67fe43ea..60328c1a2c 100644 --- a/src/main/java/graphql/util/NodeMultiZipper.java +++ b/src/main/java/graphql/util/NodeMultiZipper.java @@ -62,7 +62,7 @@ public T toRootNode() { Map>> sameParent = zipperWithSameParent(deepestZippers); List> newZippers = new ArrayList<>(); - Map> zipperByNode = FpKit.groupingByUniqueKey(curZippers, NodeZipper::getCurNode); + Map> zipperByNode = FpKit.toMapByUniqueKey(curZippers, NodeZipper::getCurNode); for (Map.Entry>> entry : sameParent.entrySet()) { NodeZipper newZipper = moveUp(entry.getKey(), entry.getValue()); Optional> zipperToBeReplaced = Optional.ofNullable(zipperByNode.get(entry.getKey())); diff --git a/src/main/java/graphql/validation/ValidationErrorType.java b/src/main/java/graphql/validation/ValidationErrorType.java index e480c5c360..e701a5d778 100644 --- a/src/main/java/graphql/validation/ValidationErrorType.java +++ b/src/main/java/graphql/validation/ValidationErrorType.java @@ -43,7 +43,6 @@ public enum ValidationErrorType implements ValidationErrorClassification { NullValueForNonNullArgument, SubscriptionMultipleRootFields, SubscriptionIntrospectionRootField, - ForbidSkipAndIncludeOnSubscriptionRoot, UniqueObjectFieldName, UnknownOperation } diff --git a/src/main/java/graphql/validation/Validator.java b/src/main/java/graphql/validation/Validator.java index 8bda10839b..52709109d6 100644 --- a/src/main/java/graphql/validation/Validator.java +++ b/src/main/java/graphql/validation/Validator.java @@ -27,7 +27,7 @@ import graphql.validation.rules.PossibleFragmentSpreads; import graphql.validation.rules.ProvidedNonNullArguments; import graphql.validation.rules.ScalarLeaves; -import graphql.validation.rules.SubscriptionRootField; +import graphql.validation.rules.SubscriptionUniqueRootField; import graphql.validation.rules.UniqueArgumentNames; import graphql.validation.rules.UniqueDirectiveNamesPerLocation; import graphql.validation.rules.UniqueFragmentNames; @@ -155,7 +155,7 @@ public List createRules(ValidationContext validationContext, Valid UniqueVariableNames uniqueVariableNamesRule = new UniqueVariableNames(validationContext, validationErrorCollector); rules.add(uniqueVariableNamesRule); - SubscriptionRootField uniqueSubscriptionRootField = new SubscriptionRootField(validationContext, validationErrorCollector); + SubscriptionUniqueRootField uniqueSubscriptionRootField = new SubscriptionUniqueRootField(validationContext, validationErrorCollector); rules.add(uniqueSubscriptionRootField); UniqueObjectFieldName uniqueObjectFieldName = new UniqueObjectFieldName(validationContext, validationErrorCollector); diff --git a/src/main/java/graphql/validation/rules/SubscriptionRootField.java b/src/main/java/graphql/validation/rules/SubscriptionUniqueRootField.java similarity index 58% rename from src/main/java/graphql/validation/rules/SubscriptionRootField.java rename to src/main/java/graphql/validation/rules/SubscriptionUniqueRootField.java index 758ecff605..0ded9ca632 100644 --- a/src/main/java/graphql/validation/rules/SubscriptionRootField.java +++ b/src/main/java/graphql/validation/rules/SubscriptionUniqueRootField.java @@ -6,12 +6,9 @@ import graphql.execution.FieldCollectorParameters; import graphql.execution.MergedField; import graphql.execution.MergedSelectionSet; -import graphql.execution.RawVariables; -import graphql.execution.ValuesResolver; -import graphql.language.Directive; import graphql.language.NodeUtil; import graphql.language.OperationDefinition; -import graphql.language.VariableDefinition; +import graphql.language.Selection; import graphql.schema.GraphQLObjectType; import graphql.validation.AbstractRule; import graphql.validation.ValidationContext; @@ -19,25 +16,20 @@ import java.util.List; -import static graphql.Directives.INCLUDE_DIRECTIVE_DEFINITION; -import static graphql.Directives.SKIP_DIRECTIVE_DEFINITION; import static graphql.language.OperationDefinition.Operation.SUBSCRIPTION; import static graphql.validation.ValidationErrorType.SubscriptionIntrospectionRootField; import static graphql.validation.ValidationErrorType.SubscriptionMultipleRootFields; -import static graphql.validation.ValidationErrorType.ForbidSkipAndIncludeOnSubscriptionRoot; /** * A subscription operation must only have one root field * A subscription operation's single root field must not be an introspection field * https://spec.graphql.org/draft/#sec-Single-root-field - * - * A subscription operation's root field must not have neither @skip nor @include directives */ @Internal -public class SubscriptionRootField extends AbstractRule { +public class SubscriptionUniqueRootField extends AbstractRule { private final FieldCollector fieldCollector = new FieldCollector(); - public SubscriptionRootField(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { + public SubscriptionUniqueRootField(ValidationContext validationContext, ValidationErrorCollector validationErrorCollector) { super(validationContext, validationErrorCollector); } @@ -47,24 +39,16 @@ public void checkOperationDefinition(OperationDefinition operationDef) { GraphQLObjectType subscriptionType = getValidationContext().getSchema().getSubscriptionType(); - // This coercion takes into account default values for variables - List variableDefinitions = operationDef.getVariableDefinitions(); - CoercedVariables coercedVariableValues = ValuesResolver.coerceVariableValues( - getValidationContext().getSchema(), - variableDefinitions, - RawVariables.emptyVariables(), - getValidationContext().getGraphQLContext(), - getValidationContext().getI18n().getLocale()); - FieldCollectorParameters collectorParameters = FieldCollectorParameters.newParameters() .schema(getValidationContext().getSchema()) .fragments(NodeUtil.getFragmentsByName(getValidationContext().getDocument())) - .variables(coercedVariableValues.toMap()) + .variables(CoercedVariables.emptyVariables().toMap()) .objectType(subscriptionType) .graphQLContext(getValidationContext().getGraphQLContext()) .build(); MergedSelectionSet fields = fieldCollector.collectFields(collectorParameters, operationDef.getSelectionSet()); + List subscriptionSelections = operationDef.getSelectionSet().getSelections(); if (fields.size() > 1) { String message = i18n(SubscriptionMultipleRootFields, "SubscriptionUniqueRootField.multipleRootFields", operationDef.getName()); @@ -73,15 +57,11 @@ public void checkOperationDefinition(OperationDefinition operationDef) { MergedField mergedField = fields.getSubFieldsList().get(0); + if (isIntrospectionField(mergedField)) { String message = i18n(SubscriptionIntrospectionRootField, "SubscriptionIntrospectionRootField.introspectionRootField", operationDef.getName(), mergedField.getName()); addError(SubscriptionIntrospectionRootField, mergedField.getSingleField().getSourceLocation(), message); } - - if (hasSkipOrIncludeDirectives(mergedField)) { - String message = i18n(ForbidSkipAndIncludeOnSubscriptionRoot, "SubscriptionRootField.forbidSkipAndIncludeOnSubscriptionRoot", operationDef.getName(), mergedField.getName()); - addError(ForbidSkipAndIncludeOnSubscriptionRoot, mergedField.getSingleField().getSourceLocation(), message); - } } } } @@ -89,14 +69,4 @@ public void checkOperationDefinition(OperationDefinition operationDef) { private boolean isIntrospectionField(MergedField field) { return field.getName().startsWith("__"); } - - private boolean hasSkipOrIncludeDirectives(MergedField field) { - List directives = field.getSingleField().getDirectives(); - for (Directive directive : directives) { - if (directive.getName().equals(SKIP_DIRECTIVE_DEFINITION.getName()) || directive.getName().equals(INCLUDE_DIRECTIVE_DEFINITION.getName())) { - return true; - } - } - return false; - } } diff --git a/src/main/resources/i18n/Validation.properties b/src/main/resources/i18n/Validation.properties index e638233cf2..a9403bea5b 100644 --- a/src/main/resources/i18n/Validation.properties +++ b/src/main/resources/i18n/Validation.properties @@ -68,8 +68,9 @@ ScalarLeaves.subselectionOnLeaf=Validation error ({0}) : Subselection not allowe ScalarLeaves.subselectionRequired=Validation error ({0}) : Subselection required for type ''{1}'' of field ''{2}'' # SubscriptionUniqueRootField.multipleRootFields=Validation error ({0}) : Subscription operation ''{1}'' must have exactly one root field +SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validation error ({0}) : Subscription operation ''{1}'' must have exactly one root field with fragments SubscriptionIntrospectionRootField.introspectionRootField=Validation error ({0}) : Subscription operation ''{1}'' root field ''{2}'' cannot be an introspection field -SubscriptionRootField.forbidSkipAndIncludeOnSubscriptionRoot=Validation error ({0}) : Subscription operation ''{1}'' root field ''{2}'' must not use @skip nor @include directives in top level selection +SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validation error ({0}) : Subscription operation ''{1}'' fragment root field ''{2}'' cannot be an introspection field # UniqueArgumentNames.uniqueArgument=Validation error ({0}) : There can be only one argument named ''{1}'' # diff --git a/src/main/resources/i18n/Validation_de.properties b/src/main/resources/i18n/Validation_de.properties index fa58577fa1..7823c9d511 100644 --- a/src/main/resources/i18n/Validation_de.properties +++ b/src/main/resources/i18n/Validation_de.properties @@ -60,9 +60,9 @@ ScalarLeaves.subselectionOnLeaf=Validierungsfehler ({0}) : Unterauswahl für Bla ScalarLeaves.subselectionRequired=Validierungsfehler ({0}) : Unterauswahl erforderlich für Typ ''{1}'' des Feldes ''{2}'' # SubscriptionUniqueRootField.multipleRootFields=Validierungsfehler ({0}) : Subscription operation ''{1}'' muss genau ein root field haben +SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validierungsfehler ({0}) : Subscription operation ''{1}'' muss genau ein root field mit Fragmenten haben SubscriptionIntrospectionRootField.introspectionRootField=Validierungsfehler ({0}) : Subscription operation ''{1}'' root field ''{2}'' kann kein introspection field sein -SubscriptionRootField.forbidSkipAndIncludeOnSubscriptionRoot=Validierungsfehler ({0}) : Subscription operation ''{1}'' root field ''{2}'' darf weder @skip noch @include directive in top level selection -# +SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validierungsfehler ({0}) : Subscription operation ''{1}'' fragment root field ''{2}'' kann kein introspection field sein # UniqueArgumentNames.uniqueArgument=Validierungsfehler ({0}) : Es kann nur ein Argument namens ''{1}'' geben # diff --git a/src/main/resources/i18n/Validation_nl.properties b/src/main/resources/i18n/Validation_nl.properties index 4cef5f2a0a..e30b342640 100644 --- a/src/main/resources/i18n/Validation_nl.properties +++ b/src/main/resources/i18n/Validation_nl.properties @@ -58,8 +58,9 @@ ScalarLeaves.subselectionOnLeaf=Validatiefout ({0}) : Sub-selectie niet toegesta ScalarLeaves.subselectionRequired=Validatiefout ({0}) : Sub-selectie verplicht voor type ''{1}'' van veld ''{2}'' # SubscriptionUniqueRootField.multipleRootFields=Validatiefout ({0}) : Subscription operation ''{1}'' moet exact één root field hebben +SubscriptionUniqueRootField.multipleRootFieldsWithFragment=Validatiefout ({0}) : Subscription operation ''{1}'' moet exact één root field met fragmenten hebben SubscriptionIntrospectionRootField.introspectionRootField=Validatiefout ({0}) : Subscription operation ''{1}'' root field ''{2}'' kan geen introspectieveld zijn -SubscriptionRootField.forbidSkipAndIncludeOnSubscriptionRoot=Validation error ({0}) : Subscription operation ''{1}'' root field ''{2}'' must not use @skip nor @include directives in top level selection +SubscriptionIntrospectionRootField.introspectionRootFieldWithFragment=Validatiefout ({0}) : Subscription operation ''{1}'' fragment root field ''{2}'' kan geen introspectieveld zijn # UniqueArgumentNames.uniqueArgument=Validatiefout ({0}) : Er mag maar één argument met naam ''{1}'' bestaan # diff --git a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy index 018c9f1577..0736b1671a 100644 --- a/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy +++ b/src/test/groovy/graphql/GraphqlErrorHelperTest.groovy @@ -3,6 +3,7 @@ package graphql import graphql.language.SourceLocation import graphql.validation.ValidationError import graphql.validation.ValidationErrorType +import spock.lang.RepeatUntilFailure import spock.lang.Specification class GraphqlErrorHelperTest extends Specification { @@ -154,4 +155,15 @@ class GraphqlErrorHelperTest extends Specification { assert gErr.getExtensions() == null } } + + @RepeatUntilFailure(maxAttempts = 1_000) + def "can deterministically serialize SourceLocation"() { + when: + def specMap = GraphqlErrorHelper.toSpecification(new TestError()) + + then: + def location = specMap["locations"][0] as Map + def keys = location.keySet().toList() + keys == ["line", "column"] + } } diff --git a/src/test/groovy/graphql/MutationTest.groovy b/src/test/groovy/graphql/MutationTest.groovy index 5c872b1f83..9ad61f6dd4 100644 --- a/src/test/groovy/graphql/MutationTest.groovy +++ b/src/test/groovy/graphql/MutationTest.groovy @@ -141,16 +141,17 @@ class MutationTest extends Specification { ]]) def graphQL = GraphQL.newGraphQL(schema).build() - - when: - def er = graphQL.execute(""" + def ei = ExecutionInput.newExecutionInput(""" mutation m { plus1(arg:10) plus2(arg:10) plus3(arg:10) } - """) + """).build() + ei.getGraphQLContext().put(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, defeEnabled) + when: + def er = graphQL.execute(ei) then: er.errors.isEmpty() er.data == [ @@ -158,6 +159,8 @@ class MutationTest extends Specification { plus2: 12, plus3: 13, ] + where: + defeEnabled << [true, false] } def "simple async mutation with DataLoader"() { @@ -213,6 +216,7 @@ class MutationTest extends Specification { plus3(arg:10) } """).dataLoaderRegistry(dlReg).build() + ei.getGraphQLContext().put(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, defeEnabled) when: def er = graphQL.execute(ei) @@ -223,12 +227,16 @@ class MutationTest extends Specification { plus2: 12, plus3: 13, ] + + where: + defeEnabled << [true, false] } /* This test shows a dataloader being called at the mutation field level, in serial via AsyncSerialExecutionStrategy, and then again at the sub field level, in parallel, via AsyncExecutionStrategy. */ + def "more complex async mutation with DataLoader"() { def sdl = """ type Query { @@ -436,6 +444,7 @@ class MutationTest extends Specification { } } """).dataLoaderRegistry(dlReg).build() + ei.getGraphQLContext().put(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, defeEnabled) when: def cf = graphQL.executeAsync(ei) @@ -459,5 +468,8 @@ class MutationTest extends Specification { topLevelF3: expectedMap, topLevelF4: expectedMap, ] + + where: + defeEnabled << [true, false] } } diff --git a/src/test/groovy/graphql/collect/ImmutableKitTest.groovy b/src/test/groovy/graphql/collect/ImmutableKitTest.groovy index 82d76bae1e..f546147d7b 100644 --- a/src/test/groovy/graphql/collect/ImmutableKitTest.groovy +++ b/src/test/groovy/graphql/collect/ImmutableKitTest.groovy @@ -1,7 +1,6 @@ package graphql.collect import com.google.common.collect.ImmutableList -import com.google.common.collect.ImmutableMap import spock.lang.Specification class ImmutableKitTest extends Specification { @@ -63,4 +62,16 @@ class ImmutableKitTest extends Specification { then: set == ["a", "b", "c", "d", "e", "f"] as Set } + + def "flatMapList works"() { + def listOfLists = [ + ["A", "B"], + ["C"], + ["D", "E"], + ] + when: + def flatList = ImmutableKit.flatMapList(listOfLists) + then: + flatList == ["A", "B", "C", "D", "E",] + } } diff --git a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy index 0b7b5f2a11..9d99fbbfba 100644 --- a/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncExecutionStrategyTest.groovy @@ -117,6 +117,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .newParameters() .executionStepInfo(typeInfo) .fields(mergedSelectionSet(['hello': mergedField([Field.newField('hello').build()]), 'hello2': mergedField([Field.newField('hello2').build()])])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() AsyncExecutionStrategy asyncExecutionStrategy = new AsyncExecutionStrategy() @@ -160,6 +161,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .newParameters() .executionStepInfo(typeInfo) .fields(mergedSelectionSet(['hello': mergedField([Field.newField('hello').build()]), 'hello2': mergedField([Field.newField('hello2').build()])])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() AsyncExecutionStrategy asyncExecutionStrategy = new AsyncExecutionStrategy() @@ -205,6 +207,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .newParameters() .executionStepInfo(typeInfo) .fields(mergedSelectionSet(['hello': mergedField([Field.newField('hello').build()]), 'hello2': mergedField([Field.newField('hello2').build()])])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() AsyncExecutionStrategy asyncExecutionStrategy = new AsyncExecutionStrategy() @@ -249,6 +252,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .newParameters() .executionStepInfo(typeInfo) .fields(mergedSelectionSet(['hello': mergedField([Field.newField('hello').build()]), 'hello2': mergedField([Field.newField('hello2').build()])])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() AsyncExecutionStrategy asyncExecutionStrategy = new AsyncExecutionStrategy() @@ -312,6 +316,7 @@ abstract class AsyncExecutionStrategyTest extends Specification { .newParameters() .executionStepInfo(typeInfo) .fields(mergedSelectionSet(['hello': mergedField([new Field('hello')]), 'hello2': mergedField([new Field('hello2')])])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() AsyncExecutionStrategy asyncExecutionStrategy = new AsyncExecutionStrategy() diff --git a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy index efb67639d5..937c99c705 100644 --- a/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/AsyncSerialExecutionStrategyTest.groovy @@ -115,6 +115,7 @@ class AsyncSerialExecutionStrategyTest extends Specification { .newParameters() .executionStepInfo(typeInfo) .fields(mergedSelectionSet(['hello': mergedField(new Field('hello')), 'hello2': mergedField(new Field('hello2')), 'hello3': mergedField(new Field('hello3'))])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() AsyncSerialExecutionStrategy strategy = new AsyncSerialExecutionStrategy() @@ -163,6 +164,7 @@ class AsyncSerialExecutionStrategyTest extends Specification { .newParameters() .executionStepInfo(typeInfo) .fields(mergedSelectionSet(['hello': mergedField(new Field('hello')), 'hello2': mergedField(new Field('hello2')), 'hello3': mergedField(new Field('hello3'))])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() AsyncSerialExecutionStrategy strategy = new AsyncSerialExecutionStrategy() diff --git a/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy b/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy index 35fbfe2f1d..07318afa75 100644 --- a/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy +++ b/src/test/groovy/graphql/execution/DataFetcherResultTest.groovy @@ -1,5 +1,6 @@ package graphql.execution +import graphql.GraphQLError import graphql.InvalidSyntaxError import graphql.validation.ValidationError import graphql.validation.ValidationErrorType @@ -107,4 +108,77 @@ class DataFetcherResultTest extends Specification { result.getExtensions() == [a : "b"] result.getErrors() == [error1, error2] } + + def "implements equals/hashCode for matching results"() { + when: + def firstResult = toDataFetcherResult(first) + def secondResult = toDataFetcherResult(second) + + then: + firstResult == secondResult + firstResult.hashCode() == secondResult.hashCode() + + where: + first | second + [data: "A string"] | [data: "A string"] + [data: 5] | [data: 5] + [data: ["a", "b"]] | [data: ["a", "b"]] + [errors: [error("An error")]] | [errors: [error("An error")]] + [data: "A value", errors: [error("An error")]] | [data: "A value", errors: [error("An error")]] + [data: "A value", localContext: 5] | [data: "A value", localContext: 5] + [data: "A value", errors: [error("An error")], localContext: 5] | [data: "A value", errors: [error("An error")], localContext: 5] + [data: "A value", extensions: ["key": "value"]] | [data: "A value", extensions: ["key": "value"]] + [data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]] | [data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]] + } + + def "implements equals/hashCode for different results"() { + when: + def firstResult = toDataFetcherResult(first) + def secondResult = toDataFetcherResult(second) + + then: + firstResult != secondResult + firstResult.hashCode() != secondResult.hashCode() + + where: + first | second + [data: "A string"] | [data: "A different string"] + [data: 5] | [data: "not 5"] + [data: ["a", "b"]] | [data: ["a", "c"]] + [errors: [error("An error")]] | [errors: [error("A different error")]] + [data: "A value", errors: [error("An error")]] | [data: "A different value", errors: [error("An error")]] + [data: "A value", localContext: 5] | [data: "A value", localContext: 1] + [data: "A value", errors: [error("An error")], localContext: 5] | [data: "A value", errors: [error("A different error")], localContext: 5] + [data: "A value", extensions: ["key": "value"]] | [data: "A value", extensions: ["key", "different value"]] + [data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "value"]] | [data: "A value", errors: [error("An error")], localContext: 5, extensions: ["key": "different value"]] + } + + private static DataFetcherResult toDataFetcherResult(Map resultFields) { + def resultBuilder = DataFetcherResult.newResult(); + resultFields.forEach { key, value -> + if (value != null) { + switch (key) { + case "data": + resultBuilder.data(value) + break; + case "errors": + resultBuilder.errors(value as List); + break; + case "localContext": + resultBuilder.localContext(value); + break; + case "extensions": + resultBuilder.extensions(value as Map); + break; + } + } + } + return resultBuilder.build(); + } + + private static GraphQLError error(String message) { + return GraphQLError.newError() + .message(message) + .build(); + } } diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyParametersTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyParametersTest.groovy index e45ff7c546..df09445497 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyParametersTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyParametersTest.groovy @@ -12,10 +12,12 @@ class ExecutionStrategyParametersTest extends Specification { def "ExecutionParameters can be transformed"() { given: + def executionContext = Mock(ExecutionContext) def parameters = newParameters() .executionStepInfo(newExecutionStepInfo().type(GraphQLString)) .source(new Object()) .localContext("localContext") + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .fields(mergedSelectionSet("a": [])) .build() diff --git a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy index ca513d5ba1..a8de454c06 100644 --- a/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy +++ b/src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy @@ -137,6 +137,7 @@ class ExecutionStrategyTest extends Specification { .executionStepInfo(ExecutionStepInfo.newExecutionStepInfo().type(objectType)) .source(result) .fields(mergedSelectionSet(["fld": [Field.newField().build()]])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .field(mergedField(Field.newField().build())) .build() @@ -157,7 +158,7 @@ class ExecutionStrategyTest extends Specification { Field field = new Field("someField") def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).path(ResultPath.rootPath()).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def result = ["test", "1", "2", "3"] @@ -182,7 +183,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = GraphQLString def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -208,7 +209,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = nonNull(GraphQLString) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -230,7 +231,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = GraphQLString def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -256,7 +257,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = nonNull(GraphQLString) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -278,7 +279,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = GraphQLString def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -304,7 +305,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = nonNull(GraphQLString) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(typeInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -326,7 +327,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = GraphQLString def fldDef = newFieldDefinition().name("test").type(fieldType).build() def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(typeInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -352,7 +353,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = nonNull(GraphQLString) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(typeInfo) .nonNullFieldValidator(nullableFieldValidator) @@ -374,7 +375,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = list(GraphQLString) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).path(ResultPath.rootPath()).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def result = ["test", "1", "2", "3"] def parameters = newParameters() .executionStepInfo(executionStepInfo) @@ -396,7 +397,7 @@ class ExecutionStrategyTest extends Specification { ExecutionContext executionContext = buildContext() def fieldType = Scalars.GraphQLInt def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) String result = "not a number" def parameters = newParameters() @@ -421,7 +422,7 @@ class ExecutionStrategyTest extends Specification { ExecutionContext executionContext = buildContext() GraphQLEnumType enumType = newEnum().name("Enum").value("value").build() def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(enumType).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) String result = "not a enum number" def parameters = newParameters() @@ -468,7 +469,7 @@ class ExecutionStrategyTest extends Specification { ExecutionContext executionContext = buildContext() def fieldType = NullProducingScalar def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(nonNull(fieldType)).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) when: def parameters = newParameters() @@ -529,7 +530,7 @@ class ExecutionStrategyTest extends Specification { .build() ExecutionContext executionContext = buildContext(schema) ExecutionStepInfo typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(objectType).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) Argument argument = new Argument("arg1", new StringValue("argVal")) Field field = new Field(someFieldName, [argument]) MergedField mergedField = mergedField(field) @@ -594,7 +595,7 @@ class ExecutionStrategyTest extends Specification { .build() ExecutionContext executionContext = buildContext(schema) def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(objectType).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) ResultPath expectedPath = ResultPath.rootPath().segment(someFieldName) SourceLocation sourceLocation = new SourceLocation(666, 999) @@ -730,7 +731,7 @@ class ExecutionStrategyTest extends Specification { ExecutionContext executionContext = buildContext(schema) def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(objectType).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) Field field = new Field(someFieldName) def parameters = newParameters() @@ -759,7 +760,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = list(Scalars.GraphQLInt) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).path(ResultPath.rootPath()).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) @@ -783,7 +784,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = list(Scalars.GraphQLInt) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).path(ResultPath.rootPath()).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) @@ -807,7 +808,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = list(Scalars.GraphQLInt) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).path(ResultPath.rootPath()).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) @@ -837,6 +838,7 @@ class ExecutionStrategyTest extends Specification { .path(ResultPath.fromList(["parent"])) .field(mergedField(field)) .fields(mergedSelectionSet(["parent": [mergedField(field)]])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .executionStepInfo(executionStepInfo) .build() @@ -869,6 +871,7 @@ class ExecutionStrategyTest extends Specification { .field(mergedField(field)) .fields(mergedSelectionSet(["parent": [mergedField(field)]])) .executionStepInfo(executionStepInfo) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .build() when: @@ -890,6 +893,7 @@ class ExecutionStrategyTest extends Specification { .path(ResultPath.fromList(["parent"])) .field(mergedField(field)) .fields(mergedSelectionSet(["parent": [mergedField(field)]])) + .nonNullFieldValidator(new NonNullableFieldValidator(executionContext)) .executionStepInfo(executionStepInfo) .build() @@ -913,7 +917,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = list(Scalars.GraphQLInt) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def executionStepInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).path(ResultPath.rootPath()).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, executionStepInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(executionStepInfo) @@ -937,7 +941,7 @@ class ExecutionStrategyTest extends Specification { def fieldType = list(Scalars.GraphQLInt) def fldDef = newFieldDefinition().name("test").type(fieldType).build() def typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(fieldType).fieldDefinition(fldDef).build() - NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext, typeInfo) + NonNullableFieldValidator nullableFieldValidator = new NonNullableFieldValidator(executionContext) def parameters = newParameters() .executionStepInfo(typeInfo) diff --git a/src/test/groovy/graphql/execution/NonNullableFieldValidatorTest.groovy b/src/test/groovy/graphql/execution/NonNullableFieldValidatorTest.groovy index 34a48affe7..33977d515c 100644 --- a/src/test/groovy/graphql/execution/NonNullableFieldValidatorTest.groovy +++ b/src/test/groovy/graphql/execution/NonNullableFieldValidatorTest.groovy @@ -7,10 +7,6 @@ import static graphql.schema.GraphQLNonNull.nonNull class NonNullableFieldValidatorTest extends Specification { - def parameters = Mock(ExecutionStrategyParameters) { - getPath() >> ResultPath.rootPath() - } - def "non nullable field throws exception"() { ExecutionContext context = Mock(ExecutionContext) { propagateErrorsOnNonNullContractFailure() >> true @@ -18,7 +14,12 @@ class NonNullableFieldValidatorTest extends Specification { ExecutionStepInfo typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(nonNull(GraphQLString)).build() - NonNullableFieldValidator validator = new NonNullableFieldValidator(context, typeInfo) + def parameters = Mock(ExecutionStrategyParameters) { + getPath() >> ResultPath.rootPath() + getExecutionStepInfo() >> typeInfo + } + + NonNullableFieldValidator validator = new NonNullableFieldValidator(context) when: validator.validate(parameters, null) @@ -35,7 +36,12 @@ class NonNullableFieldValidatorTest extends Specification { ExecutionStepInfo typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(GraphQLString).build() - NonNullableFieldValidator validator = new NonNullableFieldValidator(context, typeInfo) + def parameters = Mock(ExecutionStrategyParameters) { + getPath() >> ResultPath.rootPath() + getExecutionStepInfo() >> typeInfo + } + + NonNullableFieldValidator validator = new NonNullableFieldValidator(context) when: def result = validator.validate(parameters, null) @@ -51,7 +57,12 @@ class NonNullableFieldValidatorTest extends Specification { ExecutionStepInfo typeInfo = ExecutionStepInfo.newExecutionStepInfo().type(nonNull(GraphQLString)).build() - NonNullableFieldValidator validator = new NonNullableFieldValidator(context, typeInfo) + def parameters = Mock(ExecutionStrategyParameters) { + getPath() >> ResultPath.rootPath() + getExecutionStepInfo() >> typeInfo + } + + NonNullableFieldValidator validator = new NonNullableFieldValidator(context) when: def result = validator.validate(parameters, null) diff --git a/src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy b/src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy index 3b57bf9780..9aebce3640 100644 --- a/src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/DataLoaderCacheCanBeAsyncTest.groovy @@ -87,7 +87,7 @@ class DataLoaderCacheCanBeAsyncTest extends Specification { def valueCache = new CustomValueCache() valueCache.store.put("a", [id: "cachedA", name: "cachedAName"]) - DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(valueCache).setCachingEnabled(true) + DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(valueCache).setCachingEnabled(true).build() DataLoader userDataLoader = DataLoaderFactory.newDataLoader(userBatchLoader, options) registry = DataLoaderRegistry.newRegistry() diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java b/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java index d0dacd5964..3c43b94b5b 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/BatchCompareDataFetchers.java @@ -100,9 +100,9 @@ private static List> getDepartmentsForShops(List shops) { public DataLoader> departmentsForShopDataLoader = DataLoaderFactory.newDataLoader(departmentsForShopsBatchLoader); - public DataFetcher>> departmentsForShopDataLoaderDataFetcher = environment -> { + public DataFetcher departmentsForShopDataLoaderDataFetcher = environment -> { Shop shop = environment.getSource(); - return departmentsForShopDataLoader.load(shop.getId()); + return environment.getDataLoader("departments").load(shop.getId()); }; // Products @@ -136,9 +136,9 @@ private static List> getProductsForDepartments(List de public DataLoader> productsForDepartmentDataLoader = DataLoaderFactory.newDataLoader(productsForDepartmentsBatchLoader); - public DataFetcher>> productsForDepartmentDataLoaderDataFetcher = environment -> { + public DataFetcher productsForDepartmentDataLoaderDataFetcher = environment -> { Department department = environment.getSource(); - return productsForDepartmentDataLoader.load(department.getId()); + return environment.getDataLoader("products").load(department.getId()); }; private CompletableFuture maybeAsyncWithSleep(Supplier> supplier) { diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java index 51d2353bf7..14d2f425c8 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductBackend.java @@ -2,6 +2,7 @@ import com.google.common.collect.ImmutableList; +import org.dataloader.BatchLoader; import org.dataloader.DataLoader; import org.dataloader.DataLoaderFactory; @@ -26,12 +27,13 @@ public DataLoaderCompanyProductBackend(int companyCount, int projectCount) { mkCompany(projectCount); } - projectsLoader = DataLoaderFactory.newDataLoader(keys -> getProjectsForCompanies(keys).thenApply(projects -> keys + BatchLoader> uuidListBatchLoader = keys -> getProjectsForCompanies(keys).thenApply(projects -> keys .stream() .map(companyId -> projects.stream() .filter(project -> project.getCompanyId().equals(companyId)) .collect(Collectors.toList())) - .collect(Collectors.toList()))); + .collect(Collectors.toList())); + projectsLoader = DataLoaderFactory.newDataLoader(uuidListBatchLoader); } diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductMutationTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductMutationTest.groovy index 649da5e0d4..33cbb86d1f 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductMutationTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderCompanyProductMutationTest.groovy @@ -48,7 +48,7 @@ class DataLoaderCompanyProductMutationTest extends Specification { newTypeWiring("Company").dataFetcher("projects", { environment -> DataLoaderCompanyProductBackend.Company source = environment.getSource() - return backend.getProjectsLoader().load(source.getId()) + return environment.getDataLoader("projects-dl").load(source.getId()) })) .type( newTypeWiring("Query").dataFetcher("companies", { diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy index 2d98da377f..00dd49a098 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderHangingTest.groovy @@ -192,7 +192,7 @@ class DataLoaderHangingTest extends Specification { }) }, executor) } - }, DataLoaderOptions.newOptions().setMaxBatchSize(5)) + }, DataLoaderOptions.newOptions().setMaxBatchSize(5).build()) def dataLoaderSongs = DataLoaderFactory.newDataLoader(new BatchLoader>() { @Override @@ -209,7 +209,7 @@ class DataLoaderHangingTest extends Specification { }) }, executor) } - }, DataLoaderOptions.newOptions().setMaxBatchSize(5)) + }, DataLoaderOptions.newOptions().setMaxBatchSize(5).build()) def dataLoaderRegistry = new DataLoaderRegistry() dataLoaderRegistry.register("artist.albums", dataLoaderAlbums) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy index dd4be355f7..16d00be727 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderNodeTest.groovy @@ -11,6 +11,7 @@ import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLSchema import graphql.schema.StaticDataFetcher import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry import spock.lang.Specification @@ -69,15 +70,15 @@ class DataLoaderNodeTest extends Specification { } class NodeDataFetcher implements DataFetcher { - DataLoader loader + String name - NodeDataFetcher(DataLoader loader) { - this.loader = loader + NodeDataFetcher(String name) { + this.name = name } @Override Object get(DataFetchingEnvironment environment) throws Exception { - return loader.load(environment.getSource()) + return environment.getDataLoader(name).load(environment.getSource()) } } @@ -85,7 +86,8 @@ class DataLoaderNodeTest extends Specification { List> nodeLoads = [] - DataLoader> loader = new DataLoader<>({ keys -> + + def batchLoadFunction = { keys -> nodeLoads.add(keys) List> childNodes = new ArrayList<>() for (Node key : keys) { @@ -93,15 +95,17 @@ class DataLoaderNodeTest extends Specification { } System.out.println("BatchLoader called for " + keys + " -> got " + childNodes) return CompletableFuture.completedFuture(childNodes) - }) - - DataFetcher nodeDataFetcher = new NodeDataFetcher(loader) + } + DataLoader> loader = DataLoaderFactory.newDataLoader(batchLoadFunction) def nodeTypeName = "Node" def childNodesFieldName = "childNodes" def queryTypeName = "Query" def rootFieldName = "root" + DataFetcher nodeDataFetcher = new NodeDataFetcher(childNodesFieldName) + DataLoaderRegistry registry = new DataLoaderRegistry().register(childNodesFieldName, loader) + GraphQLObjectType nodeType = GraphQLObjectType .newObject() .name(nodeTypeName) @@ -132,8 +136,6 @@ class DataLoaderNodeTest extends Specification { .build()) .build() - DataLoaderRegistry registry = new DataLoaderRegistry().register(childNodesFieldName, loader) - ExecutionResult result = GraphQL.newGraphQL(schema) // .instrumentation(new DataLoaderDispatcherInstrumentation()) .build() diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderTypeMismatchTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderTypeMismatchTest.groovy index 03b60e4e39..5b6f1cfb2f 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderTypeMismatchTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/DataLoaderTypeMismatchTest.groovy @@ -9,6 +9,7 @@ import graphql.schema.idl.SchemaGenerator import graphql.schema.idl.SchemaParser import org.dataloader.BatchLoader import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry import spock.lang.Specification @@ -36,7 +37,7 @@ class DataLoaderTypeMismatchTest extends Specification { def typeDefinitionRegistry = new SchemaParser().parse(sdl) - def dataLoader = new DataLoader(new BatchLoader() { + def dataLoader = DataLoaderFactory.newDataLoader(new BatchLoader() { @Override CompletionStage> load(List keys) { return CompletableFuture.completedFuture([ @@ -50,7 +51,7 @@ class DataLoaderTypeMismatchTest extends Specification { def todosDef = new DataFetcher>() { @Override CompletableFuture get(DataFetchingEnvironment environment) { - return dataLoader.load(environment) + return environment.getDataLoader("getTodos").load(environment) } } diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy index b816602cde..135b52ba8d 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/Issue1178DataLoaderDispatchTest.groovy @@ -8,6 +8,7 @@ import graphql.schema.StaticDataFetcher import graphql.schema.idl.RuntimeWiring import org.dataloader.BatchLoader import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry import spock.lang.Specification @@ -40,7 +41,7 @@ class Issue1178DataLoaderDispatchTest extends Specification { def executor = Executors.newFixedThreadPool(5) - def dataLoader = new DataLoader(new BatchLoader() { + def dataLoader = DataLoaderFactory.newDataLoader(new BatchLoader() { @Override CompletionStage> load(List keys) { return CompletableFuture.supplyAsync({ @@ -48,7 +49,7 @@ class Issue1178DataLoaderDispatchTest extends Specification { }, executor) } }) - def dataLoader2 = new DataLoader(new BatchLoader() { + def dataLoader2 = DataLoaderFactory.newDataLoader(new BatchLoader() { @Override CompletionStage> load(List keys) { return CompletableFuture.supplyAsync({ @@ -61,8 +62,8 @@ class Issue1178DataLoaderDispatchTest extends Specification { dataLoaderRegistry.register("todo.related", dataLoader) dataLoaderRegistry.register("todo.related2", dataLoader2) - def relatedDf = new MyDataFetcher(dataLoader) - def relatedDf2 = new MyDataFetcher(dataLoader2) + def relatedDf = new MyDataFetcher("todo.related") + def relatedDf2 = new MyDataFetcher("todo.related2") def wiring = RuntimeWiring.newRuntimeWiring() .type(newTypeWiring("Query") @@ -119,16 +120,16 @@ class Issue1178DataLoaderDispatchTest extends Specification { static class MyDataFetcher implements DataFetcher> { - private final DataLoader dataLoader + private final String name - MyDataFetcher(DataLoader dataLoader) { - this.dataLoader = dataLoader + MyDataFetcher(String name) { + this.name = name } @Override CompletableFuture get(DataFetchingEnvironment environment) { def todo = environment.source as Map - return dataLoader.load(todo['id']) + return environment.getDataLoader(name).load(todo['id']) } } } diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy index 70bad946b0..0cc9a73c40 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/PeopleCompaniesAndProductsDataLoaderTest.groovy @@ -12,6 +12,7 @@ import graphql.schema.DataFetchingEnvironment import graphql.schema.idl.RuntimeWiring import org.dataloader.BatchLoader import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry import spock.lang.Specification @@ -168,8 +169,8 @@ class PeopleCompaniesAndProductsDataLoaderTest extends Specification { private DataLoaderRegistry buildRegistry() { - DataLoader personDataLoader = new DataLoader<>(personBatchLoader) - DataLoader companyDataLoader = new DataLoader<>(companyBatchLoader) + DataLoader personDataLoader = DataLoaderFactory.newDataLoader(personBatchLoader) + DataLoader companyDataLoader = DataLoaderFactory.newDataLoader(companyBatchLoader) DataLoaderRegistry registry = new DataLoaderRegistry() registry.register("person", personDataLoader) diff --git a/src/test/groovy/graphql/execution/instrumentation/dataloader/StarWarsDataLoaderWiring.groovy b/src/test/groovy/graphql/execution/instrumentation/dataloader/StarWarsDataLoaderWiring.groovy index 3bc4848e98..757d5564a0 100644 --- a/src/test/groovy/graphql/execution/instrumentation/dataloader/StarWarsDataLoaderWiring.groovy +++ b/src/test/groovy/graphql/execution/instrumentation/dataloader/StarWarsDataLoaderWiring.groovy @@ -10,6 +10,7 @@ import graphql.schema.idl.MapEnumValuesProvider import graphql.schema.idl.RuntimeWiring import org.dataloader.BatchLoader import org.dataloader.DataLoader +import org.dataloader.DataLoaderFactory import org.dataloader.DataLoaderRegistry import java.util.concurrent.CompletableFuture @@ -60,7 +61,7 @@ class StarWarsDataLoaderWiring { def newDataLoaderRegistry() { // a data loader for characters that points to the character batch loader - def characterDataLoader = new DataLoader(characterBatchLoader) + def characterDataLoader = DataLoaderFactory.newDataLoader(characterBatchLoader) new DataLoaderRegistry().register("character", characterDataLoader) } diff --git a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy index 2830419999..36aa87eb54 100644 --- a/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy +++ b/src/test/groovy/graphql/schema/DataFetchingEnvironmentImplTest.groovy @@ -73,7 +73,7 @@ class DataFetchingEnvironmentImplTest extends Specification { dfe.getVariables() == variables dfe.getOperationDefinition() == operationDefinition dfe.getExecutionId() == executionId - dfe.getDataLoader("dataLoader") == dataLoader + dfe.getDataLoader("dataLoader") != null } def "create environment from existing one will copy everything to new instance"() { @@ -118,7 +118,7 @@ class DataFetchingEnvironmentImplTest extends Specification { dfe.getDocument() == dfeCopy.getDocument() dfe.getOperationDefinition() == dfeCopy.getOperationDefinition() dfe.getVariables() == dfeCopy.getVariables() - dfe.getDataLoader("dataLoader") == dataLoader + dfe.getDataLoader("dataLoader") != null dfe.getLocale() == dfeCopy.getLocale() dfe.getLocalContext() == dfeCopy.getLocalContext() } diff --git a/src/test/groovy/graphql/util/FpKitTest.groovy b/src/test/groovy/graphql/util/FpKitTest.groovy index 455e9b57a1..21dad8ef1f 100644 --- a/src/test/groovy/graphql/util/FpKitTest.groovy +++ b/src/test/groovy/graphql/util/FpKitTest.groovy @@ -1,6 +1,6 @@ package graphql.util - +import com.google.common.collect.ImmutableList import spock.lang.Specification import java.util.function.Supplier @@ -98,21 +98,115 @@ class FpKitTest extends Specification { l == ["Parrot"] } + class Person { + String name + String city + + Person(String name) { + this.name = name + } + + Person(String name, String city) { + this.name = name + this.city = city + } + + String getName() { + return name + } + + String getCity() { + return city + } + } + + def a = new Person("a", "New York") + def b = new Person("b", "New York") + def c1 = new Person("c", "Sydney") + def c2 = new Person("c", "London") + + def "getByName tests"() { + + when: + def map = FpKit.getByName([a, b, c1, c2], { it -> it.getName() }) + then: + map == ["a": a, "b": b, c: c1] + + when: + map = FpKit.getByName([a, b, c1, c2], { it -> it.getName() }, { it1, it2 -> it2 }) + then: + map == ["a": a, "b": b, c: c2] + } + + def "groupingBy tests"() { + + when: + Map> map = FpKit.groupingBy([a, b, c1, c2], { it -> it.getCity() }) + then: + map == ["New York": [a, b], "Sydney": [c1], "London": [c2]] + + when: + map = FpKit.filterAndGroupingBy([a, b, c1, c2], { it -> it != c1 }, { it -> it.getCity() }) + then: + map == ["New York": [a, b], "London": [c2]] + + } + + def "toMapByUniqueKey works"() { + + when: + Map map = FpKit.toMapByUniqueKey([a, b, c1], { it -> it.getName() }) + then: + map == ["a": a, "b": b, "c": c1] + + when: + FpKit.toMapByUniqueKey([a, b, c1, c2], { it -> it.getName() }) + then: + def e = thrown(IllegalStateException.class) + e.message.contains("Duplicate key") + } + + def "findOne test"() { + when: + def opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "c" }) + then: + opt.isPresent() + opt.get() == c1 + + when: + opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "d" }) + then: + opt.isEmpty() + + when: + opt = FpKit.findOne([a, b, c1, c2], { it -> it.getName() == "a" }) + then: + opt.isPresent() + opt.get() == a + } + + def "filterList works"() { + when: + def list = FpKit.filterList([a, b, c1, c2], { it -> it.getName() == "c" }) + then: + list == [c1, c2] + } + def "set intersection works"() { - def set1 = ["A","B","C"] as Set - def set2 = ["A","C","D"] as Set + def set1 = ["A", "B", "C"] as Set + def set2 = ["A", "C", "D"] as Set def singleSetA = ["A"] as Set - def disjointSet = ["X","Y"] as Set + def disjointSet = ["X", "Y"] as Set when: def intersection = FpKit.intersection(set1, set2) then: - intersection == ["A","C"] as Set + intersection == ["A", "C"] as Set when: // reversed parameters intersection = FpKit.intersection(set2, set1) then: - intersection == ["A","C"] as Set + intersection == ["A", "C"] as Set when: // singles intersection = FpKit.intersection(set1, singleSetA) @@ -130,8 +224,15 @@ class FpKitTest extends Specification { intersection.isEmpty() when: // disjoint reversed - intersection = FpKit.intersection(disjointSet,set1) + intersection = FpKit.intersection(disjointSet, set1) then: intersection.isEmpty() } + + def "test sized allocation"() { + when: + def newArrayList = FpKit.arrayListSizedTo(["a", "b", "c"]) + then: + newArrayList instanceof ArrayList + } } diff --git a/src/test/groovy/graphql/validation/rules/SubscriptionRootFieldNoSkipNoIncludeTest.groovy b/src/test/groovy/graphql/validation/rules/SubscriptionRootFieldNoSkipNoIncludeTest.groovy deleted file mode 100644 index 31b526fe4c..0000000000 --- a/src/test/groovy/graphql/validation/rules/SubscriptionRootFieldNoSkipNoIncludeTest.groovy +++ /dev/null @@ -1,154 +0,0 @@ -package graphql.validation.rules - -import graphql.parser.Parser -import graphql.validation.SpecValidationSchema -import graphql.validation.ValidationError -import graphql.validation.Validator -import spock.lang.Specification - -class SubscriptionRootFieldNoSkipNoIncludeTest extends Specification { - - def "valid subscription with @skip and @include directives on subfields"() { - given: - def query = """ - subscription MySubscription(\$bool: Boolean = true) { - dog { - name @skip(if: \$bool) - nickname @include(if: \$bool) - } - } - """ - - when: - def validationErrors = validate(query) - - then: - validationErrors.isEmpty() - } - - def "invalid subscription with @skip directive on root field"() { - given: - def query = """ - subscription MySubscription(\$bool: Boolean = false) { - dog @skip(if: \$bool) { - name - } - dog @include(if: true) { - nickname - } - } - """ - - when: - def validationErrors = validate(query) - - then: - validationErrors.size() == 1 - validationErrors.first().getMessage().contains("Subscription operation 'MySubscription' root field 'dog' must not use @skip nor @include directives in top level selection") - } - - def "invalid subscription with @include directive on root field"() { - given: - def query = """ - subscription MySubscription(\$bool: Boolean = true) { - dog @include(if: \$bool) { - name - } - } - """ - - when: - def validationErrors = validate(query) - - then: - validationErrors.size() == 1 - validationErrors.first().getMessage().contains("Subscription operation 'MySubscription' root field 'dog' must not use @skip nor @include directives in top level selection") - } - - def "invalid subscription with directive on root field in fragment spread"() { - given: - def query = """ - subscription MySubscription(\$bool: Boolean = false) { - ...dogFragment - } - - fragment dogFragment on SubscriptionRoot { - dog @skip(if: \$bool) { - name - } - } - """ - - when: - def validationErrors = validate(query) - - then: - validationErrors.size() == 1 - validationErrors.first().getMessage().contains("Subscription operation 'MySubscription' root field 'dog' must not use @skip nor @include directives in top level selection") - } - - def "invalid subscription with directive on root field in inline fragment"() { - given: - def query = """ - subscription MySubscription(\$bool: Boolean = true) { - ... on SubscriptionRoot { - dog @include(if: \$bool) { - name - } - } - } - """ - - when: - def validationErrors = validate(query) - - then: - validationErrors.size() == 1 - validationErrors.first().getMessage().contains("Subscription operation 'MySubscription' root field 'dog' must not use @skip nor @include directives in top level selection") - } - - def "@skip and @include directives are valid on query root fields"() { - given: - def query = """ - query MyQuery { - pet @skip(if: false) { - name - } - pet @include(if: true) { - name - } - } - """ - - when: - def validationErrors = validate(query) - - then: - validationErrors.size() == 0 - } - - def "@skip and @include directives are valid on mutation root fields"() { - given: - def query = """ - mutation MyMutation { - createDog(input: {id: "a"}) @skip(if: false) { - name - } - createDog(input: {id: "a"}) @include(if: true) { - name - } - } - """ - - when: - def validationErrors = validate(query) - - then: - validationErrors.size() == 0 - } - - static List validate(String query) { - def document = new Parser().parseDocument(query) - return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) - } -} diff --git a/src/test/groovy/graphql/validation/rules/SubscriptionRootFieldTest.groovy b/src/test/groovy/graphql/validation/rules/SubscriptionUniqueRootFieldTest.groovy similarity index 99% rename from src/test/groovy/graphql/validation/rules/SubscriptionRootFieldTest.groovy rename to src/test/groovy/graphql/validation/rules/SubscriptionUniqueRootFieldTest.groovy index 53f0d7fe50..9b171f2256 100644 --- a/src/test/groovy/graphql/validation/rules/SubscriptionRootFieldTest.groovy +++ b/src/test/groovy/graphql/validation/rules/SubscriptionUniqueRootFieldTest.groovy @@ -7,7 +7,7 @@ import graphql.validation.ValidationErrorType import graphql.validation.Validator import spock.lang.Specification -class SubscriptionRootFieldTest extends Specification { +class SubscriptionUniqueRootFieldTest extends Specification { def "5.2.3.1 subscription with only one root field passes validation"() { given: def subscriptionOneRoot = ''' @@ -286,7 +286,6 @@ class SubscriptionRootFieldTest extends Specification { then: validationErrors.empty } - static List validate(String query) { def document = new Parser().parseDocument(query) return new Validator().validateDocument(SpecValidationSchema.specValidationSchema, document, Locale.ENGLISH) diff --git a/src/test/groovy/readme/DataLoaderBatchingExamples.java b/src/test/groovy/readme/DataLoaderBatchingExamples.java index 287d4c5650..1bf55e0452 100644 --- a/src/test/groovy/readme/DataLoaderBatchingExamples.java +++ b/src/test/groovy/readme/DataLoaderBatchingExamples.java @@ -171,7 +171,7 @@ public CompletableFuture clear() { } }; - DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(crossRequestValueCache); + DataLoaderOptions options = DataLoaderOptions.newOptions().setValueCache(crossRequestValueCache).build(); DataLoader dataLoader = DataLoaderFactory.newDataLoader(batchLoader, options); } @@ -260,7 +260,7 @@ public Object getContext() { // // this creates an overall context for the dataloader // - DataLoaderOptions loaderOptions = DataLoaderOptions.newOptions().setBatchLoaderContextProvider(contextProvider); + DataLoaderOptions loaderOptions = DataLoaderOptions.newOptions().setBatchLoaderContextProvider(contextProvider).build(); DataLoader characterDataLoader = DataLoaderFactory.newDataLoader(batchLoaderWithCtx, loaderOptions); // .... later in your data fetcher