diff --git a/src/main/java/graphql/ExecutionInput.java b/src/main/java/graphql/ExecutionInput.java index 59efc4f48..7b0a02b2e 100644 --- a/src/main/java/graphql/ExecutionInput.java +++ b/src/main/java/graphql/ExecutionInput.java @@ -222,6 +222,14 @@ public static class Builder { private ExecutionId executionId; private AtomicBoolean cancelled = new AtomicBoolean(false); + /** + * Package level access to the graphql context + * @return shhh but it's the graphql context + */ + GraphQLContext graphQLContext() { + return graphQLContext; + } + public Builder query(String query) { this.query = assertNotNull(query, () -> "query can't be null"); return this; diff --git a/src/main/java/graphql/GraphQL.java b/src/main/java/graphql/GraphQL.java index a279dab2f..d4d8fb3b6 100644 --- a/src/main/java/graphql/GraphQL.java +++ b/src/main/java/graphql/GraphQL.java @@ -85,6 +85,72 @@ @PublicApi public class GraphQL { + /** + * This allows you to control "unusual" aspects of the GraphQL system + * including some JVM wide settings + *

+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration} object + */ + public static GraphQLUnusualConfiguration unusualConfiguration() { + return new GraphQLUnusualConfiguration(); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *

+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(ExecutionInput executionInput) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(executionInput.getGraphQLContext()); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *

+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(ExecutionInput.Builder executionInputBuilder) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(executionInputBuilder.graphQLContext()); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *

+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(GraphQLContext graphQLContext) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(graphQLContext); + } + + /** + * This allows you to control "unusual" per execution aspects of the GraphQL system + *

+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + * + * @return a {@link GraphQLUnusualConfiguration.GraphQLContextConfiguration} object + */ + public static GraphQLUnusualConfiguration.GraphQLContextConfiguration unusualConfiguration(GraphQLContext.Builder graphQLContextBuilder) { + return new GraphQLUnusualConfiguration.GraphQLContextConfiguration(graphQLContextBuilder); + } + private final GraphQLSchema graphQLSchema; private final ExecutionStrategy queryStrategy; private final ExecutionStrategy mutationStrategy; diff --git a/src/main/java/graphql/GraphQLContext.java b/src/main/java/graphql/GraphQLContext.java index 48f79ce77..64ec5c406 100644 --- a/src/main/java/graphql/GraphQLContext.java +++ b/src/main/java/graphql/GraphQLContext.java @@ -326,6 +326,15 @@ public Builder put( ); } + public Object get(Object key) { + return map.get(key); + } + + public boolean getBoolean(Object key) { + return Boolean.parseBoolean(String.valueOf(get(key))); + } + + public Builder of( Object key1, Object value1 ) { diff --git a/src/main/java/graphql/GraphQLUnusualConfiguration.java b/src/main/java/graphql/GraphQLUnusualConfiguration.java new file mode 100644 index 000000000..1f99b5848 --- /dev/null +++ b/src/main/java/graphql/GraphQLUnusualConfiguration.java @@ -0,0 +1,396 @@ +package graphql; + +import graphql.execution.instrumentation.dataloader.DelayedDataLoaderDispatcherExecutorFactory; +import graphql.introspection.GoodFaithIntrospection; +import graphql.parser.ParserOptions; +import graphql.schema.PropertyDataFetcherHelper; + +import java.time.Duration; + +import static graphql.Assert.assertNotNull; +import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS; +import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY; +import static graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING; + +/** + * This allows you to control "unusual" aspects of the GraphQL system + * including some JVM wide settings and some per execution settings + * as well as experimental ones. + *

+ * This is named unusual because in general we don't expect you to + * have to make ths configuration by default, but you can opt into certain features + * or disable them if you want to. + */ +public class GraphQLUnusualConfiguration { + GraphQLUnusualConfiguration() { + } + + /** + * @return an element that allows you to control JVM wide parsing configuration + */ + public ParserConfig parsing() { + return new ParserConfig(this); + } + + /** + * @return an element that allows you to control JVM wide {@link graphql.schema.PropertyDataFetcher} configuration + */ + public PropertyDataFetcherConfig propertyDataFetching() { + return new PropertyDataFetcherConfig(this); + } + + /** + * @return an element that allows you to control JVM wide configuration + * of {@link graphql.introspection.GoodFaithIntrospection} + */ + public GoodFaithIntrospectionConfig goodFaithIntrospection() { + return new GoodFaithIntrospectionConfig(this); + } + + private static class BaseConfig { + protected final GraphQLUnusualConfiguration configuration; + + private BaseConfig(GraphQLUnusualConfiguration configuration) { + this.configuration = configuration; + } + + /** + * @return an element that allows you to chain multiple configuration elements + */ + public GraphQLUnusualConfiguration then() { + return configuration; + } + } + + public static class ParserConfig extends BaseConfig { + + private ParserConfig(GraphQLUnusualConfiguration configuration) { + super(configuration); + } + + /** + * By default, the Parser will not capture ignored characters. A static holds this default + * value in a JVM wide basis options object. + *

+ * Significant memory savings can be made if we do NOT capture ignored characters, + * especially in SDL parsing. + * + * @return the static default JVM value + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserOptions getDefaultParserOptions() { + return ParserOptions.getDefaultParserOptions(); + } + + /** + * By default, the Parser will not capture ignored characters. A static holds this default + * value in a JVM wide basis options object. + *

+ * Significant memory savings can be made if we do NOT capture ignored characters, + * especially in SDL parsing. So we have set this to false by default. + *

+ * This static can be set to true to allow the behavior of version 16.x or before. + * + * @param options - the new default JVM parser options + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserConfig setDefaultParserOptions(ParserOptions options) { + ParserOptions.setDefaultParserOptions(options); + return this; + } + + + /** + * By default, for operation parsing, the Parser will not capture ignored characters, and it will not capture line comments into AST + * elements . A static holds this default value for operation parsing in a JVM wide basis options object. + * + * @return the static default JVM value for operation parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserOptions getDefaultOperationParserOptions() { + return ParserOptions.getDefaultOperationParserOptions(); + } + + /** + * By default, the Parser will not capture ignored characters or line comments. A static holds this default + * value in a JVM wide basis options object for operation parsing. + *

+ * This static can be set to true to allow the behavior of version 16.x or before. + * + * @param options - the new default JVM parser options for operation parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserConfig setDefaultOperationParserOptions(ParserOptions options) { + ParserOptions.setDefaultOperationParserOptions(options); + return this; + } + + /** + * By default, for SDL parsing, the Parser will not capture ignored characters, but it will capture line comments into AST + * elements. The SDL default options allow unlimited tokens and whitespace, since a DOS attack vector is + * not commonly available via schema SDL parsing. + *

+ * A static holds this default value for SDL parsing in a JVM wide basis options object. + * + * @return the static default JVM value for SDL parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + * @see graphql.schema.idl.SchemaParser + */ + public ParserOptions getDefaultSdlParserOptions() { + return ParserOptions.getDefaultSdlParserOptions(); + } + + /** + * By default, for SDL parsing, the Parser will not capture ignored characters, but it will capture line comments into AST + * elements . A static holds this default value for operation parsing in a JVM wide basis options object. + *

+ * This static can be set to true to allow the behavior of version 16.x or before. + * + * @param options - the new default JVM parser options for SDL parsing + * + * @see graphql.language.IgnoredChar + * @see graphql.language.SourceLocation + */ + public ParserConfig setDefaultSdlParserOptions(ParserOptions options) { + ParserOptions.setDefaultSdlParserOptions(options); + return this; + } + } + + public static class PropertyDataFetcherConfig extends BaseConfig { + private PropertyDataFetcherConfig(GraphQLUnusualConfiguration configuration) { + super(configuration); + } + + /** + * PropertyDataFetcher caches the methods and fields that map from a class to a property for runtime performance reasons + * as well as negative misses. + *

+ * However during development you might be using an assistance tool like JRebel to allow you to tweak your code base and this + * caching may interfere with this. So you can call this method to clear the cache. A JRebel plugin could + * be developed to do just that. + */ + @SuppressWarnings("unused") + public PropertyDataFetcherConfig clearReflectionCache() { + PropertyDataFetcherHelper.clearReflectionCache(); + return this; + } + + /** + * This can be used to control whether PropertyDataFetcher will use {@link java.lang.reflect.Method#setAccessible(boolean)} to gain access to property + * values. By default, it PropertyDataFetcher WILL use setAccessible. + * + * @param flag whether to use setAccessible + * + * @return the previous value of the flag + */ + public boolean setUseSetAccessible(boolean flag) { + return PropertyDataFetcherHelper.setUseSetAccessible(flag); + } + + /** + * This can be used to control whether PropertyDataFetcher will cache negative lookups for a property for performance reasons. By default it PropertyDataFetcher WILL cache misses. + * + * @param flag whether to cache misses + * + * @return the previous value of the flag + */ + public boolean setUseNegativeCache(boolean flag) { + return PropertyDataFetcherHelper.setUseNegativeCache(flag); + } + } + + public static class GoodFaithIntrospectionConfig extends BaseConfig { + private GoodFaithIntrospectionConfig(GraphQLUnusualConfiguration configuration) { + super(configuration); + } + + /** + * @return true if good faith introspection is enabled + */ + public boolean isEnabledJvmWide() { + return GoodFaithIntrospection.isEnabledJvmWide(); + } + + /** + * This allows you to disable good faith introspection, which is on by default. + * + * @param enabled the desired state + * + * @return the previous state + */ + public GoodFaithIntrospectionConfig enabledJvmWide(boolean enabled) { + GoodFaithIntrospection.enabledJvmWide(enabled); + return this; + } + } + + /* + * =============================================== + * Per GraphqlContext code down here + * =============================================== + */ + + @SuppressWarnings("DataFlowIssue") + public static class GraphQLContextConfiguration { + // it will be one or the other types of GraphQLContext + private final GraphQLContext graphQLContext; + private final GraphQLContext.Builder graphQLContextBuilder; + + GraphQLContextConfiguration(GraphQLContext graphQLContext) { + this.graphQLContext = graphQLContext; + this.graphQLContextBuilder = null; + } + + GraphQLContextConfiguration(GraphQLContext.Builder graphQLContextBuilder) { + this.graphQLContextBuilder = graphQLContextBuilder; + this.graphQLContext = null; + } + + /** + * @return an element that allows you to control incremental support, that is @defer configuration + */ + public IncrementalSupportConfig incrementalSupport() { + return new IncrementalSupportConfig(this); + } + + /** + * @return an element that allows you to precisely control {@link org.dataloader.DataLoader} behavior + * in graphql-java. + */ + public DataloaderConfig dataloaderConfig() { + return new DataloaderConfig(this); + } + + private void put(String named, Object value) { + if (graphQLContext != null) { + graphQLContext.put(named, value); + } else { + assertNotNull(graphQLContextBuilder).put(named, value); + } + } + + private boolean getBoolean(String named) { + if (graphQLContext != null) { + return graphQLContext.getBoolean(named); + } else { + return assertNotNull(graphQLContextBuilder).getBoolean(named); + } + } + + private T get(String named) { + if (graphQLContext != null) { + return graphQLContext.get(named); + } else { + //noinspection unchecked + return (T) assertNotNull(graphQLContextBuilder).get(named); + } + } + } + + private static class BaseContextConfig { + protected final GraphQLContextConfiguration contextConfig; + + private BaseContextConfig(GraphQLContextConfiguration contextConfig) { + this.contextConfig = contextConfig; + } + + /** + * @return an element that allows you to chain multiple configuration elements + */ + public GraphQLContextConfiguration then() { + return contextConfig; + } + } + + public static class IncrementalSupportConfig extends BaseContextConfig { + private IncrementalSupportConfig(GraphQLContextConfiguration contextConfig) { + super(contextConfig); + } + + /** + * @return true if @defer and @stream behaviour is enabled for this execution. + */ + public boolean isIncrementalSupportEnabled() { + return contextConfig.getBoolean(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT); + } + + /** + * This controls whether @defer and @stream behaviour is enabled for this execution. + */ + @ExperimentalApi + public IncrementalSupportConfig enableIncrementalSupport(boolean enable) { + contextConfig.put(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT, enable); + return this; + } + } + + public static class DataloaderConfig extends BaseContextConfig { + private DataloaderConfig(GraphQLContextConfiguration contextConfig) { + super(contextConfig); + } + + /** + * @return true if @defer and @stream behaviour is enabled for this execution. + */ + public boolean isDataLoaderChainingEnabled() { + return contextConfig.getBoolean(ENABLE_DATA_LOADER_CHAINING); + } + + /** + * Enables the ability that chained DataLoaders are dispatched automatically. + */ + @ExperimentalApi + public DataloaderConfig enableDataLoaderChaining(boolean enable) { + contextConfig.put(ENABLE_DATA_LOADER_CHAINING, enable); + return this; + } + + /** + * @return the batch window duration size for delayed DataLoaders. + */ + public Duration delayedDataLoaderBatchWindowSize() { + Long d = contextConfig.get(DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS); + return d != null ? Duration.ofNanos(d) : null; + } + + /** + * Sets the batch window duration size for delayed DataLoaders. + * That is for DataLoaders, that are not batched as part of the normal per level + * dispatching, because they were created after the level was already dispatched. + */ + @ExperimentalApi + public DataloaderConfig delayedDataLoaderBatchWindowSize(Duration batchWindowSize) { + contextConfig.put(DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS, batchWindowSize.toNanos()); + return this; + } + + /** + * @return the instance of {@link DelayedDataLoaderDispatcherExecutorFactory} that is used to create the + * {@link java.util.concurrent.ScheduledExecutorService} for the delayed DataLoader dispatching. + */ + public DelayedDataLoaderDispatcherExecutorFactory delayedDataLoaderExecutorFactory() { + return contextConfig.get(DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY); + } + + /** + * Sets the instance of {@link DelayedDataLoaderDispatcherExecutorFactory} that is used to create the + * {@link java.util.concurrent.ScheduledExecutorService} for the delayed DataLoader dispatching. + */ + @ExperimentalApi + public DataloaderConfig delayedDataLoaderExecutorFactory(DelayedDataLoaderDispatcherExecutorFactory delayedDataLoaderDispatcherExecutorFactory) { + contextConfig.put(DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY, delayedDataLoaderDispatcherExecutorFactory); + return this; + } + } +} diff --git a/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatchingContextKeys.java b/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatchingContextKeys.java index 2aea2460e..e85322526 100644 --- a/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatchingContextKeys.java +++ b/src/main/java/graphql/execution/instrumentation/dataloader/DataLoaderDispatchingContextKeys.java @@ -1,8 +1,8 @@ package graphql.execution.instrumentation.dataloader; -import graphql.ExperimentalApi; import graphql.GraphQLContext; +import graphql.Internal; import org.jspecify.annotations.NullMarked; import java.time.Duration; @@ -10,7 +10,7 @@ /** * GraphQLContext keys related to DataLoader dispatching. */ -@ExperimentalApi +@Internal @NullMarked public final class DataLoaderDispatchingContextKeys { private DataLoaderDispatchingContextKeys() { diff --git a/src/main/java/graphql/parser/ParserOptions.java b/src/main/java/graphql/parser/ParserOptions.java index 0adfb73f6..2e0429442 100644 --- a/src/main/java/graphql/parser/ParserOptions.java +++ b/src/main/java/graphql/parser/ParserOptions.java @@ -175,7 +175,7 @@ public static ParserOptions getDefaultSdlParserOptions() { * * This static can be set to true to allow the behavior of version 16.x or before. * - * @param options - the new default JVM parser options for operation parsing + * @param options - the new default JVM parser options for SDL parsing * * @see graphql.language.IgnoredChar * @see graphql.language.SourceLocation diff --git a/src/test/groovy/graphql/config/GraphQLUnusualConfigurationTest.groovy b/src/test/groovy/graphql/config/GraphQLUnusualConfigurationTest.groovy new file mode 100644 index 000000000..765a608c0 --- /dev/null +++ b/src/test/groovy/graphql/config/GraphQLUnusualConfigurationTest.groovy @@ -0,0 +1,210 @@ +package graphql.config + +import graphql.ExecutionInput +import graphql.ExperimentalApi +import graphql.GraphQL +import graphql.GraphQLContext +import graphql.execution.instrumentation.dataloader.DataLoaderDispatchingContextKeys +import graphql.execution.instrumentation.dataloader.DelayedDataLoaderDispatcherExecutorFactory +import graphql.introspection.GoodFaithIntrospection +import graphql.parser.ParserOptions +import graphql.schema.PropertyDataFetcherHelper +import spock.lang.Specification + +import java.time.Duration + +import static graphql.parser.ParserOptions.newParserOptions + +class GraphQLUnusualConfigurationTest extends Specification { + + def startingParserOptions = ParserOptions.getDefaultParserOptions() + def startingState = GoodFaithIntrospection.isEnabledJvmWide() + + void cleanup() { + // JVM wide so other tests can be affected + ParserOptions.setDefaultParserOptions(startingParserOptions) + PropertyDataFetcherHelper.setUseNegativeCache(true) + GoodFaithIntrospection.enabledJvmWide(startingState) + } + + def "can set parser configurations"() { + when: + def parserOptions = newParserOptions().maxRuleDepth(99).build() + GraphQL.unusualConfiguration().parsing().setDefaultParserOptions(parserOptions) + def defaultParserOptions = GraphQL.unusualConfiguration().parsing().getDefaultParserOptions() + + then: + defaultParserOptions.getMaxRuleDepth() == 99 + } + + def "can set property data fetcher config"() { + when: + def prevValue = GraphQL.unusualConfiguration().propertyDataFetching().setUseNegativeCache(false) + then: + prevValue + + when: + prevValue = GraphQL.unusualConfiguration().propertyDataFetching().setUseNegativeCache(false) + then: + !prevValue + + when: + prevValue = GraphQL.unusualConfiguration().propertyDataFetching().setUseNegativeCache(true) + then: + !prevValue + } + + def "can set good faith settings"() { + when: + GraphQL.unusualConfiguration().goodFaithIntrospection().enabledJvmWide(false) + + then: + !GraphQL.unusualConfiguration().goodFaithIntrospection().isEnabledJvmWide() + + when: + GraphQL.unusualConfiguration().goodFaithIntrospection().enabledJvmWide(true) + + then: + GraphQL.unusualConfiguration().goodFaithIntrospection().isEnabledJvmWide() + + // showing chaining + when: + GraphQL.unusualConfiguration().goodFaithIntrospection() + .enabledJvmWide(true) + .then().goodFaithIntrospection() + .enabledJvmWide(false) + + then: + !GraphQL.unusualConfiguration().goodFaithIntrospection().isEnabledJvmWide() + } + + def "can set defer configuration on graphql context objects"() { + when: + def graphqlContextBuilder = GraphQLContext.newContext() + GraphQL.unusualConfiguration(graphqlContextBuilder).incrementalSupport().enableIncrementalSupport(true) + + then: + graphqlContextBuilder.build().get(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT) == true + GraphQL.unusualConfiguration(graphqlContextBuilder).incrementalSupport().isIncrementalSupportEnabled() + + when: + graphqlContextBuilder = GraphQLContext.newContext() + GraphQL.unusualConfiguration(graphqlContextBuilder).incrementalSupport().enableIncrementalSupport(false) + + then: + graphqlContextBuilder.build().get(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT) == false + !GraphQL.unusualConfiguration(graphqlContextBuilder).incrementalSupport().isIncrementalSupportEnabled() + + when: + def graphqlContext = GraphQLContext.newContext().build() + GraphQL.unusualConfiguration(graphqlContext).incrementalSupport().enableIncrementalSupport(true) + + then: + graphqlContext.get(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT) == true + GraphQL.unusualConfiguration(graphqlContext).incrementalSupport().isIncrementalSupportEnabled() + + when: + graphqlContext = GraphQLContext.newContext().build() + GraphQL.unusualConfiguration(graphqlContext).incrementalSupport().enableIncrementalSupport(false) + + then: + graphqlContext.get(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT) == false + !GraphQL.unusualConfiguration(graphqlContext).incrementalSupport().isIncrementalSupportEnabled() + + when: + graphqlContext = GraphQLContext.newContext().build() + // just to show we we can navigate the DSL + GraphQL.unusualConfiguration(graphqlContext) + .incrementalSupport() + .enableIncrementalSupport(false) + .enableIncrementalSupport(true) + .then().incrementalSupport() + .enableIncrementalSupport(false) + + then: + graphqlContext.get(ExperimentalApi.ENABLE_INCREMENTAL_SUPPORT) == false + !GraphQL.unusualConfiguration(graphqlContext).incrementalSupport().isIncrementalSupportEnabled() + } + + def "can set data loader chaining config for enablement"() { + when: + def graphqlContextBuilder = GraphQLContext.newContext() + GraphQL.unusualConfiguration(graphqlContextBuilder).dataloaderConfig().enableDataLoaderChaining(true) + + then: + graphqlContextBuilder.build().get(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING) == true + GraphQL.unusualConfiguration(graphqlContextBuilder).dataloaderConfig().isDataLoaderChainingEnabled() + + + when: + def graphqlContext = GraphQLContext.newContext().build() + GraphQL.unusualConfiguration(graphqlContext).dataloaderConfig().enableDataLoaderChaining(true) + + then: + graphqlContext.get(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING) == true + GraphQL.unusualConfiguration(graphqlContext).dataloaderConfig().isDataLoaderChainingEnabled() + } + + def "can set data loader chaining config for extra config"() { + when: + def graphqlContext = GraphQLContext.newContext().build() + GraphQL.unusualConfiguration(graphqlContext).dataloaderConfig().delayedDataLoaderBatchWindowSize(Duration.ofMillis(10)) + + then: + graphqlContext.get(DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS) == Duration.ofMillis(10).toNanos() + GraphQL.unusualConfiguration(graphqlContext).dataloaderConfig().delayedDataLoaderBatchWindowSize() == Duration.ofMillis(10) + + when: + DelayedDataLoaderDispatcherExecutorFactory factory = {} + graphqlContext = GraphQLContext.newContext().build() + GraphQL.unusualConfiguration(graphqlContext).dataloaderConfig().delayedDataLoaderExecutorFactory(factory) + + then: + graphqlContext.get(DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY) == factory + GraphQL.unusualConfiguration(graphqlContext).dataloaderConfig().delayedDataLoaderExecutorFactory() == factory + + when: + graphqlContext = GraphQLContext.newContext().build() + // just to show we we can navigate the DSL + GraphQL.unusualConfiguration(graphqlContext) + .incrementalSupport() + .enableIncrementalSupport(false) + .enableIncrementalSupport(true) + .then() + .dataloaderConfig() + .enableDataLoaderChaining(true) + .then() + .dataloaderConfig() + .delayedDataLoaderBatchWindowSize(Duration.ofMillis(10)) + .delayedDataLoaderExecutorFactory(factory) + + then: + graphqlContext.get(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING) == true + graphqlContext.get(DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_BATCH_WINDOW_SIZE_NANO_SECONDS) == Duration.ofMillis(10).toNanos() + graphqlContext.get(DataLoaderDispatchingContextKeys.DELAYED_DATA_LOADER_DISPATCHING_EXECUTOR_FACTORY) == factory + } + + def "we can access via the ExecutionInput"() { + when: + def eiBuilder = ExecutionInput.newExecutionInput("query q {f}") + + GraphQL.unusualConfiguration(eiBuilder) + .dataloaderConfig() + .enableDataLoaderChaining(true) + + def ei = eiBuilder.build() + + then: + ei.getGraphQLContext().get(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING) == true + + when: + ei = ExecutionInput.newExecutionInput("query q {f}").build() + + GraphQL.unusualConfiguration(ei) + .dataloaderConfig() + .enableDataLoaderChaining(true) + + then: + ei.getGraphQLContext().get(DataLoaderDispatchingContextKeys.ENABLE_DATA_LOADER_CHAINING) == true + } +}