Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 134 additions & 68 deletions src/main/java/graphql/execution/ExecutionStrategy.java

Large diffs are not rendered by default.

54 changes: 47 additions & 7 deletions src/main/java/graphql/execution/FieldValueInfo.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,16 @@

import static graphql.Assert.assertNotNull;

/**
* The {@link FieldValueInfo} holds the type of field that was fetched and completed along with the completed value.
* <p>
* A field value is considered when its is both fetch via a {@link graphql.schema.DataFetcher} to a raw value, and then
* it is serialized into scalar or enum or if it's an object type, it is completed as an object given its field sub selection
* <p>
* The {@link #getFieldValueObject()} method returns either a materialized value or a {@link CompletableFuture}
* promise to a materialized value. Simple in-memory values will tend to be materialized, while complicated
* values might need a call to a database or other systems will tend to be {@link CompletableFuture} promises.
*/
@PublicApi
public class FieldValueInfo {

Expand All @@ -19,7 +29,6 @@ public enum CompleteValueType {
NULL,
SCALAR,
ENUM

}

private final CompleteValueType completeValueType;
Expand All @@ -31,33 +40,64 @@ public FieldValueInfo(CompleteValueType completeValueType, Object fieldValueObje
}

public FieldValueInfo(CompleteValueType completeValueType, Object fieldValueObject, List<FieldValueInfo> fieldValueInfos) {
assertNotNull(fieldValueInfos, () -> "fieldValueInfos can't be null");
assertNotNull(fieldValueInfos, "fieldValueInfos can't be null");
this.completeValueType = completeValueType;
this.fieldValueObject = fieldValueObject;
this.fieldValueInfos = fieldValueInfos;
}

/**
* This is an enum that represents the type of field value that was completed for a field
*
* @return the type of field value
*/
public CompleteValueType getCompleteValueType() {
return completeValueType;
}

@Deprecated(since = "2023-09-11")
public CompletableFuture<ExecutionResult> getFieldValue() {
return getFieldValueFuture().thenApply(fv -> ExecutionResultImpl.newExecutionResult().data(fv).build());
/**
* This value can be either an object that is materialized or a {@link CompletableFuture} promise to a value
*
* @return either an object that is materialized or a {@link CompletableFuture} promise to a value
*/
public Object /* CompletableFuture<Object> | Object */ getFieldValueObject() {
return fieldValueObject;
}

/**
* This returns the value in {@link CompletableFuture} form. If it is already a {@link CompletableFuture} it is returned
* directly, otherwise the materialized value is wrapped in a {@link CompletableFuture} and returned
*
* @return a {@link CompletableFuture} promise to the value
*/
public CompletableFuture<Object> getFieldValueFuture() {
return Async.toCompletableFuture(fieldValueObject);
}

public Object /* CompletableFuture<Object> | Object */ getFieldValueObject() {
return fieldValueObject;
/**
* Kept for legacy reasons - this method is no longer sensible and is no longer used by the graphql-java engine
* and is kept only for backwards compatible API reasons.
*
* @return a promise to the {@link ExecutionResult} that wraps the field value.
*/
@Deprecated(since = "2023-09-11")
public CompletableFuture<ExecutionResult> getFieldValue() {
return getFieldValueFuture().thenApply(fv -> ExecutionResultImpl.newExecutionResult().data(fv).build());
}

/**
* @return true if the value is a {@link CompletableFuture} promise to a value
*/
public boolean isFutureValue() {
return fieldValueObject instanceof CompletableFuture;
}

/**
* When the {@link #getCompleteValueType()} is {@link CompleteValueType#LIST} this holds the list
* of completed values inside that list object.
*
* @return the list of completed field values inside a list
*/
public List<FieldValueInfo> getFieldValueInfos() {
return fieldValueInfos;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.Internal;
import graphql.execution.ExecutionContext;
import graphql.execution.ExecutionStrategyParameters;
Expand Down Expand Up @@ -162,7 +163,10 @@ private Supplier<CompletableFuture<DeferredFragmentCall.FieldWithExecutionResult
// Create a reference to the CompletableFuture that resolves an ExecutionResult
// so we can pass it to the Instrumentation "onDispatched" callback.
CompletableFuture<ExecutionResult> executionResultCF = fieldValueResult
.thenCompose(FieldValueInfo::getFieldValue);
.thenCompose(fvi -> fvi
.getFieldValueFuture()
.thenApply(fv -> ExecutionResultImpl.newExecutionResult().data(fv).build())
);

return executionResultCF
.thenApply(executionResult ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@
import graphql.schema.FieldCoordinates;
import graphql.schema.GraphQLCompositeType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLFieldsContainer;
import graphql.schema.GraphQLInterfaceType;
import graphql.schema.GraphQLNamedOutputType;
import graphql.schema.GraphQLObjectType;
Expand Down Expand Up @@ -326,6 +325,7 @@ public static ExecutableNormalizedOperation createExecutableNormalizedOperation(
* @param operationDefinition the operation to be executed
* @param fragments a set of fragments associated with the operation
* @param coercedVariableValues the coerced variables to use
* @param options the options to use
*
* @return a runtime representation of the graphql operation.
*/
Expand Down
60 changes: 30 additions & 30 deletions src/test/groovy/graphql/execution/ExecutionStrategyTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == result
executionResult == result
}

def "completes value for java.util.Optional"() {
Expand All @@ -186,10 +186,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == expected
executionResult == expected

where:
result || expected
Expand All @@ -212,7 +212,7 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
def e = thrown(CompletionException)
Expand All @@ -234,10 +234,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == expected
executionResult == expected

where:
result || expected
Expand All @@ -260,7 +260,7 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
def e = thrown(CompletionException)
Expand All @@ -282,10 +282,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == expected
executionResult == expected

where:
result || expected
Expand All @@ -308,7 +308,7 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
def e = thrown(CompletionException)
Expand All @@ -330,10 +330,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == expected
executionResult == expected

where:
result || expected
Expand All @@ -356,7 +356,7 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
def e = thrown(CompletionException)
Expand All @@ -380,10 +380,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == result
executionResult == result
}

def "completing value with serializing throwing exception"() {
Expand All @@ -402,10 +402,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == null
executionResult == null
executionContext.errors.size() == 1
executionContext.errors[0] instanceof SerializationError

Expand All @@ -427,10 +427,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == null
executionResult == null
executionContext.errors.size() == 1
executionContext.errors[0] instanceof SerializationError

Expand Down Expand Up @@ -787,10 +787,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.get().data == [1, 2, 3]
executionResult == [1, 2, 3]
}

def "#842 completes value for java.util.Stream"() {
Expand All @@ -811,10 +811,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.get().data == [1, 2, 3]
executionResult == [1, 2, 3]
}

def "#842 completes value for java.util.Iterator"() {
Expand All @@ -835,10 +835,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.get().data == [1, 2, 3]
executionResult == [1, 2, 3]
}


Expand Down Expand Up @@ -941,10 +941,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.get().data == [1L, 2L, 3L]
executionResult == [1L, 2L, 3L]
}

def "when completeValue expects GraphQLList and non iterable or non array is passed then it should yield a TypeMismatch error"() {
Expand All @@ -965,10 +965,10 @@ class ExecutionStrategyTest extends Specification {
.build()

when:
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValue.join()
def executionResult = executionStrategy.completeValue(executionContext, parameters).fieldValueFuture.join()

then:
executionResult.data == null
executionResult == null
executionContext.errors.size() == 1
executionContext.errors[0] instanceof TypeMismatchError
}
Expand Down
Loading