-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Added support for reactive Publishers to be returned from data fetchers #3731
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e514e7f
cf2038e
484e534
a8cbd97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package graphql; | ||
|
|
||
| import java.lang.annotation.Retention; | ||
| import java.lang.annotation.RetentionPolicy; | ||
| import java.lang.annotation.Target; | ||
|
|
||
| import static java.lang.annotation.ElementType.METHOD; | ||
| import static java.lang.annotation.ElementType.PARAMETER; | ||
|
|
||
| /** | ||
| * An annotation that marks a method return value or method parameter as returning a duck type value. | ||
| * <p> | ||
| * For efficiency reasons, the graphql engine methods can return {@link Object} values | ||
| * which maybe two well known types of values. Often a {@link java.util.concurrent.CompletableFuture} | ||
| * or a plain old {@link Object}, to represent an async value or a materialised value. | ||
| */ | ||
| @Internal | ||
| @Retention(RetentionPolicy.RUNTIME) | ||
| @Target(value = {METHOD, PARAMETER}) | ||
| public @interface DuckTyped { | ||
| String shape(); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -170,6 +170,34 @@ public ValueUnboxer getValueUnboxer() { | |
| return valueUnboxer; | ||
| } | ||
|
|
||
| /** | ||
| * @return true if the current operation is a Query | ||
| */ | ||
| public boolean isQueryOperation() { | ||
| return isOpType(OperationDefinition.Operation.QUERY); | ||
| } | ||
|
|
||
| /** | ||
| * @return true if the current operation is a Mutation | ||
| */ | ||
| public boolean isMutationOperation() { | ||
| return isOpType(OperationDefinition.Operation.MUTATION); | ||
| } | ||
|
|
||
| /** | ||
| * @return true if the current operation is a Subscription | ||
| */ | ||
| public boolean isSubscriptionOperation() { | ||
| return isOpType(OperationDefinition.Operation.SUBSCRIPTION); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hav long wanted this. Checking operationDefinition.getOperation() is a PITA
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice |
||
|
|
||
| private boolean isOpType(OperationDefinition.Operation operation) { | ||
| if (operationDefinition != null) { | ||
| return operation.equals(operationDefinition.getOperation()); | ||
| } | ||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * This method will only put one error per field path. | ||
| * | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,6 +2,7 @@ | |
|
|
||
| import com.google.common.collect.ImmutableList; | ||
| import com.google.common.collect.Maps; | ||
| import graphql.DuckTyped; | ||
| import graphql.ExecutionResult; | ||
| import graphql.ExecutionResultImpl; | ||
| import graphql.ExperimentalApi; | ||
|
|
@@ -24,6 +25,7 @@ | |
| import graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters; | ||
| import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters; | ||
| import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters; | ||
| import graphql.execution.reactive.ReactiveSupport; | ||
| import graphql.extensions.ExtensionsBuilder; | ||
| import graphql.introspection.Introspection; | ||
| import graphql.language.Argument; | ||
|
|
@@ -197,8 +199,8 @@ public static String mkNameForPath(List<Field> currentField) { | |
| * @throws NonNullableFieldWasNullException in the {@link CompletableFuture} if a non-null field resolved to a null value | ||
| */ | ||
| @SuppressWarnings("unchecked") | ||
| protected Object /* CompletableFuture<Map<String, Object>> | Map<String, Object> */ | ||
| executeObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { | ||
| @DuckTyped(shape = "CompletableFuture<Map<String, Object>> | Map<String, Object>") | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this annotation reads better in the IDEA rather than a comment. Maybe we can use it later in some way but honestly its just a more type safe way of saying the same as we try to say in the comment
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice idea. Makes it more official than a comment |
||
| protected Object executeObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters) throws NonNullableFieldWasNullException { | ||
| DataLoaderDispatchStrategy dataLoaderDispatcherStrategy = executionContext.getDataLoaderDispatcherStrategy(); | ||
| dataLoaderDispatcherStrategy.executeObject(executionContext, parameters); | ||
| Instrumentation instrumentation = executionContext.getInstrumentation(); | ||
|
|
@@ -356,8 +358,8 @@ Async.CombinedBuilder<FieldValueInfo> getAsyncFieldValueInfo( | |
| * @throws NonNullableFieldWasNullException in the future if a non-null field resolved to a null value | ||
| */ | ||
| @SuppressWarnings("unchecked") | ||
| protected Object /* CompletableFuture<Object> | Object */ | ||
| resolveField(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
| @DuckTyped(shape = " CompletableFuture<Object> | Object") | ||
| protected Object resolveField(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
| Object fieldWithInfo = resolveFieldWithInfo(executionContext, parameters); | ||
| if (fieldWithInfo instanceof CompletableFuture) { | ||
| return ((CompletableFuture<FieldValueInfo>) fieldWithInfo).thenCompose(FieldValueInfo::getFieldValueFuture); | ||
|
|
@@ -384,8 +386,8 @@ Async.CombinedBuilder<FieldValueInfo> getAsyncFieldValueInfo( | |
| * if a nonnull field resolves to a null value | ||
| */ | ||
| @SuppressWarnings("unchecked") | ||
| protected Object /* CompletableFuture<FieldValueInfo> | FieldValueInfo */ | ||
| resolveFieldWithInfo(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
| @DuckTyped(shape = "CompletableFuture<FieldValueInfo> | FieldValueInfo") | ||
| protected Object resolveFieldWithInfo(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
| GraphQLFieldDefinition fieldDef = getFieldDef(executionContext, parameters, parameters.getField().getSingleField()); | ||
| Supplier<ExecutionStepInfo> executionStepInfo = FpKit.intraThreadMemoize(() -> createExecutionStepInfo(executionContext, parameters, fieldDef, null)); | ||
|
|
||
|
|
@@ -430,16 +432,16 @@ Async.CombinedBuilder<FieldValueInfo> getAsyncFieldValueInfo( | |
| * | ||
| * @throws NonNullableFieldWasNullException in the future if a non null field resolves to a null value | ||
| */ | ||
| protected Object /*CompletableFuture<FetchedValue> | FetchedValue>*/ | ||
| fetchField(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
| @DuckTyped(shape = "CompletableFuture<FetchedValue> | FetchedValue") | ||
| protected Object fetchField(ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
| MergedField field = parameters.getField(); | ||
| GraphQLObjectType parentType = (GraphQLObjectType) parameters.getExecutionStepInfo().getUnwrappedNonNullType(); | ||
| GraphQLFieldDefinition fieldDef = getFieldDef(executionContext.getGraphQLSchema(), parentType, field.getSingleField()); | ||
| return fetchField(fieldDef, executionContext, parameters); | ||
| } | ||
|
|
||
| private Object /*CompletableFuture<FetchedValue> | FetchedValue>*/ | ||
| fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
| @DuckTyped(shape = "CompletableFuture<FetchedValue> | FetchedValue") | ||
| private Object fetchField(GraphQLFieldDefinition fieldDef, ExecutionContext executionContext, ExecutionStrategyParameters parameters) { | ||
|
|
||
| if (incrementAndCheckMaxNodesExceeded(executionContext)) { | ||
| return new FetchedValue(null, Collections.emptyList(), null); | ||
|
|
@@ -498,6 +500,11 @@ Async.CombinedBuilder<FieldValueInfo> getAsyncFieldValueInfo( | |
| executionContext.getDataLoaderDispatcherStrategy().fieldFetched(executionContext, parameters, dataFetcher, fetchedObject); | ||
| fetchCtx.onDispatched(); | ||
| fetchCtx.onFetchedValue(fetchedObject); | ||
| // if it's a subscription, leave any reactive objects alone | ||
| if (!executionContext.isSubscriptionOperation()) { | ||
| // possible convert reactive objects into CompletableFutures | ||
| fetchedObject = ReactiveSupport.fetchedObject(fetchedObject); | ||
| } | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the main code - if its NOT a Subscription, we turns the Pubisher into a CF |
||
| if (fetchedObject instanceof CompletableFuture) { | ||
| @SuppressWarnings("unchecked") | ||
| CompletableFuture<Object> fetchedValue = (CompletableFuture<Object>) fetchedObject; | ||
|
|
@@ -737,8 +744,8 @@ private FieldValueInfo getFieldValueInfoForNull(ExecutionStrategyParameters para | |
| * | ||
| * @throws NonNullableFieldWasNullException inside the {@link CompletableFuture} if a non-null field resolves to a null value | ||
| */ | ||
| protected Object /* CompletableFuture<Object> | Object */ | ||
| completeValueForNull(ExecutionStrategyParameters parameters) { | ||
| @DuckTyped(shape = "CompletableFuture<Object> | Object") | ||
| protected Object completeValueForNull(ExecutionStrategyParameters parameters) { | ||
| try { | ||
| return parameters.getNonNullFieldValidator().validate(parameters, null); | ||
| } catch (Exception e) { | ||
|
|
@@ -876,8 +883,8 @@ protected <T> void handleValueException(CompletableFuture<T> overallResult, Thro | |
| * | ||
| * @return a materialized scalar value or exceptionally completed {@link CompletableFuture} if there is a problem | ||
| */ | ||
| protected Object /* CompletableFuture<Object> | Object */ | ||
| completeValueForScalar(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLScalarType scalarType, Object result) { | ||
| @DuckTyped(shape = "CompletableFuture<Object> | Object") | ||
| protected Object completeValueForScalar(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLScalarType scalarType, Object result) { | ||
| Object serialized; | ||
| try { | ||
| serialized = scalarType.getCoercing().serialize(result, executionContext.getGraphQLContext(), executionContext.getLocale()); | ||
|
|
@@ -903,8 +910,8 @@ protected <T> void handleValueException(CompletableFuture<T> overallResult, Thro | |
| * | ||
| * @return a materialized enum value or exceptionally completed {@link CompletableFuture} if there is a problem | ||
| */ | ||
| protected Object /* CompletableFuture<Object> | Object */ | ||
| completeValueForEnum(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLEnumType enumType, Object result) { | ||
| @DuckTyped(shape = "CompletableFuture<Object> | Object") | ||
| protected Object completeValueForEnum(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLEnumType enumType, Object result) { | ||
| Object serialized; | ||
| try { | ||
| serialized = enumType.serialize(result, executionContext.getGraphQLContext(), executionContext.getLocale()); | ||
|
|
@@ -929,8 +936,8 @@ protected <T> void handleValueException(CompletableFuture<T> overallResult, Thro | |
| * | ||
| * @return a {@link CompletableFuture} promise to a map of object field values or a materialized map of object field values | ||
| */ | ||
| protected Object /* CompletableFuture<Map<String, Object>> | Map<String, Object> */ | ||
| completeValueForObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLObjectType resolvedObjectType, Object result) { | ||
| @DuckTyped(shape = "CompletableFuture<Map<String, Object>> | Map<String, Object>") | ||
| protected Object completeValueForObject(ExecutionContext executionContext, ExecutionStrategyParameters parameters, GraphQLObjectType resolvedObjectType, Object result) { | ||
| ExecutionStepInfo executionStepInfo = parameters.getExecutionStepInfo(); | ||
|
|
||
| FieldCollectorParameters collectorParameters = newParameters() | ||
|
|
@@ -1000,7 +1007,6 @@ private void handleTypeMismatchProblem(ExecutionContext context, ExecutionStrate | |
| * if max nodes were exceeded for this request. | ||
| * | ||
| * @param executionContext the execution context in play | ||
| * | ||
| * @return true if max nodes were exceeded | ||
| */ | ||
| private boolean incrementAndCheckMaxNodesExceeded(ExecutionContext executionContext) { | ||
|
|
@@ -1053,7 +1059,6 @@ protected GraphQLFieldDefinition getFieldDef(GraphQLSchema schema, GraphQLObject | |
| * | ||
| * @param e this indicates that a null value was returned for a non null field, which needs to cause the parent field | ||
| * to become null OR continue on as an exception | ||
| * | ||
| * @throws NonNullableFieldWasNullException if a non null field resolves to a null value | ||
| */ | ||
| protected void assertNonNullFieldPrecondition(NonNullableFieldWasNullException e) throws NonNullableFieldWasNullException { | ||
|
|
@@ -1167,4 +1172,6 @@ private static void addErrorsToRightContext(List<GraphQLError> errors, Execution | |
| executionContext.addErrors(errors); | ||
| } | ||
| } | ||
|
|
||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is better than a comment
versus