X Tutup
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
22 changes: 22 additions & 0 deletions src/main/java/graphql/DuckTyped.java
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();
}
Copy link
Member Author

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

@DuckedTyped(shape="Object | CompletableFuture<Object>")

versus

/* Object | CompletableFuture<Object> */

3 changes: 2 additions & 1 deletion src/main/java/graphql/Internal.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
Expand All @@ -17,6 +18,6 @@
* In general unnecessary changes will be avoided but you should not depend on internal classes being stable
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value = {CONSTRUCTOR, METHOD, TYPE, FIELD, PACKAGE})
@Target(value = {CONSTRUCTOR, METHOD, TYPE, FIELD, PACKAGE, ANNOTATION_TYPE})
public @interface Internal {
}
28 changes: 28 additions & 0 deletions src/main/java/graphql/execution/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hav long wanted this. Checking operationDefinition.getOperation() is a PITA

Copy link
Member

Choose a reason for hiding this comment

The 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.
*
Expand Down
47 changes: 27 additions & 20 deletions src/main/java/graphql/execution/ExecutionStrategy.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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>")
Copy link
Member Author

Choose a reason for hiding this comment

The 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

Copy link
Member

Choose a reason for hiding this comment

The 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();
Expand Down Expand Up @@ -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);
Expand All @@ -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));

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
Copy link
Member Author

Choose a reason for hiding this comment

The 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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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());
Expand All @@ -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());
Expand All @@ -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()
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -1167,4 +1172,6 @@ private static void addErrorsToRightContext(List<GraphQLError> errors, Execution
executionContext.addErrors(errors);
}
}


}
Loading
X Tutup