graphql.execution.incremental.DeferredFragmentCall Maven / Gradle / Ivy
package graphql.execution.incremental;
import com.google.common.collect.ImmutableList;
import graphql.ExecutionResult;
import graphql.GraphQLError;
import graphql.Internal;
import graphql.execution.Async;
import graphql.execution.NonNullableFieldWasNullError;
import graphql.execution.NonNullableFieldWasNullException;
import graphql.execution.ResultPath;
import graphql.incremental.DeferPayload;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.function.Supplier;
/**
* Represents a deferred call (aka @defer) to get an execution result sometime after the initial query has returned.
*
* A deferred call can encompass multiple fields. The deferred call will resolve once all sub-fields resolve.
*
* For example, this query:
*
* {
* post {
* ... @defer(label: "defer-post") {
* text
* summary
* }
* }
* }
*
* Will result on 1 instance of `DeferredCall`, containing calls for the 2 fields: "text" and "summary".
*/
@Internal
public class DeferredFragmentCall implements IncrementalCall {
private final String label;
public ResultPath getPath() {
return path;
}
private final ResultPath path;
private final List>> calls;
private final DeferredCallContext deferredCallContext;
public DeferredFragmentCall(
String label,
ResultPath path,
List>> calls,
DeferredCallContext deferredCallContext
) {
this.label = label;
this.path = path;
this.calls = calls;
this.deferredCallContext = deferredCallContext;
}
@Override
public CompletableFuture invoke() {
Async.CombinedBuilder futures = Async.ofExpectedSize(calls.size());
calls.forEach(call -> {
CompletableFuture cf = call.get();
futures.add(cf);
});
return futures.await()
.thenApply(this::transformToDeferredPayload)
.handle(this::handleNonNullableFieldError);
}
/**
* Non-nullable errors need special treatment.
* When they happen, all the sibling fields will be ignored in the result. So as soon as one of the field calls
* throw this error, we can ignore the {@link ExecutionResult} from all the fields associated with this {@link DeferredFragmentCall}
* and build a special {@link DeferPayload} that captures the details of the error.
*/
private DeferPayload handleNonNullableFieldError(DeferPayload result, Throwable throwable) {
if (throwable != null) {
Throwable cause = throwable.getCause();
if (cause instanceof NonNullableFieldWasNullException) {
GraphQLError error = new NonNullableFieldWasNullError((NonNullableFieldWasNullException) cause);
return DeferPayload.newDeferredItem()
.errors(Collections.singletonList(error))
.label(label)
.path(path)
.build();
}
if (cause instanceof CompletionException) {
throw (CompletionException) cause;
}
throw new CompletionException(cause);
}
return result;
}
private DeferPayload transformToDeferredPayload(List fieldWithExecutionResults) {
List errorsEncountered = deferredCallContext.getErrors();
Map dataMap = new HashMap<>();
ImmutableList.Builder errorsBuilder = ImmutableList.builder();
fieldWithExecutionResults.forEach(entry -> {
dataMap.put(entry.resultKey, entry.executionResult.getData());
errorsBuilder.addAll(entry.executionResult.getErrors());
});
return DeferPayload.newDeferredItem()
.errors(errorsEncountered)
.path(path)
.label(label)
.data(dataMap)
.build();
}
public static class FieldWithExecutionResult {
private final String resultKey;
private final ExecutionResult executionResult;
public FieldWithExecutionResult(String resultKey, ExecutionResult executionResult) {
this.resultKey = resultKey;
this.executionResult = executionResult;
}
public ExecutionResult getExecutionResult() {
return executionResult;
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy