All Downloads are FREE. Search and download functionalities are using the official Maven repository.

graphql.execution.incremental.DeferredFragmentCall Maven / Gradle / Ivy

There is a newer version: 230521-nf-execution
Show newest version
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