From 92c58be6abd2d604eda76f54ac04e8f09d0523f8 Mon Sep 17 00:00:00 2001 From: Dariusz Kuc Date: Thu, 24 Jan 2019 12:09:33 -0600 Subject: [PATCH] Enable support for specifying custom DataFetchers (#152) * Enable coroutine support in default KotlinDataFetcher * Enable support for specifying custom DataFetchers * fix compilation errors * simplify KotlinDataFetcherFactoryProvider * add accidentally removed data fetcher tests * renaming variable name --- .../com/expedia/graphql/sample/Application.kt | 17 ++++++-- .../CustomDataFetcherFactoryProvider.kt | 23 ++++++++++ .../extension/CustomSchemaGeneratorHooks.kt | 2 +- .../graphql/sample/query/ContextualQuery.kt | 2 +- .../DataFetcherExecutionValidator.kt | 2 +- .../expedia/graphql/SchemaGeneratorConfig.kt | 4 +- .../DataFetcherExecutionPredicate.kt | 4 +- .../FunctionDataFetcher.kt} | 21 +++++---- .../KotlinDataFetcherFactoryProvider.kt | 43 +++++++++++++++++++ .../generator/types/FunctionTypeBuilder.kt | 5 +-- .../generator/types/PropertyTypeBuilder.kt | 21 +-------- .../graphql/hooks/SchemaGeneratorHooks.kt | 8 +--- .../CustomDataFetcherTests.kt | 23 +++++++--- .../DataFetchPredicateTests.kt | 3 +- .../FunctionDataFetcherTest.kt} | 17 ++++---- .../types/FunctionTypeBuilderTest.kt | 40 +++-------------- .../types/PropertyTypeBuilderTest.kt | 13 +++--- .../graphql/generator/types/TypeTestHelper.kt | 4 +- .../graphql/hooks/SchemaGeneratorHooksTest.kt | 40 ----------------- 19 files changed, 144 insertions(+), 148 deletions(-) create mode 100644 example/src/main/kotlin/com/expedia/graphql/sample/dataFetchers/CustomDataFetcherFactoryProvider.kt rename src/main/kotlin/com/expedia/graphql/{hooks => execution}/DataFetcherExecutionPredicate.kt (90%) rename src/main/kotlin/com/expedia/graphql/{KotlinDataFetcher.kt => execution/FunctionDataFetcher.kt} (77%) create mode 100644 src/main/kotlin/com/expedia/graphql/execution/KotlinDataFetcherFactoryProvider.kt rename src/test/kotlin/com/expedia/graphql/{dataFetchers => execution}/CustomDataFetcherTests.kt (67%) rename src/test/kotlin/com/expedia/graphql/{dataFetchers => execution}/DataFetchPredicateTests.kt (97%) rename src/test/kotlin/com/expedia/graphql/{KotlinDataFetcherTest.kt => execution/FunctionDataFetcherTest.kt} (78%) diff --git a/example/src/main/kotlin/com/expedia/graphql/sample/Application.kt b/example/src/main/kotlin/com/expedia/graphql/sample/Application.kt index 22b57a2a37..8d10b834f9 100644 --- a/example/src/main/kotlin/com/expedia/graphql/sample/Application.kt +++ b/example/src/main/kotlin/com/expedia/graphql/sample/Application.kt @@ -3,7 +3,10 @@ package com.expedia.graphql.sample import com.expedia.graphql.DirectiveWiringHelper import com.expedia.graphql.SchemaGeneratorConfig import com.expedia.graphql.TopLevelObject +import com.expedia.graphql.execution.KotlinDataFetcherFactoryProvider +import com.expedia.graphql.hooks.SchemaGeneratorHooks import com.expedia.graphql.sample.context.MyGraphQLContextBuilder +import com.expedia.graphql.sample.dataFetchers.CustomDataFetcherFactoryProvider import com.expedia.graphql.sample.dataFetchers.SpringDataFetcherFactory import com.expedia.graphql.sample.directives.DirectiveWiringFactory import com.expedia.graphql.sample.directives.LowercaseDirectiveWiring @@ -42,10 +45,18 @@ class Application { fun wiringFactory() = DirectiveWiringFactory() @Bean - fun schemaConfig(dataFetcherFactory: SpringDataFetcherFactory, validator: Validator, wiringFactory: DirectiveWiringFactory): SchemaGeneratorConfig = SchemaGeneratorConfig( + fun hooks(validator: Validator, wiringFactory: DirectiveWiringFactory) = + CustomSchemaGeneratorHooks(validator, DirectiveWiringHelper(wiringFactory, mapOf("lowercase" to LowercaseDirectiveWiring()))) + + @Bean + fun dataFetcherFactoryProvider(springDataFetcherFactory: SpringDataFetcherFactory, hooks: SchemaGeneratorHooks) = + CustomDataFetcherFactoryProvider(springDataFetcherFactory, hooks) + + @Bean + fun schemaConfig(hooks: SchemaGeneratorHooks, dataFetcherFactoryProvider: KotlinDataFetcherFactoryProvider): SchemaGeneratorConfig = SchemaGeneratorConfig( supportedPackages = listOf("com.expedia"), - hooks = CustomSchemaGeneratorHooks(validator, DirectiveWiringHelper(wiringFactory, mapOf("lowercase" to LowercaseDirectiveWiring()))), - dataFetcherFactory = dataFetcherFactory + hooks = hooks, + dataFetcherFactoryProvider = dataFetcherFactoryProvider ) @Bean diff --git a/example/src/main/kotlin/com/expedia/graphql/sample/dataFetchers/CustomDataFetcherFactoryProvider.kt b/example/src/main/kotlin/com/expedia/graphql/sample/dataFetchers/CustomDataFetcherFactoryProvider.kt new file mode 100644 index 0000000000..ff291364c2 --- /dev/null +++ b/example/src/main/kotlin/com/expedia/graphql/sample/dataFetchers/CustomDataFetcherFactoryProvider.kt @@ -0,0 +1,23 @@ +package com.expedia.graphql.sample.dataFetchers + +import com.expedia.graphql.execution.KotlinDataFetcherFactoryProvider +import com.expedia.graphql.hooks.SchemaGeneratorHooks +import graphql.schema.DataFetcherFactory +import kotlin.reflect.KClass +import kotlin.reflect.KProperty + +/** + * Custom DataFetcherFactory provider that returns custom Spring based DataFetcherFactory for resolving lateinit properties. + */ +class CustomDataFetcherFactoryProvider( + private val springDataFetcherFactory: SpringDataFetcherFactory, + hooks: SchemaGeneratorHooks +) : KotlinDataFetcherFactoryProvider(hooks) { + + override fun propertyDataFetcherFactory(kClazz: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory = + if (kProperty.isLateinit) { + springDataFetcherFactory + } else { + super.propertyDataFetcherFactory(kClazz, kProperty) + } +} \ No newline at end of file diff --git a/example/src/main/kotlin/com/expedia/graphql/sample/extension/CustomSchemaGeneratorHooks.kt b/example/src/main/kotlin/com/expedia/graphql/sample/extension/CustomSchemaGeneratorHooks.kt index 3327ea6433..3637cd2edf 100644 --- a/example/src/main/kotlin/com/expedia/graphql/sample/extension/CustomSchemaGeneratorHooks.kt +++ b/example/src/main/kotlin/com/expedia/graphql/sample/extension/CustomSchemaGeneratorHooks.kt @@ -1,7 +1,7 @@ package com.expedia.graphql.sample.extension import com.expedia.graphql.DirectiveWiringHelper -import com.expedia.graphql.hooks.DataFetcherExecutionPredicate +import com.expedia.graphql.execution.DataFetcherExecutionPredicate import com.expedia.graphql.hooks.SchemaGeneratorHooks import com.expedia.graphql.sample.validation.DataFetcherExecutionValidator import graphql.language.StringValue diff --git a/example/src/main/kotlin/com/expedia/graphql/sample/query/ContextualQuery.kt b/example/src/main/kotlin/com/expedia/graphql/sample/query/ContextualQuery.kt index cbd701a8a4..1e24980713 100644 --- a/example/src/main/kotlin/com/expedia/graphql/sample/query/ContextualQuery.kt +++ b/example/src/main/kotlin/com/expedia/graphql/sample/query/ContextualQuery.kt @@ -11,7 +11,7 @@ import org.springframework.stereotype.Component * schema and will be automatically autowired at runtime using value from the environment. * * @see com.expedia.graphql.sample.context.MyGraphQLContextBuilder - * @see com.expedia.graphql.KotlinDataFetcher + * @see com.expedia.graphql.FunctionDataFetcher */ @Component class ContextualQuery: Query { diff --git a/example/src/main/kotlin/com/expedia/graphql/sample/validation/DataFetcherExecutionValidator.kt b/example/src/main/kotlin/com/expedia/graphql/sample/validation/DataFetcherExecutionValidator.kt index 9bdb875098..e09bf53e32 100644 --- a/example/src/main/kotlin/com/expedia/graphql/sample/validation/DataFetcherExecutionValidator.kt +++ b/example/src/main/kotlin/com/expedia/graphql/sample/validation/DataFetcherExecutionValidator.kt @@ -1,6 +1,6 @@ package com.expedia.graphql.sample.validation -import com.expedia.graphql.hooks.DataFetcherExecutionPredicate +import com.expedia.graphql.execution.DataFetcherExecutionPredicate import com.expedia.graphql.sample.exceptions.ValidationException import com.expedia.graphql.sample.exceptions.asConstraintError import graphql.schema.DataFetchingEnvironment diff --git a/src/main/kotlin/com/expedia/graphql/SchemaGeneratorConfig.kt b/src/main/kotlin/com/expedia/graphql/SchemaGeneratorConfig.kt index 322c7e2f30..a422145a95 100644 --- a/src/main/kotlin/com/expedia/graphql/SchemaGeneratorConfig.kt +++ b/src/main/kotlin/com/expedia/graphql/SchemaGeneratorConfig.kt @@ -1,8 +1,8 @@ package com.expedia.graphql +import com.expedia.graphql.execution.KotlinDataFetcherFactoryProvider import com.expedia.graphql.hooks.NoopSchemaGeneratorHooks import com.expedia.graphql.hooks.SchemaGeneratorHooks -import graphql.schema.DataFetcherFactory /** * Settings for generating the schema. @@ -12,5 +12,5 @@ data class SchemaGeneratorConfig( val topLevelQueryName: String = "TopLevelQuery", val topLevelMutationName: String = "TopLevelMutation", val hooks: SchemaGeneratorHooks = NoopSchemaGeneratorHooks(), - val dataFetcherFactory: DataFetcherFactory<*>? = null + val dataFetcherFactoryProvider: KotlinDataFetcherFactoryProvider = KotlinDataFetcherFactoryProvider(hooks) ) diff --git a/src/main/kotlin/com/expedia/graphql/hooks/DataFetcherExecutionPredicate.kt b/src/main/kotlin/com/expedia/graphql/execution/DataFetcherExecutionPredicate.kt similarity index 90% rename from src/main/kotlin/com/expedia/graphql/hooks/DataFetcherExecutionPredicate.kt rename to src/main/kotlin/com/expedia/graphql/execution/DataFetcherExecutionPredicate.kt index 06882efa43..a7b57c94f5 100644 --- a/src/main/kotlin/com/expedia/graphql/hooks/DataFetcherExecutionPredicate.kt +++ b/src/main/kotlin/com/expedia/graphql/execution/DataFetcherExecutionPredicate.kt @@ -1,10 +1,10 @@ -package com.expedia.graphql.hooks +package com.expedia.graphql.execution import graphql.schema.DataFetchingEnvironment import kotlin.reflect.KParameter /** - * Perform runtime evaluations of each parameter passed to any KotlinDataFetcher. + * Perform runtime evaluations of each parameter passed to any FunctionDataFetcher. * * The DataFetcherExecutionPredicate is declared globally for all the datafetchers instances and all the parameters. * However a more precise logic (at the field level) is possible depending on the implement of `evaluate` diff --git a/src/main/kotlin/com/expedia/graphql/KotlinDataFetcher.kt b/src/main/kotlin/com/expedia/graphql/execution/FunctionDataFetcher.kt similarity index 77% rename from src/main/kotlin/com/expedia/graphql/KotlinDataFetcher.kt rename to src/main/kotlin/com/expedia/graphql/execution/FunctionDataFetcher.kt index cc63c19c2f..b9f9b9427e 100644 --- a/src/main/kotlin/com/expedia/graphql/KotlinDataFetcher.kt +++ b/src/main/kotlin/com/expedia/graphql/execution/FunctionDataFetcher.kt @@ -1,9 +1,9 @@ -package com.expedia.graphql +package com.expedia.graphql.execution import com.expedia.graphql.generator.extensions.getName import com.expedia.graphql.generator.extensions.isGraphQLContext import com.expedia.graphql.generator.extensions.javaTypeClass -import com.expedia.graphql.hooks.DataFetcherExecutionPredicate +import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import graphql.schema.DataFetcher import graphql.schema.DataFetchingEnvironment @@ -16,16 +16,19 @@ import kotlin.reflect.full.callSuspend import kotlin.reflect.full.valueParameters /** - * Simple DataFetcher that invokes function on the target object. + * Simple DataFetcher that invokes target function on the given object. * - * @param target The target object that performs the data fetching + * @param target The target object that performs the data fetching, if not specified then this data fetcher will attempt + * to use source object from the environment * @param fn The Kotlin function being invoked + * @param objectMapper Jackson ObjectMapper that will be used to deserialize environment arguments to the expected function arguments * @param executionPredicate Predicate to run to map the value to a new result */ -class KotlinDataFetcher( +class FunctionDataFetcher( private val target: Any?, private val fn: KFunction<*>, - private val executionPredicate: DataFetcherExecutionPredicate? + private val objectMapper: ObjectMapper = jacksonObjectMapper(), + private val executionPredicate: DataFetcherExecutionPredicate? = null ) : DataFetcher { @Suppress("Detekt.SpreadOperator") @@ -51,13 +54,9 @@ class KotlinDataFetcher( } else { val name = param.getName() val klazz = param.type.javaTypeClass - val value = mapper.convertValue(environment.arguments[name], klazz) + val value = objectMapper.convertValue(environment.arguments[name], klazz) val predicateResult = executionPredicate?.evaluate(value = value, parameter = param, environment = environment) predicateResult ?: value } - - private companion object { - private val mapper = jacksonObjectMapper() - } } diff --git a/src/main/kotlin/com/expedia/graphql/execution/KotlinDataFetcherFactoryProvider.kt b/src/main/kotlin/com/expedia/graphql/execution/KotlinDataFetcherFactoryProvider.kt new file mode 100644 index 0000000000..fc41243066 --- /dev/null +++ b/src/main/kotlin/com/expedia/graphql/execution/KotlinDataFetcherFactoryProvider.kt @@ -0,0 +1,43 @@ +package com.expedia.graphql.execution + +import com.expedia.graphql.hooks.SchemaGeneratorHooks +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import graphql.schema.DataFetcherFactories +import graphql.schema.DataFetcherFactory +import graphql.schema.PropertyDataFetcher +import kotlin.reflect.KClass +import kotlin.reflect.KFunction +import kotlin.reflect.KProperty + +/** + * DataFetcherFactoryProvider is used during schema construction to obtain [DataFetcherFactory] that should be used + * for target function and property resolution. + */ +open class KotlinDataFetcherFactoryProvider(private val hooks: SchemaGeneratorHooks) { + + private val defaultObjectMapper = jacksonObjectMapper() + + /** + * Retrieve instance of [DataFetcherFactory] that will be used to resolve target function. + * + * @param target target object that performs the data fetching or NULL if target object should be dynamically + * retrieved during data fetcher execution from [graphql.schema.DataFetchingEnvironment] + * @param kFunction Kotlin function being invoked + */ + open fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>): DataFetcherFactory = + DataFetcherFactories.useDataFetcher( + FunctionDataFetcher( + target = target, + fn = kFunction, + objectMapper = defaultObjectMapper, + executionPredicate = hooks.dataFetcherExecutionPredicate)) + + /** + * Retrieve instance of [DataFetcherFactory] that will be used to resolve target property. + * + * @param kClass parent class that contains this property + * @param kProperty Kotlin property that should be resolved + */ + open fun propertyDataFetcherFactory(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory = + DataFetcherFactories.useDataFetcher(PropertyDataFetcher(kProperty.name)) +} diff --git a/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt b/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt index 042c4d2ade..13382f41ad 100644 --- a/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt +++ b/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt @@ -1,6 +1,5 @@ package com.expedia.graphql.generator.types -import com.expedia.graphql.KotlinDataFetcher import com.expedia.graphql.exceptions.InvalidInputFieldTypeException import com.expedia.graphql.generator.SchemaGenerator import com.expedia.graphql.generator.TypeBuilder @@ -41,9 +40,7 @@ internal class FunctionTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gen } if (!abstract) { - val dataFetcher = KotlinDataFetcher(target, fn, config.hooks.dataFetcherExecutionPredicate) - val hookDataFetcher = config.hooks.didGenerateDataFetcher(fn, dataFetcher) - builder.dataFetcher(hookDataFetcher) + builder.dataFetcherFactory(config.dataFetcherFactoryProvider.functionDataFetcherFactory(target = target, kFunction = fn)) } val monadType = config.hooks.willResolveMonad(fn.returnType) diff --git a/src/main/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilder.kt b/src/main/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilder.kt index 684af8a8b3..66e891027f 100644 --- a/src/main/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilder.kt +++ b/src/main/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilder.kt @@ -5,9 +5,7 @@ import com.expedia.graphql.generator.TypeBuilder import com.expedia.graphql.generator.extensions.getPropertyDeprecationReason import com.expedia.graphql.generator.extensions.getPropertyDescription import com.expedia.graphql.generator.extensions.isPropertyGraphQLID -import graphql.schema.DataFetcherFactory import graphql.schema.GraphQLFieldDefinition -import graphql.schema.GraphQLNonNull import graphql.schema.GraphQLOutputType import kotlin.reflect.KClass import kotlin.reflect.KProperty @@ -21,29 +19,14 @@ internal class PropertyTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gen .description(prop.getPropertyDescription(parentClass)) .name(prop.name) .type(propertyType) + .dataFetcherFactory(config.dataFetcherFactoryProvider.propertyDataFetcherFactory(kClass = parentClass, kProperty = prop)) .deprecate(prop.getPropertyDeprecationReason(parentClass)) generator.directives(prop).forEach { fieldBuilder.withDirective(it) } - val field = if (config.dataFetcherFactory != null && prop.isLateinit) { - updatePropertyFieldBuilder(propertyType, fieldBuilder, config.dataFetcherFactory) - } else { - fieldBuilder - }.build() - + val field = fieldBuilder.build() return config.hooks.onRewireGraphQLType(prop.returnType, field) as GraphQLFieldDefinition } - - private fun updatePropertyFieldBuilder(propertyType: GraphQLOutputType, fieldBuilder: GraphQLFieldDefinition.Builder, dataFetcherFactory: DataFetcherFactory<*>?): GraphQLFieldDefinition.Builder { - val updatedFieldBuilder = if (propertyType is GraphQLNonNull) { - val graphQLOutputType = propertyType.wrappedType as? GraphQLOutputType - if (graphQLOutputType != null) fieldBuilder.type(graphQLOutputType) else fieldBuilder - } else { - fieldBuilder - } - - return updatedFieldBuilder.dataFetcherFactory(dataFetcherFactory) - } } diff --git a/src/main/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooks.kt b/src/main/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooks.kt index b503942832..718be23a8f 100644 --- a/src/main/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooks.kt +++ b/src/main/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooks.kt @@ -1,7 +1,7 @@ package com.expedia.graphql.hooks +import com.expedia.graphql.execution.DataFetcherExecutionPredicate import com.expedia.graphql.generator.extensions.getTypeOfFirstArgument -import graphql.schema.DataFetcher import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLSchema import graphql.schema.GraphQLType @@ -73,12 +73,6 @@ interface SchemaGeneratorHooks { */ fun didGenerateGraphQLType(type: KType, generatedType: GraphQLType) = Unit - /** - * Called after converting the function to a data fetcher allowing wrapping the fetcher to modify data or instrument it. - * This is more useful than the graphql.execution.instrumentation.Instrumentation as you have the function type here - */ - fun didGenerateDataFetcher(function: KFunction<*>, dataFetcher: DataFetcher<*>): DataFetcher<*> = dataFetcher - /** * Called after converting the function to a field definition but before adding to the schema to allow customization */ diff --git a/src/test/kotlin/com/expedia/graphql/dataFetchers/CustomDataFetcherTests.kt b/src/test/kotlin/com/expedia/graphql/execution/CustomDataFetcherTests.kt similarity index 67% rename from src/test/kotlin/com/expedia/graphql/dataFetchers/CustomDataFetcherTests.kt rename to src/test/kotlin/com/expedia/graphql/execution/CustomDataFetcherTests.kt index a94a9737a8..90602d652a 100644 --- a/src/test/kotlin/com/expedia/graphql/dataFetchers/CustomDataFetcherTests.kt +++ b/src/test/kotlin/com/expedia/graphql/execution/CustomDataFetcherTests.kt @@ -1,25 +1,28 @@ -package com.expedia.graphql.dataFetchers +package com.expedia.graphql.execution -import com.expedia.graphql.TopLevelObject import com.expedia.graphql.SchemaGeneratorConfig +import com.expedia.graphql.TopLevelObject import com.expedia.graphql.extensions.deepName +import com.expedia.graphql.hooks.NoopSchemaGeneratorHooks import com.expedia.graphql.toSchema import graphql.GraphQL import graphql.schema.DataFetcher +import graphql.schema.DataFetcherFactories import graphql.schema.DataFetcherFactory -import graphql.schema.DataFetcherFactoryEnvironment import graphql.schema.DataFetchingEnvironment import org.junit.jupiter.api.Test +import kotlin.reflect.KClass +import kotlin.reflect.KProperty import kotlin.test.assertEquals class CustomDataFetcherTests { @Test fun `Custom DataFetcher can be used on functions`() { - val config = SchemaGeneratorConfig(supportedPackages = listOf("com.expedia"), dataFetcherFactory = PetDataFetcherFactory()) + val config = SchemaGeneratorConfig(supportedPackages = listOf("com.expedia"), dataFetcherFactoryProvider = CustomDataFetcherFactoryProvider()) val schema = toSchema(listOf(TopLevelObject(AnimalQuery())), config = config) val animalType = schema.getObjectType("Animal") - assertEquals("AnimalDetails", animalType.getFieldDefinition("details").type.deepName) + assertEquals("AnimalDetails!", animalType.getFieldDefinition("details").type.deepName) val graphQL = GraphQL.newGraphQL(schema).build() val execute = graphQL.execute("{ findAnimal { id type details { specialId } } }") @@ -46,8 +49,14 @@ data class Animal( data class AnimalDetails(val specialId: Int) -class PetDataFetcherFactory : DataFetcherFactory { - override fun get(environment: DataFetcherFactoryEnvironment?): DataFetcher = AnimalDetailsDataFetcher() +class CustomDataFetcherFactoryProvider : KotlinDataFetcherFactoryProvider(NoopSchemaGeneratorHooks()) { + + override fun propertyDataFetcherFactory(kClazz: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory = + if (kProperty.isLateinit) { + DataFetcherFactories.useDataFetcher(AnimalDetailsDataFetcher()) + } else { + super.propertyDataFetcherFactory(kClazz, kProperty) + } } class AnimalDetailsDataFetcher : DataFetcher { diff --git a/src/test/kotlin/com/expedia/graphql/dataFetchers/DataFetchPredicateTests.kt b/src/test/kotlin/com/expedia/graphql/execution/DataFetchPredicateTests.kt similarity index 97% rename from src/test/kotlin/com/expedia/graphql/dataFetchers/DataFetchPredicateTests.kt rename to src/test/kotlin/com/expedia/graphql/execution/DataFetchPredicateTests.kt index 293a1c903f..d0a2f49140 100644 --- a/src/test/kotlin/com/expedia/graphql/dataFetchers/DataFetchPredicateTests.kt +++ b/src/test/kotlin/com/expedia/graphql/execution/DataFetchPredicateTests.kt @@ -1,9 +1,8 @@ -package com.expedia.graphql.dataFetchers +package com.expedia.graphql.execution import com.expedia.graphql.TopLevelObject import com.expedia.graphql.exceptions.GraphQLKotlinException import com.expedia.graphql.getTestSchemaConfigWithHooks -import com.expedia.graphql.hooks.DataFetcherExecutionPredicate import com.expedia.graphql.hooks.SchemaGeneratorHooks import com.expedia.graphql.toSchema import graphql.ExceptionWhileDataFetching diff --git a/src/test/kotlin/com/expedia/graphql/KotlinDataFetcherTest.kt b/src/test/kotlin/com/expedia/graphql/execution/FunctionDataFetcherTest.kt similarity index 78% rename from src/test/kotlin/com/expedia/graphql/KotlinDataFetcherTest.kt rename to src/test/kotlin/com/expedia/graphql/execution/FunctionDataFetcherTest.kt index c60bc219b8..23d4b3452c 100644 --- a/src/test/kotlin/com/expedia/graphql/KotlinDataFetcherTest.kt +++ b/src/test/kotlin/com/expedia/graphql/execution/FunctionDataFetcherTest.kt @@ -1,7 +1,6 @@ -package com.expedia.graphql +package com.expedia.graphql.execution import com.expedia.graphql.annotations.GraphQLContext -import com.expedia.graphql.hooks.DataFetcherExecutionPredicate import graphql.schema.DataFetchingEnvironment import io.mockk.every import io.mockk.mockk @@ -9,7 +8,7 @@ import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.test.assertNull -internal class KotlinDataFetcherTest { +internal class FunctionDataFetcherTest { internal class MyClass { fun print(string: String) = string @@ -19,7 +18,7 @@ internal class KotlinDataFetcherTest { @Test fun `null target and null source returns null`() { - val dataFetcher = KotlinDataFetcher(null, MyClass::print, null) + val dataFetcher = FunctionDataFetcher(target = null, fn = MyClass::print) val mockEnvironmet: DataFetchingEnvironment = mockk() every { mockEnvironmet.getSource() } returns null assertNull(dataFetcher.get(mockEnvironmet)) @@ -27,7 +26,7 @@ internal class KotlinDataFetcherTest { @Test fun `null target and valid source returns the value`() { - val dataFetcher = KotlinDataFetcher(null, MyClass::print, null) + val dataFetcher = FunctionDataFetcher(target = null, fn = MyClass::print) val mockEnvironmet: DataFetchingEnvironment = mockk() every { mockEnvironmet.getSource() } returns MyClass() every { mockEnvironmet.arguments } returns mapOf("string" to "hello") @@ -36,7 +35,7 @@ internal class KotlinDataFetcherTest { @Test fun `valid target and null source returns the value`() { - val dataFetcher = KotlinDataFetcher(MyClass(), MyClass::print, null) + val dataFetcher = FunctionDataFetcher(target = MyClass(), fn = MyClass::print) val mockEnvironmet: DataFetchingEnvironment = mockk() every { mockEnvironmet.arguments } returns mapOf("string" to "hello") assertEquals(expected = "hello", actual = dataFetcher.get(mockEnvironmet)) @@ -44,7 +43,7 @@ internal class KotlinDataFetcherTest { @Test fun `valid target with context`() { - val dataFetcher = KotlinDataFetcher(MyClass(), MyClass::context, null) + val dataFetcher = FunctionDataFetcher(target = MyClass(), fn = MyClass::context) val mockEnvironmet: DataFetchingEnvironment = mockk() every { mockEnvironmet.getContext() } returns "foo" assertEquals(expected = "foo", actual = dataFetcher.get(mockEnvironmet)) @@ -54,7 +53,7 @@ internal class KotlinDataFetcherTest { fun `valid target and value from predicate`() { val mockPredicate: DataFetcherExecutionPredicate = mockk() every { mockPredicate.evaluate(any(), any(), any()) } returns "baz" - val dataFetcher = KotlinDataFetcher(MyClass(), MyClass::print, mockPredicate) + val dataFetcher = FunctionDataFetcher(target = MyClass(), fn = MyClass::print, executionPredicate = mockPredicate) val mockEnvironmet: DataFetchingEnvironment = mockk() every { mockEnvironmet.arguments } returns mapOf("string" to "hello") assertEquals(expected = "baz", actual = dataFetcher.get(mockEnvironmet)) @@ -64,7 +63,7 @@ internal class KotlinDataFetcherTest { fun `valid target and null from predicate`() { val mockPredicate: DataFetcherExecutionPredicate = mockk() every { mockPredicate.evaluate(any(), any(), any()) } returns null - val dataFetcher = KotlinDataFetcher(MyClass(), MyClass::print, mockPredicate) + val dataFetcher = FunctionDataFetcher(target = MyClass(), fn = MyClass::print, executionPredicate = mockPredicate) val mockEnvironmet: DataFetchingEnvironment = mockk() every { mockEnvironmet.arguments } returns mapOf("string" to "hello") assertEquals(expected = "hello", actual = dataFetcher.get(mockEnvironmet)) diff --git a/src/test/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilderTest.kt b/src/test/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilderTest.kt index 11d97ddc71..5bc231dfd5 100644 --- a/src/test/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilderTest.kt +++ b/src/test/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilderTest.kt @@ -1,18 +1,14 @@ package com.expedia.graphql.generator.types -import com.expedia.graphql.KotlinDataFetcher +import com.expedia.graphql.execution.FunctionDataFetcher import com.expedia.graphql.annotations.GraphQLContext import com.expedia.graphql.annotations.GraphQLDescription import com.expedia.graphql.annotations.GraphQLDirective -import com.expedia.graphql.generator.extensions.isTrue -import com.expedia.graphql.hooks.SchemaGeneratorHooks import graphql.Scalars import graphql.introspection.Introspection -import graphql.schema.DataFetcher import graphql.schema.GraphQLNonNull import org.junit.jupiter.api.Test import java.util.UUID -import kotlin.reflect.KFunction import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue @@ -26,18 +22,6 @@ internal class FunctionTypeBuilderTest : TypeTestHelper() { builder = FunctionTypeBuilder(generator) } - override fun beforeSetup() { - hooks = DataFetcherHooks() - } - - internal class DataFetcherHooks : SchemaGeneratorHooks { - var calledHook = false - override fun didGenerateDataFetcher(function: KFunction<*>, dataFetcher: DataFetcher<*>): DataFetcher<*> { - calledHook = true - return dataFetcher - } - } - internal interface MyInterface { fun printMessage(message: String): String } @@ -139,39 +123,29 @@ internal class FunctionTypeBuilderTest : TypeTestHelper() { } @Test - fun `function with hooks`() { - val newBuilder = FunctionTypeBuilder(generator) - val kFunction = Happy::paint - - assertFalse((hooks as? DataFetcherHooks)?.calledHook.isTrue()) - newBuilder.function(kFunction) - assertTrue((hooks as? DataFetcherHooks)?.calledHook.isTrue()) - } - - @Test - fun `Non-abstract function with no hooks`() { + fun `Non-abstract function`() { val kFunction = MyInterface::printMessage val result = builder.function(fn = kFunction, target = null, abstract = false) assertEquals(expected = 1, actual = result.arguments.size) - assertTrue(result.dataFetcher is KotlinDataFetcher) + assertTrue(result.dataFetcher is FunctionDataFetcher) } @Test - fun `Abstract function with no hooks`() { + fun `Abstract function`() { val kFunction = MyInterface::printMessage val result = builder.function(fn = kFunction, target = null, abstract = true) assertEquals(expected = 1, actual = result.arguments.size) - assertFalse(result.dataFetcher is KotlinDataFetcher) + assertFalse(result.dataFetcher is FunctionDataFetcher) } @Test - fun `Abstract function with target and no hooks`() { + fun `Abstract function with target`() { val kFunction = MyInterface::printMessage val result = builder.function(fn = kFunction, target = MyImplementation(), abstract = true) assertEquals(expected = 1, actual = result.arguments.size) - assertFalse(result.dataFetcher is KotlinDataFetcher) + assertFalse(result.dataFetcher is FunctionDataFetcher) } } diff --git a/src/test/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilderTest.kt b/src/test/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilderTest.kt index 474c4c7462..d7040ab96b 100644 --- a/src/test/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilderTest.kt +++ b/src/test/kotlin/com/expedia/graphql/generator/types/PropertyTypeBuilderTest.kt @@ -4,6 +4,7 @@ import com.expedia.graphql.SchemaGeneratorConfig import com.expedia.graphql.annotations.GraphQLDescription import com.expedia.graphql.annotations.GraphQLDirective import com.expedia.graphql.annotations.GraphQLID +import com.expedia.graphql.execution.KotlinDataFetcherFactoryProvider import com.expedia.graphql.generator.SchemaGenerator import com.expedia.graphql.hooks.NoopSchemaGeneratorHooks import graphql.Scalars @@ -134,10 +135,12 @@ internal class PropertyTypeBuilderTest : TypeTestHelper() { val localConfig: SchemaGeneratorConfig = mockk() every { localConfig.hooks } returns NoopSchemaGeneratorHooks() every { localConfig.supportedPackages } returns emptyList() - val mockDataDatafetcher: DataFetcher<*> = mockk() - val dataFetcherFactory: DataFetcherFactory<*> = mockk() - every { dataFetcherFactory.get(any()) } returns mockDataDatafetcher - every { localConfig.dataFetcherFactory } returns dataFetcherFactory + val mockDataFetcherFactoryProvider: KotlinDataFetcherFactoryProvider = mockk() + val mockFactory: DataFetcherFactory = mockk() + val mockDataFetcher: DataFetcher = mockk() + every { mockDataFetcherFactoryProvider.propertyDataFetcherFactory(any(), any()) } returns mockFactory + every { mockFactory.get(any()) } returns mockDataFetcher + every { localConfig.dataFetcherFactoryProvider } returns mockDataFetcherFactoryProvider val localGenerator = SchemaGenerator(localConfig) val localBuilder = PropertyTypeBuilder(localGenerator) @@ -145,6 +148,6 @@ internal class PropertyTypeBuilderTest : TypeTestHelper() { val result = localBuilder.property(prop, ClassWithProperties::class) assertFalse(result.dataFetcher is PropertyDataFetcher) - assertEquals(expected = mockDataDatafetcher, actual = result.dataFetcher) + assertEquals(expected = mockDataFetcher, actual = result.dataFetcher) } } diff --git a/src/test/kotlin/com/expedia/graphql/generator/types/TypeTestHelper.kt b/src/test/kotlin/com/expedia/graphql/generator/types/TypeTestHelper.kt index 95bc3f848a..5d40e396e4 100644 --- a/src/test/kotlin/com/expedia/graphql/generator/types/TypeTestHelper.kt +++ b/src/test/kotlin/com/expedia/graphql/generator/types/TypeTestHelper.kt @@ -1,6 +1,7 @@ package com.expedia.graphql.generator.types import com.expedia.graphql.SchemaGeneratorConfig +import com.expedia.graphql.execution.KotlinDataFetcherFactoryProvider import com.expedia.graphql.generator.SchemaGenerator import com.expedia.graphql.generator.SubTypeMapper import com.expedia.graphql.generator.state.SchemaGeneratorState @@ -30,6 +31,7 @@ internal open class TypeTestHelper { var subTypeMapper = spyk(SubTypeMapper(listOf("com.expedia.graphql.generator.types"))) var cache = spyk(TypesCache(listOf("com.expedia.graphql.generator.types"))) var hooks: SchemaGeneratorHooks = NoopSchemaGeneratorHooks() + var dataFetcherFactory: KotlinDataFetcherFactoryProvider = KotlinDataFetcherFactoryProvider(hooks) private var scalarTypeBuilder: ScalarTypeBuilder? = null private var objectTypeBuilder: ObjectTypeBuilder? = null @@ -46,7 +48,7 @@ internal open class TypeTestHelper { every { generator.config } returns config every { generator.subTypeMapper } returns subTypeMapper every { config.hooks } returns hooks - every { config.dataFetcherFactory } returns null + every { config.dataFetcherFactoryProvider } returns dataFetcherFactory every { config.topLevelQueryName } returns "TestTopLevelQuery" every { config.topLevelMutationName } returns "TestTopLevelMutation" diff --git a/src/test/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooksTest.kt b/src/test/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooksTest.kt index a9ab231dd4..b7de99b59f 100644 --- a/src/test/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooksTest.kt +++ b/src/test/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooksTest.kt @@ -5,9 +5,6 @@ import com.expedia.graphql.extensions.deepName import com.expedia.graphql.generator.extensions.getSimpleName import com.expedia.graphql.getTestSchemaConfigWithHooks import com.expedia.graphql.toSchema -import graphql.GraphQL -import graphql.schema.DataFetcher -import graphql.schema.DataFetchingEnvironment import graphql.schema.GraphQLFieldDefinition import graphql.schema.GraphQLObjectType import graphql.schema.GraphQLSchema @@ -17,7 +14,6 @@ import kotlin.reflect.KFunction import kotlin.reflect.KProperty import kotlin.reflect.KType import kotlin.reflect.full.createType -import kotlin.reflect.full.functions import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse @@ -128,34 +124,6 @@ class SchemaGeneratorHooksTest { assertEquals("SomeData!", hooks.lastSeenGeneratedType?.deepName) } - @Test - fun `calls hook before adding data fetcher`() { - class MockSchemaGeneratorHooks : SchemaGeneratorHooks { - var didGenerateDataFetcherCalled = false - var lastSeenFunction: KFunction<*>? = null - var lastReturnedDataFetcher: WrappingDataFetcher? = null - override fun didGenerateDataFetcher(function: KFunction<*>, dataFetcher: DataFetcher<*>): DataFetcher<*> { - lastSeenFunction = function - didGenerateDataFetcherCalled = true - val wrappingDataFetcher = WrappingDataFetcher(dataFetcher) - lastReturnedDataFetcher = wrappingDataFetcher - return wrappingDataFetcher - } - } - - val hooks = MockSchemaGeneratorHooks() - val schema = toSchema( - listOf(TopLevelObject(TestQuery())), - config = getTestSchemaConfigWithHooks(hooks) - ) - val graphQL = GraphQL.newGraphQL(schema).build() - val result = graphQL.execute("{ query { someNumber } }") - assertEquals(0, result.errors.count(), result.errors.toString()) - assertTrue(hooks.didGenerateDataFetcherCalled) - assertEquals(TestQuery::class.functions.find { it.name == "query" }, hooks.lastSeenFunction) - assertEquals(true, hooks.lastReturnedDataFetcher?.getCalled) - } - @Test fun `calls hook before adding query to schema`() { class MockSchemaGeneratorHooks : SchemaGeneratorHooks { @@ -234,12 +202,4 @@ class SchemaGeneratorHooksTest { } data class SomeData(val someNumber: Int) - - private class WrappingDataFetcher(private val dataFetcher: DataFetcher<*>) : DataFetcher { - var getCalled = false - override fun get(environment: DataFetchingEnvironment?): Any { - getCalled = true - return dataFetcher.get(environment) - } - } }