Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

BREAKING CHANGE(generator): update functionDataFetcherFactory to accept KClass #1732

Merged
merged 2 commits into from
Mar 29, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ class CustomDataFetcherFactoryProvider(
private val applicationContext: ApplicationContext
) : SimpleKotlinDataFetcherFactoryProvider() {

override fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>) = DataFetcherFactory {
override fun functionDataFetcherFactory(target: Any?, kClass: KClass<*>, kFunction: KFunction<*>) = DataFetcherFactory {
CustomFunctionDataFetcher(
target = target,
fn = kFunction,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Expedia, Inc
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -32,9 +32,10 @@ interface KotlinDataFetcherFactoryProvider {
*
* @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 kClass parent class that contains this property
* @param kFunction Kotlin function being invoked
*/
fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>): DataFetcherFactory<Any?>
fun functionDataFetcherFactory(target: Any?, kClass: KClass<*>, kFunction: KFunction<*>): DataFetcherFactory<Any?>

/**
* Retrieve an instance of [DataFetcherFactory] that will be used to resolve target property.
Expand All @@ -51,7 +52,7 @@ interface KotlinDataFetcherFactoryProvider {
*/
open class SimpleKotlinDataFetcherFactoryProvider : KotlinDataFetcherFactoryProvider {

override fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>) = DataFetcherFactory {
override fun functionDataFetcherFactory(target: Any?, kClass: KClass<*>, kFunction: KFunction<*>) = DataFetcherFactory {
FunctionDataFetcher(
target = target,
fn = kFunction
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Expedia, Inc
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -28,9 +28,10 @@ import graphql.introspection.Introspection.DirectiveLocation
import graphql.schema.FieldCoordinates
import graphql.schema.GraphQLFieldDefinition
import graphql.schema.GraphQLOutputType
import kotlin.reflect.KClass
import kotlin.reflect.KFunction

internal fun generateFunction(generator: SchemaGenerator, fn: KFunction<*>, parentName: String, target: Any? = null, abstract: Boolean = false): GraphQLFieldDefinition {
internal fun generateFunction(generator: SchemaGenerator, kClass: KClass<*>, fn: KFunction<*>, parentName: String, target: Any? = null, abstract: Boolean = false): GraphQLFieldDefinition {
val builder = GraphQLFieldDefinition.newFieldDefinition()
val functionName = fn.getFunctionName()
builder.name(functionName)
Expand All @@ -57,7 +58,7 @@ internal fun generateFunction(generator: SchemaGenerator, fn: KFunction<*>, pare
val coordinates = FieldCoordinates.coordinates(parentName, functionName)

if (!abstract) {
val dataFetcherFactory = generator.config.dataFetcherFactoryProvider.functionDataFetcherFactory(target = target, kFunction = fn)
val dataFetcherFactory = generator.config.dataFetcherFactoryProvider.functionDataFetcherFactory(target = target, kClass = kClass, kFunction = fn)
generator.codeRegistry.dataFetcher(coordinates, dataFetcherFactory)
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Expedia, Inc
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -58,7 +58,7 @@ internal fun generateInterface(generator: SchemaGenerator, kClass: KClass<*>): G
.forEach { builder.field(generateProperty(generator, it, kClass)) }

kClass.getValidFunctions(generator.config.hooks)
.forEach { builder.field(generateFunction(generator, it, kClass.getSimpleName(), null, abstract = true)) }
.forEach { builder.field(generateFunction(generator, kClass, it, kClass.getSimpleName(), null, abstract = true)) }

generator.classScanner.getSubTypesOf(kClass)
.filter { generator.config.hooks.isValidAdditionalType(it, inputType = false) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Expedia, Inc
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -45,7 +45,7 @@ internal fun generateMutations(generator: SchemaGenerator, mutations: List<TopLe

mutation.kClass.getValidFunctions(generator.config.hooks)
.forEach {
val function = generateFunction(generator, it, generator.config.topLevelNames.mutation, mutation.obj)
val function = generateFunction(generator, mutation.kClass, it, generator.config.topLevelNames.mutation, mutation.obj)
val functionFromHook = generator.config.hooks.didGenerateMutationField(mutation.kClass, it, function)
if (mutationBuilder.hasField(functionFromHook.name)) {
throw ConflictingFieldsException("Mutation(class: ${mutation.kClass})", it.name)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Expedia, Inc
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -61,7 +61,7 @@ internal fun generateObject(generator: SchemaGenerator, kClass: KClass<*>): Grap
.forEach { builder.field(generateProperty(generator, it, kClass)) }

kClass.getValidFunctions(generator.config.hooks)
.forEach { builder.field(generateFunction(generator, it, name)) }
.forEach { builder.field(generateFunction(generator, kClass, it, name)) }

return generator.config.hooks.onRewireGraphQLType(builder.build(), null, generator.codeRegistry).safeCast()
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ internal fun generateQueries(generator: SchemaGenerator, queries: List<TopLevelO

query.kClass.getValidFunctions(generator.config.hooks)
.forEach {
val function = generateFunction(generator, it, generator.config.topLevelNames.query, query.obj)
val function = generateFunction(generator, query.kClass, it, generator.config.topLevelNames.query, query.obj)
val functionFromHook = generator.config.hooks.didGenerateQueryField(query.kClass, it, function)
if (queryBuilder.hasField(functionFromHook.name)) {
throw ConflictingFieldsException("Query(class: ${query.kClass})", it.name)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Expedia, Inc
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -50,7 +50,7 @@ internal fun generateSubscriptions(generator: SchemaGenerator, subscriptions: Li
throw InvalidSubscriptionTypeException(kClass, it)
}

val function = generateFunction(generator, it, generator.config.topLevelNames.subscription, subscription.obj)
val function = generateFunction(generator, kClass, it, generator.config.topLevelNames.subscription, subscription.obj)
val functionFromHook = generator.config.hooks.didGenerateSubscriptionField(kClass, it, function)
if (subscriptionBuilder.hasField(functionFromHook.name)) {
throw ConflictingFieldsException("Subscription(class: ${subscription.kClass})", it.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,22 +109,22 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `Function description can be set`() {
val kFunction = Happy::littleTrees
val result = generateFunction(generator, kFunction, "Query", target = null, abstract = false)
val result = generateFunction(generator, Happy::class, kFunction, "Query", target = null, abstract = false)
assertEquals("By bob", result.description)
}

@Test
fun `Function names can be changed`() {
val kFunction = Happy::originalName
val result = generateFunction(generator, kFunction, "Query", target = null, abstract = false)
val result = generateFunction(generator, Happy::class, kFunction, "Query", target = null, abstract = false)
assertEquals("renamedFunction", result.name)
}

@Test
fun `Functions can be deprecated`() {
@Suppress("DEPRECATION")
val kFunction = Happy::sketch
val result = generateFunction(generator, kFunction, "Query", target = null, abstract = false)
val result = generateFunction(generator, Happy::class, kFunction, "Query", target = null, abstract = false)
assertTrue(result.isDeprecated)
assertEquals("Should paint instead, replace with paint", result.deprecationReason)

Expand All @@ -136,7 +136,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `test custom directive on function`() {
val kFunction = Happy::littleTrees
val result = generateFunction(generator, kFunction, "Query", target = null, abstract = false)
val result = generateFunction(generator, Happy::class, kFunction, "Query", target = null, abstract = false)

assertEquals(1, result.appliedDirectives.size)
val appliedDirective = result.appliedDirectives[0]
Expand All @@ -155,7 +155,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `test ignored parameter`() {
val kFunction = Happy::ignoredParameter
val result = generateFunction(generator, kFunction, "Query", target = null, abstract = false)
val result = generateFunction(generator, Happy::class, kFunction, "Query", target = null, abstract = false)

assertTrue(result.directives.isEmpty())
assertEquals(expected = 1, actual = result.arguments.size)
Expand All @@ -166,7 +166,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `non-abstract function`() {
val kFunction = MyInterface::printMessage
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = MyInterface::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertEquals(expected = 1, actual = result.arguments.size)
assertTrue(generator.codeRegistry.getDataFetcher(FieldCoordinates.coordinates("Query", kFunction.name), result) is FunctionDataFetcher)
Expand All @@ -175,23 +175,23 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `abstract function`() {
val kFunction = MyInterface::printMessage
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = true)
val result = generateFunction(generator, kClass = MyInterface::class, fn = kFunction, parentName = "Query", target = null, abstract = true)

assertEquals(expected = 1, actual = result.arguments.size)
}

@Test
fun `abstract function with target`() {
val kFunction = MyInterface::printMessage
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = MyImplementation(), abstract = true)
val result = generateFunction(generator, kClass = MyInterface::class, fn = kFunction, parentName = "Query", target = MyImplementation(), abstract = true)

assertEquals(expected = 1, actual = result.arguments.size)
}

@Test
fun `publisher return type is valid`() {
val kFunction = Happy::publisher
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertEquals(expected = 1, actual = result.arguments.size)
assertEquals(GraphQLInt, (result.type as? GraphQLNonNull)?.wrappedType)
Expand All @@ -200,7 +200,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `a return type that implements Publisher is valid`() {
val kFunction = Happy::flowable
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertEquals(expected = 1, actual = result.arguments.size)
assertEquals(GraphQLInt, (result.type as? GraphQLNonNull)?.wrappedType)
Expand All @@ -209,7 +209,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `completable future return type is valid`() {
val kFunction = Happy::completableFuture
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertEquals(expected = 1, actual = result.arguments.size)
assertEquals(GraphQLInt, (result.type as? GraphQLNonNull)?.wrappedType)
Expand All @@ -218,7 +218,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `DataFetchingEnvironment argument type is ignored`() {
val kFunction = Happy::dataFetchingEnvironment
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertEquals(expected = 0, actual = result.arguments.size)
assertEquals(GraphQLString, (result.type as? GraphQLNonNull)?.wrappedType)
Expand All @@ -227,15 +227,15 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `DataFetcherResult return type is valid and unwrapped in the schema`() {
val kFunction = Happy::dataFetcherResult
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertEquals(GraphQLString, (result.type as? GraphQLNonNull)?.wrappedType)
}

@Test
fun `DataFetcherResult of a List is valid and unwrapped in the schema`() {
val kFunction = Happy::listDataFetcherResult
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertTrue(result.type is GraphQLNonNull)
val listType = GraphQLTypeUtil.unwrapNonNull(result.type)
Expand All @@ -247,7 +247,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `DataFetcherResult of a nullable List is valid and unwrapped in the schema`() {
val kFunction = Happy::nullableListDataFetcherResult
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

val listType = result.type
assertTrue(listType is GraphQLList)
Expand All @@ -260,14 +260,14 @@ class GenerateFunctionTest : TypeTestHelper() {
val kFunction = Happy::dataFetcherCompletableFutureResult

assertFailsWith(TypeNotSupportedException::class) {
generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)
}
}

@Test
fun `CompletableFuture of a DataFetcherResult is valid and unwrapped in the schema`() {
val kFunction = Happy::completableFutureDataFetcherResult
val result = generateFunction(generator, fn = kFunction, parentName = "Query", target = null, abstract = false)
val result = generateFunction(generator, kClass = Happy::class, fn = kFunction, parentName = "Query", target = null, abstract = false)

assertTrue(result.type is GraphQLNonNull)
val stringType = GraphQLTypeUtil.unwrapNonNull(result.type)
Expand All @@ -277,9 +277,9 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `Nested Self referencing object returns non null`() {
val kInterfaceFunction = MyInterface::nestedReturnType
val kInterfaceResult = generateFunction(generator, fn = kInterfaceFunction, parentName = "Query", target = null, abstract = false)
val kInterfaceResult = generateFunction(generator, kClass = MyInterface::class, fn = kInterfaceFunction, parentName = "Query", target = null, abstract = false)
val kImplFunction = MyImplementation::nestedReturnType
val implResult = generateFunction(generator, fn = kImplFunction, parentName = "Query", target = null, abstract = false)
val implResult = generateFunction(generator, kClass = MyImplementation::class, fn = kImplFunction, parentName = "Query", target = null, abstract = false)

assertTrue(implResult.type is GraphQLNonNull)
val resultType = kInterfaceResult.type as? GraphQLNonNull
Expand All @@ -290,7 +290,7 @@ class GenerateFunctionTest : TypeTestHelper() {
@Test
fun `function can return GraphQL ID scalar`() {
val kFunction = Happy::randomId
val result = generateFunction(generator, kFunction, "Query", target = null, abstract = false)
val result = generateFunction(generator, Happy::class, kFunction, "Query", target = null, abstract = false)

assertEquals("randomId", result.name)
val returnType = GraphQLTypeUtil.unwrapAll(result.type)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2022 Expedia, Inc
* Copyright 2023 Expedia, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -19,15 +19,16 @@ package com.expediagroup.graphql.server.spring.execution
import com.expediagroup.graphql.generator.execution.SimpleKotlinDataFetcherFactoryProvider
import graphql.schema.DataFetcherFactory
import org.springframework.context.ApplicationContext
import kotlin.reflect.KClass
import kotlin.reflect.KFunction

/**
* This provides a wrapper around the [SimpleKotlinDataFetcherFactoryProvider] to call the [SpringDataFetcher] on functions.
* This allows you to use Spring beans as function arugments and they will be populated by the data fetcher.
*/
class SpringKotlinDataFetcherFactoryProvider(
open class SpringKotlinDataFetcherFactoryProvider(
private val applicationContext: ApplicationContext
) : SimpleKotlinDataFetcherFactoryProvider() {
override fun functionDataFetcherFactory(target: Any?, kFunction: KFunction<*>): DataFetcherFactory<Any?> =
override fun functionDataFetcherFactory(target: Any?, kClass: KClass<*>, kFunction: KFunction<*>): DataFetcherFactory<Any?> =
DataFetcherFactory { SpringDataFetcher(target, kFunction, applicationContext) }
}