From c7e78d684072bfcd32c289ebddefba8989e9e7cb Mon Sep 17 00:00:00 2001 From: Jessie Jacobs Date: Thu, 14 Nov 2024 15:08:08 -0800 Subject: [PATCH 1/3] Copy test to project with new spring-graphql starter --- .../GraphQLContextContributorTest.java | 72 +++++++++++++++++++ .../example/datafetcher/TestAppTestSlice.java | 2 + 2 files changed, 74 insertions(+) create mode 100644 graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java diff --git a/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java b/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java new file mode 100644 index 000000000..18370f762 --- /dev/null +++ b/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java @@ -0,0 +1,72 @@ +/* + * Copyright 2024 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.netflix.graphql.dgs.example.datafetcher;import com.netflix.graphql.dgs.DgsQueryExecutor; +import com.netflix.graphql.dgs.autoconfig.DgsAutoConfiguration; +import com.netflix.graphql.dgs.autoconfig.DgsExtendedScalarsAutoConfiguration; +import com.netflix.graphql.dgs.example.context.MyContextBuilder; +import com.netflix.graphql.dgs.example.datafetcher.HelloDataFetcher; +import com.netflix.graphql.dgs.example.shared.context.ExampleGraphQLContextContributor; +import com.netflix.graphql.dgs.example.shared.dataLoader.ExampleLoaderWithContext; +import com.netflix.graphql.dgs.example.shared.dataLoader.ExampleLoaderWithGraphQLContext; +import com.netflix.graphql.dgs.example.shared.datafetcher.RequestHeadersDataFetcher; +import com.netflix.graphql.dgs.example.shared.instrumentation.ExampleInstrumentationDependingOnContextContributor; +import com.netflix.graphql.dgs.example.shared.datafetcher.MovieDataFetcher; +import com.netflix.graphql.dgs.pagination.DgsPaginationAutoConfiguration; +import com.netflix.graphql.dgs.test.EnableDgsTest; +import graphql.scalars.ExtendedScalars; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.web.context.request.ServletWebRequest; + +import static com.netflix.graphql.dgs.example.shared.context.ExampleGraphQLContextContributor.CONTEXT_CONTRIBUTOR_HEADER_NAME; +import static com.netflix.graphql.dgs.example.shared.context.ExampleGraphQLContextContributor.CONTEXT_CONTRIBUTOR_HEADER_VALUE; +import static org.assertj.core.api.Assertions.assertThat; + +@EnableDgsTest +@TestAppTestSlice +@SpringBootTest(classes = {SpringGraphQLDataFetchers.class, com.netflix.graphql.dgs.example.datafetcher.HelloDataFetcher.class, WithHeader.class, WithCookie.class, MovieDataFetcher.class, RequestHeadersDataFetcher.class}) +public class GraphQLContextContributorTest { + + @Autowired + DgsQueryExecutor queryExecutor; + + @Test + void moviesExtensionShouldHaveContributedEnabledExtension() { + final MockHttpServletRequest mockServletRequest = new MockHttpServletRequest(); + mockServletRequest.addHeader(CONTEXT_CONTRIBUTOR_HEADER_NAME, CONTEXT_CONTRIBUTOR_HEADER_VALUE); + ServletWebRequest servletWebRequest = new ServletWebRequest(mockServletRequest); + String contributorEnabled = queryExecutor.executeAndExtractJsonPath("{ movies { director } }", "extensions.contributorEnabled", servletWebRequest); + assertThat(contributorEnabled).isEqualTo("true"); + } + + @Test + void withDataloaderContext() { + String message = queryExecutor.executeAndExtractJsonPath("{withDataLoaderContext}", "data.withDataLoaderContext"); + assertThat(message).isEqualTo("Custom state! A"); + } + + @Test + void withDataloaderGraphQLContext() { + final MockHttpServletRequest mockServletRequest = new MockHttpServletRequest(); + mockServletRequest.addHeader(CONTEXT_CONTRIBUTOR_HEADER_NAME, CONTEXT_CONTRIBUTOR_HEADER_VALUE); + ServletWebRequest servletWebRequest = new ServletWebRequest(mockServletRequest); + String contributorEnabled = queryExecutor.executeAndExtractJsonPath("{ withDataLoaderGraphQLContext }", "data.withDataLoaderGraphQLContext", servletWebRequest); + assertThat(contributorEnabled).isEqualTo("true"); + } +} \ No newline at end of file diff --git a/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/TestAppTestSlice.java b/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/TestAppTestSlice.java index cfc99f7eb..6b92d3004 100644 --- a/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/TestAppTestSlice.java +++ b/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/TestAppTestSlice.java @@ -20,6 +20,7 @@ import com.netflix.graphql.dgs.example.context.MyContextBuilder; import com.netflix.graphql.dgs.example.shared.context.ExampleGraphQLContextContributor; import com.netflix.graphql.dgs.example.shared.dataLoader.ExampleLoaderWithContext; +import com.netflix.graphql.dgs.example.shared.dataLoader.ExampleLoaderWithGraphQLContext; import com.netflix.graphql.dgs.example.shared.dataLoader.MessageDataLoader; import com.netflix.graphql.dgs.example.shared.instrumentation.ExampleInstrumentationDependingOnContextContributor; import com.netflix.graphql.dgs.pagination.DgsPaginationAutoConfiguration; @@ -36,6 +37,7 @@ @Import({MessageDataLoader.class, UploadScalar.class, ExampleLoaderWithContext.class, + ExampleLoaderWithGraphQLContext.class, ExampleGraphQLContextContributor.class, ExampleInstrumentationDependingOnContextContributor.class, MyContextBuilder.class From a201aaf2f769fea887c76040873818807437c2cc Mon Sep 17 00:00:00 2001 From: "$(git --no-pager log --format=format:'%an' -n 1)" Date: Wed, 20 Nov 2024 11:35:34 -0800 Subject: [PATCH 2/3] Call context contributors in the webmvcgraphql interceptor --- .../DgsSpringGraphQLAutoConfiguration.kt | 3 +++ .../webmvc/DgsWebMvcGraphQLInterceptor.kt | 21 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt index 7fa5d99c3..b1f1e6ca5 100644 --- a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt +++ b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt @@ -120,6 +120,7 @@ open class DgsSpringGraphQLAutoConfiguration { ): GraphQlSourceBuilderCustomizer = GraphQlSourceBuilderCustomizer { builder -> builder.configureGraphQl { graphQlBuilder -> + if (preparsedDocumentProvider.isPresent) { graphQlBuilder .preparsedDocumentProvider(preparsedDocumentProvider.get()) @@ -176,11 +177,13 @@ open class DgsSpringGraphQLAutoConfiguration { open fun dgsGraphQlInterceptor( dgsDataLoaderProvider: DgsDataLoaderProvider, dgsDefaultContextBuilder: DefaultDgsGraphQLContextBuilder, + graphQLContextContributors: List, ): DgsWebMvcGraphQLInterceptor = DgsWebMvcGraphQLInterceptor( dgsDataLoaderProvider, dgsDefaultContextBuilder, dgsSpringGraphQLConfigurationProperties, + graphQLContextContributors, ) } diff --git a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt index 174038801..f85e5029e 100644 --- a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt +++ b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt @@ -16,6 +16,8 @@ package com.netflix.graphql.dgs.springgraphql.webmvc +import com.netflix.graphql.dgs.context.DgsContext +import com.netflix.graphql.dgs.context.GraphQLContextContributor import com.netflix.graphql.dgs.internal.DefaultDgsGraphQLContextBuilder import com.netflix.graphql.dgs.internal.DgsDataLoaderProvider import com.netflix.graphql.dgs.internal.DgsWebMvcRequestData @@ -35,6 +37,7 @@ class DgsWebMvcGraphQLInterceptor( private val dgsDataLoaderProvider: DgsDataLoaderProvider, private val dgsContextBuilder: DefaultDgsGraphQLContextBuilder, private val dgsSpringConfigurationProperties: DgsSpringGraphQLConfigurationProperties, + private val graphQLContextContributors: List, ) : WebGraphQlInterceptor { override fun intercept( request: WebGraphQlRequest, @@ -55,8 +58,18 @@ class DgsWebMvcGraphQLInterceptor( } else { dgsContextBuilder.build(DgsWebMvcRequestData(request.extensions, request.headers)) } - val graphQLContextFuture = CompletableFuture() - val dataLoaderRegistry = dgsDataLoaderProvider.buildRegistryWithContextSupplier { graphQLContextFuture.get() } + val dataLoaderRegistry = dgsDataLoaderProvider.buildRegistryWithContextSupplier { + val graphQLContext = request.toExecutionInput().graphQLContext + if (graphQLContextContributors.isNotEmpty()) { + val extensions = request.extensions + val requestData = dgsContext.requestData + val builderForContributors = GraphQLContext.newContext() + graphQLContextContributors.forEach { it.contribute(builderForContributors, extensions, requestData) } + graphQLContext.putAll(builderForContributors) + } + + graphQLContext + } request.configureExecutionInput { _, builder -> builder @@ -65,7 +78,9 @@ class DgsWebMvcGraphQLInterceptor( .dataLoaderRegistry(dataLoaderRegistry) .build() } - graphQLContextFuture.complete(request.toExecutionInput().graphQLContext) + + + return if (dgsSpringConfigurationProperties.webmvc.asyncdispatch.enabled) { chain.next(request).doFinally { From 7ddf06b2de43d3606727c32ef91ecbd82eda9724 Mon Sep 17 00:00:00 2001 From: Jessie Jacobs Date: Wed, 20 Nov 2024 13:50:50 -0800 Subject: [PATCH 3/3] Call context contributors in the SpringGraphQLDgsQueryExecutor for tests --- .../GraphQLContextContributorTest.java | 14 ++-------- .../SpringGraphQLDgsQueryExecutor.kt | 18 +++++++++--- .../DgsSpringGraphQLAutoConfiguration.kt | 3 +- .../webmvc/DgsWebMvcGraphQLInterceptor.kt | 28 ++++++++----------- 4 files changed, 31 insertions(+), 32 deletions(-) diff --git a/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java b/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java index 18370f762..b0768210e 100644 --- a/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java +++ b/graphql-dgs-spring-graphql-example-java/src/test/java/com/netflix/graphql/dgs/example/datafetcher/GraphQLContextContributorTest.java @@ -14,20 +14,12 @@ * limitations under the License. */ -package com.netflix.graphql.dgs.example.datafetcher;import com.netflix.graphql.dgs.DgsQueryExecutor; -import com.netflix.graphql.dgs.autoconfig.DgsAutoConfiguration; -import com.netflix.graphql.dgs.autoconfig.DgsExtendedScalarsAutoConfiguration; -import com.netflix.graphql.dgs.example.context.MyContextBuilder; -import com.netflix.graphql.dgs.example.datafetcher.HelloDataFetcher; -import com.netflix.graphql.dgs.example.shared.context.ExampleGraphQLContextContributor; -import com.netflix.graphql.dgs.example.shared.dataLoader.ExampleLoaderWithContext; -import com.netflix.graphql.dgs.example.shared.dataLoader.ExampleLoaderWithGraphQLContext; +package com.netflix.graphql.dgs.example.datafetcher; + +import com.netflix.graphql.dgs.DgsQueryExecutor; import com.netflix.graphql.dgs.example.shared.datafetcher.RequestHeadersDataFetcher; -import com.netflix.graphql.dgs.example.shared.instrumentation.ExampleInstrumentationDependingOnContextContributor; import com.netflix.graphql.dgs.example.shared.datafetcher.MovieDataFetcher; -import com.netflix.graphql.dgs.pagination.DgsPaginationAutoConfiguration; import com.netflix.graphql.dgs.test.EnableDgsTest; -import graphql.scalars.ExtendedScalars; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; diff --git a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/SpringGraphQLDgsQueryExecutor.kt b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/SpringGraphQLDgsQueryExecutor.kt index 127df774b..9f4050baf 100644 --- a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/SpringGraphQLDgsQueryExecutor.kt +++ b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/SpringGraphQLDgsQueryExecutor.kt @@ -21,6 +21,7 @@ import com.jayway.jsonpath.JsonPath import com.jayway.jsonpath.TypeRef import com.jayway.jsonpath.spi.mapper.MappingException import com.netflix.graphql.dgs.DgsQueryExecutor +import com.netflix.graphql.dgs.context.GraphQLContextContributor import com.netflix.graphql.dgs.exceptions.DgsQueryExecutionDataExtractionException import com.netflix.graphql.dgs.exceptions.QueryException import com.netflix.graphql.dgs.internal.BaseDgsQueryExecutor @@ -42,6 +43,7 @@ class SpringGraphQLDgsQueryExecutor( private val dgsContextBuilder: DefaultDgsGraphQLContextBuilder, private val dgsDataLoaderProvider: DgsDataLoaderProvider, private val requestCustomizer: DgsQueryExecutorRequestCustomizer = DgsQueryExecutorRequestCustomizer.DEFAULT_REQUEST_CUSTOMIZER, + private val graphQLContextContributors: List, ) : DgsQueryExecutor { override fun execute( query: String, @@ -63,8 +65,18 @@ class SpringGraphQLDgsQueryExecutor( val httpRequest = requestCustomizer.apply(webRequest ?: RequestContextHolder.getRequestAttributes() as? WebRequest, headers) val dgsContext = dgsContextBuilder.build(DgsWebMvcRequestData(request.extensions, headers, httpRequest)) - lateinit var graphQLContext: GraphQLContext - val dataLoaderRegistry = dgsDataLoaderProvider.buildRegistryWithContextSupplier { graphQLContext } + val dataLoaderRegistry = + dgsDataLoaderProvider.buildRegistryWithContextSupplier { + val graphQLContext = request.toExecutionInput().graphQLContext + if (graphQLContextContributors.isNotEmpty()) { + val requestData = dgsContext.requestData + val builderForContributors = GraphQLContext.newContext() + graphQLContextContributors.forEach { it.contribute(builderForContributors, extensions, requestData) } + graphQLContext.putAll(builderForContributors) + } + + graphQLContext + } request.configureExecutionInput { _, builder -> builder @@ -74,8 +86,6 @@ class SpringGraphQLDgsQueryExecutor( .build() } - graphQLContext = request.toExecutionInput().graphQLContext - val response = executionService.execute(request).block() ?: throw IllegalStateException("Unexpected null response from Spring GraphQL client") diff --git a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt index b1f1e6ca5..b7d97181b 100644 --- a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt +++ b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/autoconfig/DgsSpringGraphQLAutoConfiguration.kt @@ -120,7 +120,6 @@ open class DgsSpringGraphQLAutoConfiguration { ): GraphQlSourceBuilderCustomizer = GraphQlSourceBuilderCustomizer { builder -> builder.configureGraphQl { graphQlBuilder -> - if (preparsedDocumentProvider.isPresent) { graphQlBuilder .preparsedDocumentProvider(preparsedDocumentProvider.get()) @@ -160,12 +159,14 @@ open class DgsSpringGraphQLAutoConfiguration { dgsContextBuilder: DefaultDgsGraphQLContextBuilder, dgsDataLoaderProvider: DgsDataLoaderProvider, requestCustomizer: ObjectProvider, + graphQLContextContributors: List, ): DgsQueryExecutor = SpringGraphQLDgsQueryExecutor( executionService, dgsContextBuilder, dgsDataLoaderProvider, requestCustomizer = requestCustomizer.getIfAvailable(DgsQueryExecutorRequestCustomizer::DEFAULT_REQUEST_CUSTOMIZER), + graphQLContextContributors, ) @Configuration(proxyBeanMethods = false) diff --git a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt index f85e5029e..ca0092b9a 100644 --- a/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt +++ b/graphql-dgs-spring-graphql/src/main/kotlin/com/netflix/graphql/dgs/springgraphql/webmvc/DgsWebMvcGraphQLInterceptor.kt @@ -16,7 +16,6 @@ package com.netflix.graphql.dgs.springgraphql.webmvc -import com.netflix.graphql.dgs.context.DgsContext import com.netflix.graphql.dgs.context.GraphQLContextContributor import com.netflix.graphql.dgs.internal.DefaultDgsGraphQLContextBuilder import com.netflix.graphql.dgs.internal.DgsDataLoaderProvider @@ -31,7 +30,6 @@ import org.springframework.web.context.request.ServletRequestAttributes import org.springframework.web.context.request.ServletWebRequest import org.springframework.web.context.request.WebRequest import reactor.core.publisher.Mono -import java.util.concurrent.CompletableFuture class DgsWebMvcGraphQLInterceptor( private val dgsDataLoaderProvider: DgsDataLoaderProvider, @@ -58,18 +56,19 @@ class DgsWebMvcGraphQLInterceptor( } else { dgsContextBuilder.build(DgsWebMvcRequestData(request.extensions, request.headers)) } - val dataLoaderRegistry = dgsDataLoaderProvider.buildRegistryWithContextSupplier { - val graphQLContext = request.toExecutionInput().graphQLContext - if (graphQLContextContributors.isNotEmpty()) { - val extensions = request.extensions - val requestData = dgsContext.requestData - val builderForContributors = GraphQLContext.newContext() - graphQLContextContributors.forEach { it.contribute(builderForContributors, extensions, requestData) } - graphQLContext.putAll(builderForContributors) - } + val dataLoaderRegistry = + dgsDataLoaderProvider.buildRegistryWithContextSupplier { + val graphQLContext = request.toExecutionInput().graphQLContext + if (graphQLContextContributors.isNotEmpty()) { + val extensions = request.extensions + val requestData = dgsContext.requestData + val builderForContributors = GraphQLContext.newContext() + graphQLContextContributors.forEach { it.contribute(builderForContributors, extensions, requestData) } + graphQLContext.putAll(builderForContributors) + } - graphQLContext - } + graphQLContext + } request.configureExecutionInput { _, builder -> builder @@ -79,9 +78,6 @@ class DgsWebMvcGraphQLInterceptor( .build() } - - - return if (dgsSpringConfigurationProperties.webmvc.asyncdispatch.enabled) { chain.next(request).doFinally { if (dataLoaderRegistry is AutoCloseable) {