Skip to content
Closed
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
24 changes: 21 additions & 3 deletions src/main/java/graphql/execution/DataFetcherResult.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
public class DataFetcherResult<T> {

private final T data;
private final boolean stopPathExecution;
private final List<GraphQLError> errors;
private final Object localContext;

Expand All @@ -44,13 +45,14 @@ public class DataFetcherResult<T> {
@Deprecated
@DeprecatedAt("2019-01-11")
public DataFetcherResult(T data, List<GraphQLError> errors) {
this(data, errors, null);
this(data, errors, null, false);
}

private DataFetcherResult(T data, List<GraphQLError> errors, Object localContext) {
private DataFetcherResult(T data, List<GraphQLError> errors, Object localContext, boolean stopPathExecution) {
this.data = data;
this.errors = ImmutableList.copyOf(assertNotNull(errors));
this.localContext = localContext;
this.stopPathExecution = stopPathExecution;
}

/**
Expand All @@ -60,6 +62,15 @@ public T getData() {
return data;
}

/**
* if this is set then the graphql engine will stop executing any path under this value
*
* @return true if you want the engine to stop execution on this path and accept the value as is
*/
public boolean isStopPathExecution() {
return stopPathExecution;
}

/**
* @return errors encountered when fetching data. This will be non null but possibly empty.
*/
Expand Down Expand Up @@ -110,11 +121,13 @@ public static <T> Builder<T> newResult() {

public static class Builder<T> {
private T data;
private boolean stopPathExecution =false;
private Object localContext;
private final List<GraphQLError> errors = new ArrayList<>();

public Builder(DataFetcherResult<T> existing) {
data = existing.getData();
stopPathExecution = existing.isStopPathExecution();
localContext = existing.getLocalContext();
errors.addAll(existing.getErrors());
}
Expand All @@ -131,6 +144,11 @@ public Builder<T> data(T data) {
return this;
}

public Builder<T> stopPathExecution(boolean stopPathExecution) {
this.stopPathExecution = stopPathExecution;
return this;
}

public Builder<T> errors(List<GraphQLError> errors) {
this.errors.addAll(errors);
return this;
Expand Down Expand Up @@ -159,7 +177,7 @@ public Builder<T> localContext(Object localContext) {
}

public DataFetcherResult<T> build() {
return new DataFetcherResult<>(data, errors, localContext);
return new DataFetcherResult<>(data, errors, localContext, stopPathExecution);
}
}
}
18 changes: 15 additions & 3 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -330,12 +330,14 @@ protected FetchedValue unboxPossibleDataFetcherResult(ExecutionContext execution
.rawFetchedValue(dataFetcherResult.getData())
.errors(dataFetcherResult.getErrors())
.localContext(localContext)
.stopPathExecution(dataFetcherResult.isStopPathExecution())
.build();
} else {
return FetchedValue.newFetchedValue()
.fetchedValue(executionContext.getValueUnboxer().unbox(result))
.rawFetchedValue(result)
.localContext(parameters.getLocalContext())
.stopPathExecution(false)
.build();
}
}
Expand Down Expand Up @@ -402,6 +404,7 @@ protected FieldValueInfo completeField(ExecutionContext executionContext, Execut
.source(fetchedValue.getFetchedValue())
.localContext(fetchedValue.getLocalContext())
.nonNullFieldValidator(nonNullableFieldValidator)
.stopPathExecution(fetchedValue.isStopPathExecution())
);

if (log.isDebugEnabled()) {
Expand Down Expand Up @@ -504,6 +507,10 @@ protected FieldValueInfo completeValueForList(ExecutionContext executionContext,
if (resultIterable == null) {
return FieldValueInfo.newFieldValueInfo(LIST).fieldValue(completedFuture(new ExecutionResultImpl(null, executionContext.getErrors()))).build();
}
if (parameters.isStopPathExecution()) {
// they have asked us to stop execution of this path and accept the value as is!
return FieldValueInfo.newFieldValueInfo(LIST).fieldValue(completedFuture(new ExecutionResultImpl(resultIterable, executionContext.getErrors()))).build();
}
return completeValueForList(executionContext, parameters, resultIterable);
}

Expand Down Expand Up @@ -539,16 +546,17 @@ protected FieldValueInfo completeValueForList(ExecutionContext executionContext,
NonNullableFieldValidator nonNullableFieldValidator = new NonNullableFieldValidator(executionContext, stepInfoForListElement);

int finalIndex = index;
FetchedValue value = unboxPossibleDataFetcherResult(executionContext, parameters, item);
FetchedValue fetchedValue = unboxPossibleDataFetcherResult(executionContext, parameters, item);

ExecutionStrategyParameters newParameters = parameters.transform(builder ->
builder.executionStepInfo(stepInfoForListElement)
.nonNullFieldValidator(nonNullableFieldValidator)
.listSize(size.orElse(-1)) // -1 signals that we don't know the size
.localContext(value.getLocalContext())
.localContext(fetchedValue.getLocalContext())
.currentListIndex(finalIndex)
.path(indexedPath)
.source(value.getFetchedValue())
.source(fetchedValue.getFetchedValue())
.stopPathExecution(fetchedValue.isStopPathExecution())
);
fieldValueInfos.add(completeValue(executionContext, newParameters));
index++;
Expand Down Expand Up @@ -643,6 +651,10 @@ protected CompletableFuture<ExecutionResult> completeValueForEnum(ExecutionConte
* @return a promise to an {@link ExecutionResult}
*/
protected CompletableFuture<ExecutionResult> completeValueForObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLObjectType resolvedObjectType, Object result) {
if (parameters.isStopPathExecution()) {
// they have asked us to stop execution and hence we send back the object AS IS - no coercing - no sub selection
return CompletableFuture.completedFuture(new ExecutionResultImpl(result, executionContext.getErrors()));
}
ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo();

FieldCollectorParameters collectorParameters = newParameters()
Expand Down
17 changes: 16 additions & 1 deletion src/main/java/graphql/execution/ExecutionStrategyParameters.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public class ExecutionStrategyParameters {
private final int currentListIndex;
private final ExecutionStrategyParameters parent;

private final boolean stopPathExecution;

private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo,
Object source,
Object localContext,
Expand All @@ -32,6 +34,7 @@ private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo,
MergedField currentField,
int listSize,
int currentListIndex,
boolean stopPathExecution,
ExecutionStrategyParameters parent) {

this.executionStepInfo = assertNotNull(executionStepInfo, () -> "executionStepInfo is null");
Expand All @@ -43,6 +46,7 @@ private ExecutionStrategyParameters(ExecutionStepInfo executionStepInfo,
this.currentField = currentField;
this.listSize = listSize;
this.currentListIndex = currentListIndex;
this.stopPathExecution = stopPathExecution;
this.parent = parent;
}

Expand Down Expand Up @@ -78,6 +82,10 @@ public int getCurrentListIndex() {
return currentListIndex;
}

public boolean isStopPathExecution() {
return stopPathExecution;
}

public ExecutionStrategyParameters getParent() {
return parent;
}
Expand Down Expand Up @@ -121,6 +129,7 @@ public static class Builder {
MergedField currentField;
int listSize;
int currentListIndex;
boolean stopPathExecution = false;
ExecutionStrategyParameters parent;

/**
Expand All @@ -143,6 +152,7 @@ private Builder(ExecutionStrategyParameters oldParameters) {
this.parent = oldParameters.parent;
this.listSize = oldParameters.listSize;
this.currentListIndex = oldParameters.currentListIndex;
this.stopPathExecution = oldParameters.stopPathExecution;
}

public Builder executionStepInfo(ExecutionStepInfo executionStepInfo) {
Expand Down Expand Up @@ -195,14 +205,19 @@ public Builder currentListIndex(int currentListIndex) {
return this;
}

public Builder stopPathExecution(boolean stopPathExecution) {
this.stopPathExecution = stopPathExecution;
return this;
}

public Builder parent(ExecutionStrategyParameters parent) {
this.parent = parent;
return this;
}


public ExecutionStrategyParameters build() {
return new ExecutionStrategyParameters(executionStepInfo, source, localContext, fields, nonNullableFieldValidator, path, currentField, listSize, currentListIndex, parent);
return new ExecutionStrategyParameters(executionStepInfo, source, localContext, fields, nonNullableFieldValidator, path, currentField, listSize, currentListIndex, stopPathExecution, parent);
}
}
}
17 changes: 15 additions & 2 deletions src/main/java/graphql/execution/FetchedValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
@PublicApi
public class FetchedValue {
private final Object fetchedValue;

private final boolean stopPathExecution;
private final Object rawFetchedValue;
private final Object localContext;
private final ImmutableList<GraphQLError> errors;

private FetchedValue(Object fetchedValue, Object rawFetchedValue, ImmutableList<GraphQLError> errors, Object localContext) {
private FetchedValue(Object fetchedValue, Object rawFetchedValue, ImmutableList<GraphQLError> errors, Object localContext, boolean stopPathExecution) {
this.fetchedValue = fetchedValue;
this.stopPathExecution = stopPathExecution;
this.rawFetchedValue = rawFetchedValue;
this.errors = errors;
this.localContext = localContext;
Expand All @@ -45,6 +48,10 @@ public Object getLocalContext() {
return localContext;
}

public boolean isStopPathExecution() {
return stopPathExecution;
}

public FetchedValue transform(Consumer<Builder> builderConsumer) {
Builder builder = newFetchedValue(this);
builderConsumer.accept(builder);
Expand Down Expand Up @@ -77,6 +84,7 @@ public static Builder newFetchedValue(FetchedValue otherValue) {
public static class Builder {

private Object fetchedValue;
private boolean stopPathExecution = false;
private Object rawFetchedValue;
private Object localContext;
private ImmutableList<GraphQLError> errors = ImmutableList.of();
Expand All @@ -86,6 +94,11 @@ public Builder fetchedValue(Object fetchedValue) {
return this;
}

public Builder stopPathExecution(boolean stopPathExecution) {
this.stopPathExecution = stopPathExecution;
return this;
}

public Builder rawFetchedValue(Object rawFetchedValue) {
this.rawFetchedValue = rawFetchedValue;
return this;
Expand All @@ -102,7 +115,7 @@ public Builder errors(List<GraphQLError> errors) {
}

public FetchedValue build() {
return new FetchedValue(fetchedValue, rawFetchedValue, errors, localContext);
return new FetchedValue(fetchedValue, rawFetchedValue, errors, localContext, stopPathExecution);
}
}
}
104 changes: 104 additions & 0 deletions src/test/groovy/graphql/execution/StopPathExecutionTest.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package graphql.execution


import graphql.GraphQL
import graphql.TestUtil
import graphql.schema.DataFetcher
import graphql.schema.GraphQLSchema
import graphql.schema.idl.RuntimeWiring
import spock.lang.Specification

import static graphql.schema.idl.TypeRuntimeWiring.newTypeWiring

class StopPathExecutionTest extends Specification {

def sdl = """
type Query {
longList : [Thing]
stopList : [Thing]
thing : Thing
stopThing : Thing
}

type Thing {
name : String
age : Int
active : Boolean
}
"""

def dataList = mkList()
def thingo = ["name": "Theo", "age": 42, "active": true]

DataFetcher<?> longListDF = env ->
dataList
DataFetcher<?> stopListDF = env ->
DataFetcherResult.newResult().data(dataList).stopPathExecution(true).build()

DataFetcher<?> thingDF = env -> thingo

DataFetcher<?> stopThingDF = env ->
DataFetcherResult.newResult().data(thingo).stopPathExecution(true).build()

RuntimeWiring runtimeWiring = RuntimeWiring.newRuntimeWiring()
.type(
newTypeWiring("Query")
.dataFetcher("longList", longListDF)
.dataFetcher("stopList", stopListDF)
.dataFetcher("thing", thingDF)
.dataFetcher("stopThing", stopThingDF)
)
.build();
GraphQLSchema graphQLSchema = TestUtil.schema(sdl, runtimeWiring)

GraphQL graphQL = GraphQL.newGraphQL(graphQLSchema).build()

private static List<Object> mkList() {
List<Object> list = new ArrayList<>()
for (int i = 0; i < 200; i++) {
Map<String, Object> obj = new HashMap<>()
obj.put("name", "Name" + i)
obj.put("age", i)
obj.put("active", i % 2 == 0)
list.add(obj)
}
return list
}

def "can stop execution but get results"() {

when:
def er = graphQL.execute("""
query q {
stopList { name age active }
longList { name age active }
thing { name age active }
stopThing { name age active }
}
""")

then:
er.errors.isEmpty()
def stopList = er.data["stopList"] as List
stopList.size() == 200
stopList[0]["name"] == "Name0"
stopList[0]["age"] == 0
stopList[0]["active"] == true

def longList = er.data["longList"] as List
longList.size() == 200
longList[0]["name"] == "Name0"
longList[0]["age"] == 0
longList[0]["active"] == true

def thing = er.data["thing"]
thing["name"] == "Theo"
thing["age"] == 42
thing["active"] == true

def stopThing = er.data["stopThing"]
stopThing["name"] == "Theo"
stopThing["age"] == 42
stopThing["active"] == true
}
}
Loading