Skip to content

Response factory as config #3950

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

Merged
merged 3 commits into from
Jun 5, 2025
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
10 changes: 1 addition & 9 deletions src/main/java/graphql/GraphQL.java
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,6 @@ public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualCon
private final Instrumentation instrumentation;
private final PreparsedDocumentProvider preparsedDocumentProvider;
private final ValueUnboxer valueUnboxer;
private final ResponseMapFactory responseMapFactory;
private final boolean doNotAutomaticallyDispatchDataLoader;


Expand All @@ -172,7 +171,6 @@ private GraphQL(Builder builder) {
this.instrumentation = assertNotNull(builder.instrumentation, () -> "instrumentation must not be null");
this.preparsedDocumentProvider = assertNotNull(builder.preparsedDocumentProvider, () -> "preparsedDocumentProvider must be non null");
this.valueUnboxer = assertNotNull(builder.valueUnboxer, () -> "valueUnboxer must not be null");
this.responseMapFactory = assertNotNull(builder.responseMapFactory, () -> "responseMapFactory must be not null");
this.doNotAutomaticallyDispatchDataLoader = builder.doNotAutomaticallyDispatchDataLoader;
}

Expand Down Expand Up @@ -282,7 +280,6 @@ public static class Builder {
private PreparsedDocumentProvider preparsedDocumentProvider = NoOpPreparsedDocumentProvider.INSTANCE;
private boolean doNotAutomaticallyDispatchDataLoader = false;
private ValueUnboxer valueUnboxer = ValueUnboxer.DEFAULT;
private ResponseMapFactory responseMapFactory = ResponseMapFactory.DEFAULT;

public Builder(GraphQLSchema graphQLSchema) {
this.graphQLSchema = graphQLSchema;
Expand Down Expand Up @@ -353,11 +350,6 @@ public Builder valueUnboxer(ValueUnboxer valueUnboxer) {
return this;
}

public Builder responseMapFactory(ResponseMapFactory responseMapFactory) {
this.responseMapFactory = responseMapFactory;
return this;
}

public GraphQL build() {
// we use the data fetcher exception handler unless they set their own strategy in which case bets are off
if (queryExecutionStrategy == null) {
Expand Down Expand Up @@ -617,7 +609,7 @@ private CompletableFuture<ExecutionResult> execute(ExecutionInput executionInput
EngineRunningState engineRunningState
) {

Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, valueUnboxer, responseMapFactory, doNotAutomaticallyDispatchDataLoader);
Execution execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, valueUnboxer, doNotAutomaticallyDispatchDataLoader);
ExecutionId executionId = executionInput.getExecutionId();

return execution.execute(document, graphQLSchema, executionId, executionInput, instrumentationState, engineRunningState);
Expand Down
32 changes: 32 additions & 0 deletions src/main/java/graphql/GraphQLUnusualConfiguration.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package graphql;

import graphql.execution.ResponseMapFactory;
import graphql.execution.instrumentation.dataloader.DelayedDataLoaderDispatcherExecutorFactory;
import graphql.introspection.GoodFaithIntrospection;
import graphql.parser.ParserOptions;
Expand Down Expand Up @@ -272,6 +273,13 @@ public DataloaderConfig dataloaderConfig() {
return new DataloaderConfig(this);
}

/**
* @return an element that allows you to control the {@link ResponseMapFactory} used
*/
public ResponseMapFactoryConfig responseMapFactory() {
return new ResponseMapFactoryConfig(this);
}

private void put(String named, Object value) {
if (graphQLContext != null) {
graphQLContext.put(named, value);
Expand Down Expand Up @@ -393,4 +401,28 @@ public DataloaderConfig delayedDataLoaderExecutorFactory(DelayedDataLoaderDispat
return this;
}
}

public static class ResponseMapFactoryConfig extends BaseContextConfig {
private ResponseMapFactoryConfig(GraphQLContextConfiguration contextConfig) {
super(contextConfig);
}

/**
* @return the {@link ResponseMapFactory} in play - this can be null
*/
@ExperimentalApi
public ResponseMapFactory getOr(ResponseMapFactory defaultFactory) {
ResponseMapFactory responseMapFactory = contextConfig.get(ResponseMapFactory.class.getCanonicalName());
return responseMapFactory != null ? responseMapFactory : defaultFactory;
}

/**
* This controls the {@link ResponseMapFactory} to use for this request
*/
@ExperimentalApi
public ResponseMapFactoryConfig setFactory(ResponseMapFactory factory) {
contextConfig.put(ResponseMapFactory.class.getCanonicalName(), factory);
return this;
}
}
}
9 changes: 5 additions & 4 deletions src/main/java/graphql/execution/Execution.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.ExecutionResultImpl;
import graphql.GraphQL;
import graphql.GraphQLContext;
import graphql.GraphQLError;
import graphql.Internal;
Expand Down Expand Up @@ -54,7 +55,6 @@ public class Execution {
private final ExecutionStrategy subscriptionStrategy;
private final Instrumentation instrumentation;
private final ValueUnboxer valueUnboxer;
private final ResponseMapFactory responseMapFactory;
private final boolean doNotAutomaticallyDispatchDataLoader;


Expand All @@ -63,14 +63,12 @@ public Execution(ExecutionStrategy queryStrategy,
ExecutionStrategy subscriptionStrategy,
Instrumentation instrumentation,
ValueUnboxer valueUnboxer,
ResponseMapFactory responseMapFactory,
boolean doNotAutomaticallyDispatchDataLoader) {
this.queryStrategy = queryStrategy != null ? queryStrategy : new AsyncExecutionStrategy();
this.mutationStrategy = mutationStrategy != null ? mutationStrategy : new AsyncSerialExecutionStrategy();
this.subscriptionStrategy = subscriptionStrategy != null ? subscriptionStrategy : new AsyncExecutionStrategy();
this.instrumentation = instrumentation;
this.valueUnboxer = valueUnboxer;
this.responseMapFactory = responseMapFactory;
this.doNotAutomaticallyDispatchDataLoader = doNotAutomaticallyDispatchDataLoader;
}

Expand All @@ -91,6 +89,9 @@ public CompletableFuture<ExecutionResult> execute(Document document, GraphQLSche

boolean propagateErrorsOnNonNullContractFailure = propagateErrorsOnNonNullContractFailure(getOperationResult.operationDefinition.getDirectives());

ResponseMapFactory responseMapFactory = GraphQL.unusualConfiguration(executionInput.getGraphQLContext())
.responseMapFactory().getOr(ResponseMapFactory.DEFAULT);

ExecutionContext executionContext = newExecutionContextBuilder()
.instrumentation(instrumentation)
.instrumentationState(instrumentationState)
Expand Down Expand Up @@ -284,7 +285,7 @@ private ExecutionResult mergeExtensionsBuilderIfPresent(ExecutionResult executio

private boolean propagateErrorsOnNonNullContractFailure(List<Directive> directives) {
boolean jvmWideEnabled = Directives.isExperimentalDisableErrorPropagationDirectiveEnabled();
if (! jvmWideEnabled) {
if (!jvmWideEnabled) {
return true;
}
Directive foundDirective = NodeUtil.findNodeByName(directives, EXPERIMENTAL_DISABLE_ERROR_PROPAGATION_DIRECTIVE_DEFINITION.getName());
Expand Down
1 change: 1 addition & 0 deletions src/main/java/graphql/execution/ExecutionContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,7 @@ public void addErrors(List<GraphQLError> errors) {
});
}

@Internal
public ResponseMapFactory getResponseMapFactory() {
return responseMapFactory;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,7 @@ public ExecutionContextBuilder dataLoaderDispatcherStrategy(DataLoaderDispatchSt
return this;
}

@Internal
public ExecutionContextBuilder responseMapFactory(ResponseMapFactory responseMapFactory) {
this.responseMapFactory = responseMapFactory;
return this;
Expand Down
2 changes: 1 addition & 1 deletion src/test/groovy/graphql/CustomMapImplementationTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ class CustomMapImplementationTest extends Specification {
it.dataFetcher("people", { List.of(new Person("Mario", 18), new Person("Luigi", 21))})
})
.build())
.responseMapFactory(new CustomResponseMapFactory())
.build()

def "customMapImplementation"() {
Expand All @@ -52,6 +51,7 @@ class CustomMapImplementationTest extends Specification {
}
}
''')
.graphQLContext { it -> GraphQL.unusualConfiguration(it).responseMapFactory().setFactory(new CustomResponseMapFactory())}
.build()

def executionResult = graphql.execute(input)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import graphql.GraphQL
import graphql.GraphQLContext
import graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys
import graphql.execution.instrumentation.dataloader.DelayedDataLoaderDispatcherExecutorFactory
import graphql.execution.ResponseMapFactory
import graphql.introspection.GoodFaithIntrospection
import graphql.parser.ParserOptions
import graphql.schema.PropertyDataFetcherHelper
Expand Down Expand Up @@ -207,4 +208,47 @@ class GraphQLUnusualConfigurationTest extends Specification {
then:
ei.getGraphQLContext().get(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING) == true
}

def "can set response map factory"() {
def rfm1 = new ResponseMapFactory() {
@Override
Map<String, Object> createInsertionOrdered(List<String> keys, List<Object> values) {
return null
}
}

def rfm2 = new ResponseMapFactory() {
@Override
Map<String, Object> createInsertionOrdered(List<String> keys, List<Object> values) {
return null
}
}

when:
def graphqlContextBuilder = GraphQLContext.newContext()
GraphQL.unusualConfiguration(graphqlContextBuilder).responseMapFactory().setFactory(rfm1)

then:
GraphQL.unusualConfiguration(graphqlContextBuilder).responseMapFactory().getOr(rfm2) == rfm1

when:
graphqlContextBuilder = GraphQLContext.newContext()

then: "can default"
GraphQL.unusualConfiguration(graphqlContextBuilder).responseMapFactory().getOr(rfm2) == rfm2

when:
def graphqlContext = GraphQLContext.newContext().build()
GraphQL.unusualConfiguration(graphqlContext).responseMapFactory().setFactory(rfm1)

then:
GraphQL.unusualConfiguration(graphqlContext).responseMapFactory().getOr(rfm2) == rfm1

when:
graphqlContext = GraphQLContext.newContext().build()

then: "can default"
GraphQL.unusualConfiguration(graphqlContext).responseMapFactory().getOr(rfm2) == rfm2

}
}
4 changes: 2 additions & 2 deletions src/test/groovy/graphql/execution/ExecutionTest.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class ExecutionTest extends Specification {
def subscriptionStrategy = new CountingExecutionStrategy()
def mutationStrategy = new CountingExecutionStrategy()
def queryStrategy = new CountingExecutionStrategy()
def execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, SimplePerformantInstrumentation.INSTANCE, ValueUnboxer.DEFAULT, ResponseMapFactory.DEFAULT, false)
def execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, SimplePerformantInstrumentation.INSTANCE, ValueUnboxer.DEFAULT, false)
def emptyExecutionInput = ExecutionInput.newExecutionInput().query("query").build()
def instrumentationState = new InstrumentationState() {}

Expand Down Expand Up @@ -125,7 +125,7 @@ class ExecutionTest extends Specification {
}
}

def execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, ValueUnboxer.DEFAULT, ResponseMapFactory.DEFAULT, false)
def execution = new Execution(queryStrategy, mutationStrategy, subscriptionStrategy, instrumentation, ValueUnboxer.DEFAULT, false)


when:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ class FieldValidationTest extends Specification {
def document = TestUtil.parseQuery(query)
def strategy = new AsyncExecutionStrategy()
def instrumentation = new FieldValidationInstrumentation(validation)
def execution = new Execution(strategy, strategy, strategy, instrumentation, ValueUnboxer.DEFAULT, ResponseMapFactory.DEFAULT, false)
def execution = new Execution(strategy, strategy, strategy, instrumentation, ValueUnboxer.DEFAULT, false)

def executionInput = ExecutionInput.newExecutionInput().query(query).variables(variables).build()
execution.execute(document, schema, ExecutionId.generate(), executionInput, null, new EngineRunningState())
Expand Down