From c5d4a6180c8f7367df0c9c1ea8f3ae8ed0d032da Mon Sep 17 00:00:00 2001 From: ivanasen Date: Tue, 9 Jan 2024 00:11:17 +0200 Subject: [PATCH 1/8] Provide consistent support for dynamically registered DataFetchers Implemented a new way of using `@DgsCodeRegistry` in which a `DgsCodeRegistryBuilder` is injected instead. This way, the registered DataFetchers are processed by DataFetcherResultProcessors and the behaviour is more consistent with the annotation-based approach. [Link to discussion](https://github.com/Netflix/dgs-framework/discussions/1768) --- .../graphql/dgs/DgsCodeRegistryBuilder.java | 75 +++++++++++++++++++ .../graphql/dgs/internal/DgsSchemaProvider.kt | 17 +++-- .../graphql/dgs/DgsSchemaProviderTest.kt | 60 ++++++++++++--- 3 files changed, 137 insertions(+), 15 deletions(-) create mode 100644 graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java diff --git a/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java b/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java new file mode 100644 index 000000000..ade0fcdd7 --- /dev/null +++ b/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java @@ -0,0 +1,75 @@ +/* + * 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; + +import com.netflix.graphql.dgs.internal.DataFetcherResultProcessor; +import graphql.schema.DataFetcher; +import graphql.schema.DataFetcherFactories; +import graphql.schema.FieldCoordinates; +import graphql.schema.GraphQLCodeRegistry; +import graphql.schema.GraphQLFieldDefinition; + +import java.util.List; + +/** + * Utility wrapper for {@link GraphQLCodeRegistry.Builder} which provides + * a consistent registration mechanism of DataFetchers similar to the annotation-based approach. + * Can be used as a first parameter of a {@link DgsCodeRegistry} annotated method. + */ +public class DgsCodeRegistryBuilder { + + private final List dataFetcherResultProcessors; + + private final GraphQLCodeRegistry.Builder graphQLCodeRegistry; + + public DgsCodeRegistryBuilder( + List dataFetcherResultProcessors, + GraphQLCodeRegistry.Builder codeRegistry) { + this.dataFetcherResultProcessors = dataFetcherResultProcessors; + this.graphQLCodeRegistry = codeRegistry; + } + + public DgsCodeRegistryBuilder dataFetcher(FieldCoordinates coordinates, DataFetcher dataFetcher) { + var wrapped = DataFetcherFactories.wrapDataFetcher(dataFetcher, (dfe, result) -> { + if (result == null || coordinates.getTypeName().equals("Subscription")) { + return result; + } + + var dgsDfe = new DgsDataFetchingEnvironment(dfe); + return dataFetcherResultProcessors.stream() + .filter(p -> p.supportsType(result)) + .findFirst() + .map(p -> p.process(result, dgsDfe)) + .orElse(result); + }); + + graphQLCodeRegistry.dataFetcher(coordinates, wrapped); + return this; + } + + public boolean hasDataFetcher(FieldCoordinates coordinates) { + return graphQLCodeRegistry.hasDataFetcher(coordinates); + } + + public DataFetcher getDataFetcher(FieldCoordinates coordinates, GraphQLFieldDefinition fieldDefinition) { + return graphQLCodeRegistry.getDataFetcher(coordinates, fieldDefinition); + } + + public GraphQLCodeRegistry.Builder getGraphQLCodeRegistry() { + return graphQLCodeRegistry; + } +} diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt index 7aa0bd793..02225c9e0 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt @@ -261,18 +261,25 @@ class DgsSchemaProvider( codeRegistryBuilder: GraphQLCodeRegistry.Builder, registry: TypeDefinitionRegistry ) { + val dgsCodeRegistryBuilder = DgsCodeRegistryBuilder(dataFetcherResultProcessors, codeRegistryBuilder); + dgsComponent.javaClass.methods.asSequence() .filter { it.isAnnotationPresent(DgsCodeRegistry::class.java) } .forEach { method -> - if (method.returnType != GraphQLCodeRegistry.Builder::class.java) { - throw InvalidDgsConfigurationException("Method annotated with @DgsCodeRegistry must have return type GraphQLCodeRegistry.Builder") + if (method.returnType != GraphQLCodeRegistry.Builder::class.java + && method.returnType != DgsCodeRegistryBuilder::class.java) { + throw InvalidDgsConfigurationException("Method annotated with @DgsCodeRegistry must have return type GraphQLCodeRegistry.Builder or DgsCodeRegistryBuilder") } - if (method.parameterCount != 2 || method.parameterTypes[0] != GraphQLCodeRegistry.Builder::class.java || method.parameterTypes[1] != TypeDefinitionRegistry::class.java) { - throw InvalidDgsConfigurationException("Method annotated with @DgsCodeRegistry must accept the following arguments: GraphQLCodeRegistry.Builder, TypeDefinitionRegistry. ${dgsComponent.javaClass.name}.${method.name} has the following arguments: ${method.parameterTypes.joinToString()}") + if (method.parameterCount != 2 || method.parameterTypes[0] != method.returnType || method.parameterTypes[1] != TypeDefinitionRegistry::class.java) { + throw InvalidDgsConfigurationException("Method annotated with @DgsCodeRegistry must accept the following arguments: GraphQLCodeRegistry.Builder or DgsCodeRegistryBuilder, and TypeDefinitionRegistry. ${dgsComponent.javaClass.name}.${method.name} has the following arguments: ${method.parameterTypes.joinToString()}") } - ReflectionUtils.invokeMethod(method, dgsComponent, codeRegistryBuilder, registry) + if (method.returnType == DgsCodeRegistryBuilder::class.java) { + ReflectionUtils.invokeMethod(method, dgsComponent, dgsCodeRegistryBuilder, registry) + } else if (method.returnType == GraphQLCodeRegistry.Builder::class.java) { + ReflectionUtils.invokeMethod(method, dgsComponent, codeRegistryBuilder, registry) + } } } diff --git a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt index b39403513..01585c100 100644 --- a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt +++ b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt @@ -20,6 +20,7 @@ import com.netflix.graphql.dgs.exceptions.DataFetcherSchemaMismatchException import com.netflix.graphql.dgs.exceptions.InvalidDgsConfigurationException import com.netflix.graphql.dgs.exceptions.InvalidTypeResolverException import com.netflix.graphql.dgs.exceptions.NoSchemaFoundException +import com.netflix.graphql.dgs.internal.DataFetcherResultProcessor import com.netflix.graphql.dgs.internal.DefaultInputObjectMapper import com.netflix.graphql.dgs.internal.DgsSchemaProvider import com.netflix.graphql.dgs.internal.kotlin.test.Show @@ -73,7 +74,8 @@ internal class DgsSchemaProviderTest { typeDefinitionRegistry: TypeDefinitionRegistry? = null, schemaLocations: List = listOf(DgsSchemaProvider.DEFAULT_SCHEMA_LOCATION), componentFilter: ((Any) -> Boolean)? = null, - schemaWiringValidationEnabled: Boolean = true + schemaWiringValidationEnabled: Boolean = true, + dataFetcherResultProcessors: List = emptyList() ): DgsSchemaProvider { return DgsSchemaProvider( applicationContext = applicationContext, @@ -87,7 +89,8 @@ internal class DgsSchemaProviderTest { ) ), componentFilter = componentFilter, - schemaWiringValidationEnabled = schemaWiringValidationEnabled + schemaWiringValidationEnabled = schemaWiringValidationEnabled, + dataFetcherResultProcessors = dataFetcherResultProcessors ) } @@ -468,6 +471,7 @@ internal class DgsSchemaProviderTest { fun allowMergingStaticAndDynamicSchema() { @DgsComponent class CodeRegistryComponent { + // Result should not be processed by DataFetcherResultProcessors @DgsCodeRegistry fun registry( codeRegistryBuilder: GraphQLCodeRegistry.Builder, @@ -477,29 +481,65 @@ internal class DgsSchemaProviderTest { val coordinates = FieldCoordinates.coordinates("Query", "myField") return codeRegistryBuilder.dataFetcher(coordinates, df) } + + // Result should be processed by DataFetcherResultProcessors + @DgsCodeRegistry + fun dgsProcessedRegistry( + codeRegistryBuilder: DgsCodeRegistryBuilder, + @Suppress("unused_parameter") registry: TypeDefinitionRegistry? + ): DgsCodeRegistryBuilder { + val df = DataFetcher { "Runtime added field" } + val coordinates = FieldCoordinates.coordinates("Query", "myProcessedField") + return codeRegistryBuilder.dataFetcher(coordinates, df) + } } contextRunner.withBeans(HelloFetcher::class, CodeRegistryComponent::class).run { context -> val typeDefinitionRegistry = TypeDefinitionRegistry() val objectTypeExtensionDefinition = ObjectTypeExtensionDefinition.newObjectTypeExtensionDefinition() .name("Query") - .fieldDefinition( + .fieldDefinitions(listOf( FieldDefinition.newFieldDefinition() .name("myField") - .type(TypeName("String")).build() - ) + .type(TypeName("String")).build(), + FieldDefinition.newFieldDefinition() + .name("myProcessedField") + .type(TypeName("String")).build(), + )) .build() + val processor = object : DataFetcherResultProcessor { + override fun supportsType(originalResult: Any): Boolean { + return true + } + + override fun process(originalResult: Any, dfe: DgsDataFetchingEnvironment): Any { + // Avoid processing other results apart from the one for this test + if (originalResult != "Runtime added field") { + return originalResult + } + return originalResult as String + " [suffixFromProcessor]" + } + } + typeDefinitionRegistry.add(objectTypeExtensionDefinition) - val schema = schemaProvider(applicationContext = context, typeDefinitionRegistry = typeDefinitionRegistry).schema() + val schema = schemaProvider( + applicationContext = context, + typeDefinitionRegistry = typeDefinitionRegistry, + dataFetcherResultProcessors = listOf(processor) + ).schema() val build = GraphQL.newGraphQL(schema).build() assertHello(build) - val executionResult2 = build.execute("{myField}") - assertTrue(executionResult2.isDataPresent) - - val data = executionResult2.getData>() + val executionResult = build.execute("{myField}") + assertTrue(executionResult.isDataPresent) + val data = executionResult.getData>() assertEquals("Runtime added field", data["myField"]) + + val processedExecutionResult = build.execute("{myProcessedField}") + assertTrue(processedExecutionResult.isDataPresent) + val processedData = processedExecutionResult.getData>() + assertEquals("Runtime added field [suffixFromProcessor]", processedData["myProcessedField"]) } contextRunner.withBeans(HelloFetcher::class, CodeRegistryComponent::class).run { context -> From e5b200d714e7dbd8d9b90671ee953e67a4e059c7 Mon Sep 17 00:00:00 2001 From: ivanasen Date: Tue, 9 Jan 2024 00:44:44 +0200 Subject: [PATCH 2/8] Fix lint errors --- .../com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt index 02225c9e0..42a82a5de 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt @@ -261,13 +261,12 @@ class DgsSchemaProvider( codeRegistryBuilder: GraphQLCodeRegistry.Builder, registry: TypeDefinitionRegistry ) { - val dgsCodeRegistryBuilder = DgsCodeRegistryBuilder(dataFetcherResultProcessors, codeRegistryBuilder); + val dgsCodeRegistryBuilder = DgsCodeRegistryBuilder(dataFetcherResultProcessors, codeRegistryBuilder) dgsComponent.javaClass.methods.asSequence() .filter { it.isAnnotationPresent(DgsCodeRegistry::class.java) } .forEach { method -> - if (method.returnType != GraphQLCodeRegistry.Builder::class.java - && method.returnType != DgsCodeRegistryBuilder::class.java) { + if (method.returnType != GraphQLCodeRegistry.Builder::class.java && method.returnType != DgsCodeRegistryBuilder::class.java) { throw InvalidDgsConfigurationException("Method annotated with @DgsCodeRegistry must have return type GraphQLCodeRegistry.Builder or DgsCodeRegistryBuilder") } From abfdbcbf210e71d40795410ca29cb75da378a27e Mon Sep 17 00:00:00 2001 From: ivanasen Date: Tue, 9 Jan 2024 01:07:53 +0200 Subject: [PATCH 3/8] Fix lint errors --- .../graphql/dgs/DgsSchemaProviderTest.kt | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt index 01585c100..7bb24e5bd 100644 --- a/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt +++ b/graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/DgsSchemaProviderTest.kt @@ -498,15 +498,16 @@ internal class DgsSchemaProviderTest { val typeDefinitionRegistry = TypeDefinitionRegistry() val objectTypeExtensionDefinition = ObjectTypeExtensionDefinition.newObjectTypeExtensionDefinition() .name("Query") - .fieldDefinitions(listOf( - FieldDefinition.newFieldDefinition() - .name("myField") - .type(TypeName("String")).build(), - FieldDefinition.newFieldDefinition() - .name("myProcessedField") - .type(TypeName("String")).build(), - )) - .build() + .fieldDefinitions( + listOf( + FieldDefinition.newFieldDefinition() + .name("myField") + .type(TypeName("String")).build(), + FieldDefinition.newFieldDefinition() + .name("myProcessedField") + .type(TypeName("String")).build() + ) + ).build() val processor = object : DataFetcherResultProcessor { override fun supportsType(originalResult: Any): Boolean { From 6d82c15d5bfad864d96a031d3fa0b67e13dbe46e Mon Sep 17 00:00:00 2001 From: ivanasen Date: Mon, 15 Jan 2024 22:59:32 +0200 Subject: [PATCH 4/8] Remove code duplications in DgsCodeRegistryBuilder --- .../graphql/dgs/DgsCodeRegistryBuilder.java | 75 ------------------- .../graphql/dgs/DgsCodeRegistryBuilder.kt | 54 +++++++++++++ .../graphql/dgs/internal/DgsSchemaProvider.kt | 56 +++++--------- 3 files changed, 72 insertions(+), 113 deletions(-) delete mode 100644 graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java create mode 100644 graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt diff --git a/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java b/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java deleted file mode 100644 index ade0fcdd7..000000000 --- a/graphql-dgs/src/main/java/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.java +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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; - -import com.netflix.graphql.dgs.internal.DataFetcherResultProcessor; -import graphql.schema.DataFetcher; -import graphql.schema.DataFetcherFactories; -import graphql.schema.FieldCoordinates; -import graphql.schema.GraphQLCodeRegistry; -import graphql.schema.GraphQLFieldDefinition; - -import java.util.List; - -/** - * Utility wrapper for {@link GraphQLCodeRegistry.Builder} which provides - * a consistent registration mechanism of DataFetchers similar to the annotation-based approach. - * Can be used as a first parameter of a {@link DgsCodeRegistry} annotated method. - */ -public class DgsCodeRegistryBuilder { - - private final List dataFetcherResultProcessors; - - private final GraphQLCodeRegistry.Builder graphQLCodeRegistry; - - public DgsCodeRegistryBuilder( - List dataFetcherResultProcessors, - GraphQLCodeRegistry.Builder codeRegistry) { - this.dataFetcherResultProcessors = dataFetcherResultProcessors; - this.graphQLCodeRegistry = codeRegistry; - } - - public DgsCodeRegistryBuilder dataFetcher(FieldCoordinates coordinates, DataFetcher dataFetcher) { - var wrapped = DataFetcherFactories.wrapDataFetcher(dataFetcher, (dfe, result) -> { - if (result == null || coordinates.getTypeName().equals("Subscription")) { - return result; - } - - var dgsDfe = new DgsDataFetchingEnvironment(dfe); - return dataFetcherResultProcessors.stream() - .filter(p -> p.supportsType(result)) - .findFirst() - .map(p -> p.process(result, dgsDfe)) - .orElse(result); - }); - - graphQLCodeRegistry.dataFetcher(coordinates, wrapped); - return this; - } - - public boolean hasDataFetcher(FieldCoordinates coordinates) { - return graphQLCodeRegistry.hasDataFetcher(coordinates); - } - - public DataFetcher getDataFetcher(FieldCoordinates coordinates, GraphQLFieldDefinition fieldDefinition) { - return graphQLCodeRegistry.getDataFetcher(coordinates, fieldDefinition); - } - - public GraphQLCodeRegistry.Builder getGraphQLCodeRegistry() { - return graphQLCodeRegistry; - } -} diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt new file mode 100644 index 000000000..fac42392c --- /dev/null +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt @@ -0,0 +1,54 @@ +/* + * 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 + +import com.netflix.graphql.dgs.internal.DataFetcherResultProcessor +import graphql.schema.* + +/** + * Utility wrapper for [GraphQLCodeRegistry.Builder] which provides + * a consistent registration mechanism of DataFetchers similar to the annotation-based approach. + * Can be used as a first parameter of a [DgsCodeRegistry] annotated method. + */ +class DgsCodeRegistryBuilder( + private val dataFetcherResultProcessors: List, + private val graphQLCodeRegistry: GraphQLCodeRegistry.Builder +) { + + fun dataFetcher(coordinates: FieldCoordinates, dataFetcher: DataFetcher<*>?): DgsCodeRegistryBuilder { + val wrapped = DataFetcherFactories.wrapDataFetcher(dataFetcher) { dfe, result -> + if (coordinates.typeName == "Subscription") { + return@wrapDataFetcher result + } + + result?.let { + val env = DgsDataFetchingEnvironment(dfe) + dataFetcherResultProcessors.find { it.supportsType(result) }?.process(result, env) ?: result + } + } + + graphQLCodeRegistry.dataFetcher(coordinates, wrapped) + return this + } + + fun hasDataFetcher(coordinates: FieldCoordinates?): Boolean { + return graphQLCodeRegistry.hasDataFetcher(coordinates) + } + + fun getDataFetcher(coordinates: FieldCoordinates?, fieldDefinition: GraphQLFieldDefinition?): DataFetcher<*> { + return graphQLCodeRegistry.getDataFetcher(coordinates, fieldDefinition) + } +} diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt index 42a82a5de..afd39fff4 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt @@ -41,8 +41,6 @@ import graphql.language.TypeName import graphql.language.UnionTypeDefinition import graphql.parser.MultiSourceReader import graphql.schema.Coercing -import graphql.schema.DataFetcher -import graphql.schema.DataFetcherFactories import graphql.schema.DataFetcherFactory import graphql.schema.FieldCoordinates import graphql.schema.GraphQLCodeRegistry @@ -188,12 +186,14 @@ class DgsSchemaProvider( val runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring().codeRegistry(codeRegistryBuilder).fieldVisibility(fieldVisibility) + val dgsCodeRegistryBuilder = DgsCodeRegistryBuilder(dataFetcherResultProcessors, codeRegistryBuilder) + dgsComponents.asSequence() .mapNotNull { dgsComponent -> invokeDgsTypeDefinitionRegistry(dgsComponent, mergedRegistry) } .fold(mergedRegistry) { a, b -> a.merge(b) } findScalars(applicationContext, runtimeWiringBuilder) findDirectives(applicationContext, runtimeWiringBuilder) - findDataFetchers(dgsComponents, codeRegistryBuilder, mergedRegistry) + findDataFetchers(dgsComponents, dgsCodeRegistryBuilder, mergedRegistry) findTypeResolvers(dgsComponents, runtimeWiringBuilder, mergedRegistry) dgsComponents.forEach { dgsComponent -> @@ -270,7 +270,10 @@ class DgsSchemaProvider( throw InvalidDgsConfigurationException("Method annotated with @DgsCodeRegistry must have return type GraphQLCodeRegistry.Builder or DgsCodeRegistryBuilder") } - if (method.parameterCount != 2 || method.parameterTypes[0] != method.returnType || method.parameterTypes[1] != TypeDefinitionRegistry::class.java) { + if (method.parameterCount != 2 || + method.parameterTypes[0] != method.returnType || // Check that the first argument is of type DgsCodeRegistryBuilder or GraphQLCodeRegistry.Builder and the return type is the same + method.parameterTypes[1] != TypeDefinitionRegistry::class.java + ) { throw InvalidDgsConfigurationException("Method annotated with @DgsCodeRegistry must accept the following arguments: GraphQLCodeRegistry.Builder or DgsCodeRegistryBuilder, and TypeDefinitionRegistry. ${dgsComponent.javaClass.name}.${method.name} has the following arguments: ${method.parameterTypes.joinToString()}") } @@ -300,7 +303,7 @@ class DgsSchemaProvider( private fun findDataFetchers( dgsComponents: Collection, - codeRegistryBuilder: GraphQLCodeRegistry.Builder, + codeRegistryBuilder: DgsCodeRegistryBuilder, typeDefinitionRegistry: TypeDefinitionRegistry ) { dgsComponents.forEach { dgsComponent -> @@ -334,7 +337,7 @@ class DgsSchemaProvider( private fun registerDataFetcher( typeDefinitionRegistry: TypeDefinitionRegistry, - codeRegistryBuilder: GraphQLCodeRegistry.Builder, + codeRegistryBuilder: DgsCodeRegistryBuilder, dgsComponent: Any, method: Method, dgsDataAnnotation: MergedAnnotation, @@ -387,12 +390,9 @@ class DgsSchemaProvider( // if we have a datafetcher explicitly defined for a parentType/field, use that and do not // register the base implementation for interfaces if (!codeRegistryBuilder.hasDataFetcher(FieldCoordinates.coordinates(implType.name, field))) { - val dataFetcher = - createBasicDataFetcher(method, dgsComponent, parentType == "Subscription") - codeRegistryBuilder.dataFetcher( - FieldCoordinates.coordinates(implType.name, field), - dataFetcher - ) + val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) + codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(implType.name, field), dataFetcher) + dataFetcherTracingInstrumentationEnabled["${implType.name}.$field"] = enableTracingInstrumentation dataFetcherMetricsInstrumentationEnabled["${implType.name}.$field"] = @@ -402,12 +402,9 @@ class DgsSchemaProvider( } is UnionTypeDefinition -> { type.memberTypes.asSequence().filterIsInstance().forEach { memberType -> - val dataFetcher = - createBasicDataFetcher(method, dgsComponent, parentType == "Subscription") - codeRegistryBuilder.dataFetcher( - FieldCoordinates.coordinates(memberType.name, field), - dataFetcher - ) + val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) + codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(memberType.name, field), dataFetcher) + dataFetcherTracingInstrumentationEnabled["${memberType.name}.$field"] = enableTracingInstrumentation dataFetcherMetricsInstrumentationEnabled["${memberType.name}.$field"] = enableMetricsInstrumentation } @@ -421,11 +418,9 @@ class DgsSchemaProvider( matchingField.inputValueDefinitions.map { it.name }.toSet() ) } - val dataFetcher = createBasicDataFetcher(method, dgsComponent, parentType == "Subscription") - codeRegistryBuilder.dataFetcher( - FieldCoordinates.coordinates(parentType, field), - dataFetcher - ) + + val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) + codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(parentType, field), dataFetcher) } else -> { throw InvalidDgsConfigurationException( @@ -583,21 +578,6 @@ class DgsSchemaProvider( return null } - private fun createBasicDataFetcher(method: Method, dgsComponent: Any, isSubscription: Boolean): DataFetcher { - val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) - - if (isSubscription) { - return dataFetcher - } - - return DataFetcherFactories.wrapDataFetcher(dataFetcher) { dfe, result -> - result?.let { - val env = DgsDataFetchingEnvironment(dfe) - dataFetcherResultProcessors.find { it.supportsType(result) }?.process(result, env) ?: result - } - } - } - private fun findTypeResolvers( dgsComponents: Collection, runtimeWiringBuilder: RuntimeWiring.Builder, From 687277f8b7c7f600476c5e6c2dceeb10afefa025 Mon Sep 17 00:00:00 2001 From: ivanasen Date: Sat, 20 Jan 2024 19:54:52 +0200 Subject: [PATCH 5/8] Make params non-nullable --- .../com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt index fac42392c..e008da320 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt @@ -28,7 +28,7 @@ class DgsCodeRegistryBuilder( private val graphQLCodeRegistry: GraphQLCodeRegistry.Builder ) { - fun dataFetcher(coordinates: FieldCoordinates, dataFetcher: DataFetcher<*>?): DgsCodeRegistryBuilder { + fun dataFetcher(coordinates: FieldCoordinates, dataFetcher: DataFetcher<*>): DgsCodeRegistryBuilder { val wrapped = DataFetcherFactories.wrapDataFetcher(dataFetcher) { dfe, result -> if (coordinates.typeName == "Subscription") { return@wrapDataFetcher result @@ -44,11 +44,11 @@ class DgsCodeRegistryBuilder( return this } - fun hasDataFetcher(coordinates: FieldCoordinates?): Boolean { + fun hasDataFetcher(coordinates: FieldCoordinates): Boolean { return graphQLCodeRegistry.hasDataFetcher(coordinates) } - fun getDataFetcher(coordinates: FieldCoordinates?, fieldDefinition: GraphQLFieldDefinition?): DataFetcher<*> { + fun getDataFetcher(coordinates: FieldCoordinates, fieldDefinition: GraphQLFieldDefinition): DataFetcher<*> { return graphQLCodeRegistry.getDataFetcher(coordinates, fieldDefinition) } } From 55a21128200979a64d142e566ac93cd84a2e529d Mon Sep 17 00:00:00 2001 From: ivanasen Date: Sat, 27 Jan 2024 23:32:14 +0200 Subject: [PATCH 6/8] Fix lint error --- .../kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt index 232ef6cd8..ad1df3801 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt @@ -416,7 +416,6 @@ class DgsSchemaProvider( val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(parentType, field), dataFetcher) - } else -> { throw InvalidDgsConfigurationException( From d4b62ff2d6e52a5b9d86b449340b86029c0e7ef2 Mon Sep 17 00:00:00 2001 From: ivanasen Date: Sat, 27 Jan 2024 23:39:16 +0200 Subject: [PATCH 7/8] Fix merge conflict --- .../com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt index ad1df3801..5886ea6fa 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt @@ -385,7 +385,7 @@ class DgsSchemaProvider( // if we have a datafetcher explicitly defined for a parentType/field, use that and do not // register the base implementation for interfaces if (!codeRegistryBuilder.hasDataFetcher(FieldCoordinates.coordinates(implType.name, field))) { - val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) + val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent.instance, method) codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(implType.name, field), dataFetcher) dataFetcherTracingInstrumentationEnabled["${implType.name}.$field"] = @@ -397,7 +397,7 @@ class DgsSchemaProvider( } is UnionTypeDefinition -> { type.memberTypes.asSequence().filterIsInstance().forEach { memberType -> - val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) + val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent.instance, method) codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(memberType.name, field), dataFetcher) dataFetcherTracingInstrumentationEnabled["${memberType.name}.$field"] = enableTracingInstrumentation @@ -414,7 +414,7 @@ class DgsSchemaProvider( ) } - val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent, method) + val dataFetcher = methodDataFetcherFactory.createDataFetcher(dgsComponent.instance, method) codeRegistryBuilder.dataFetcher(FieldCoordinates.coordinates(parentType, field), dataFetcher) } else -> { From 7eee501b22b4de8532786dc3a1cb4bec26871e16 Mon Sep 17 00:00:00 2001 From: ivanasen Date: Sun, 28 Jan 2024 00:07:35 +0200 Subject: [PATCH 8/8] Fix tests --- .../kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt | 4 ---- .../com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt | 4 ++-- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt index e008da320..d6e01cc95 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/DgsCodeRegistryBuilder.kt @@ -30,10 +30,6 @@ class DgsCodeRegistryBuilder( fun dataFetcher(coordinates: FieldCoordinates, dataFetcher: DataFetcher<*>): DgsCodeRegistryBuilder { val wrapped = DataFetcherFactories.wrapDataFetcher(dataFetcher) { dfe, result -> - if (coordinates.typeName == "Subscription") { - return@wrapDataFetcher result - } - result?.let { val env = DgsDataFetchingEnvironment(dfe) dataFetcherResultProcessors.find { it.supportsType(result) }?.process(result, env) ?: result diff --git a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt index 5886ea6fa..111fdbeb8 100644 --- a/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt +++ b/graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/DgsSchemaProvider.kt @@ -275,9 +275,9 @@ class DgsSchemaProvider( } if (method.returnType == DgsCodeRegistryBuilder::class.java) { - ReflectionUtils.invokeMethod(method, dgsComponent, dgsCodeRegistryBuilder, registry) + ReflectionUtils.invokeMethod(method, dgsComponent.instance, dgsCodeRegistryBuilder, registry) } else if (method.returnType == GraphQLCodeRegistry.Builder::class.java) { - ReflectionUtils.invokeMethod(method, dgsComponent, codeRegistryBuilder, registry) + ReflectionUtils.invokeMethod(method, dgsComponent.instance, codeRegistryBuilder, registry) } } }