Skip to content

Commit 7de847b

Browse files
authored
feat: open FunctionDataFetcher methods for extension (#582)
Open the internal methods to the protected scope so that we can more easily override specific methods to provide more features. Internally to Expedia Group we have the need to provide a CustomFunctionDataFetcher that overrides the get method. However we don't want to have to reimplement all the other methods as well that covert the classes and parse the names. Right now we are actually missing out on using the new feature to use List over array for input types. This could be fixed internally but it is already done in open source so we would like to take advantage of that
1 parent 4f7e8ef commit 7de847b

File tree

1 file changed

+51
-12
lines changed

1 file changed

+51
-12
lines changed

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/execution/FunctionDataFetcher.kt

Lines changed: 51 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,14 @@ import com.fasterxml.jackson.databind.ObjectMapper
2424
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
2525
import graphql.schema.DataFetcher
2626
import graphql.schema.DataFetchingEnvironment
27+
import kotlinx.coroutines.CoroutineStart
2728
import kotlinx.coroutines.GlobalScope
2829
import kotlinx.coroutines.async
2930
import kotlinx.coroutines.future.asCompletableFuture
3031
import java.lang.reflect.InvocationTargetException
3132
import java.util.concurrent.CompletableFuture
33+
import kotlin.coroutines.CoroutineContext
34+
import kotlin.coroutines.EmptyCoroutineContext
3235
import kotlin.reflect.KFunction
3336
import kotlin.reflect.KParameter
3437
import kotlin.reflect.full.callSuspend
@@ -42,19 +45,21 @@ import kotlin.reflect.full.valueParameters
4245
* @param fn The Kotlin function being invoked
4346
* @param objectMapper Jackson ObjectMapper that will be used to deserialize environment arguments to the expected function arguments
4447
*/
48+
@Suppress("Detekt.SpreadOperator")
4549
open class FunctionDataFetcher(
4650
private val target: Any?,
4751
private val fn: KFunction<*>,
4852
private val objectMapper: ObjectMapper = jacksonObjectMapper()
4953
) : DataFetcher<Any> {
5054

55+
/**
56+
* Invoke a suspend function or blocking function, passing in the [target] if not null or default to using the source from the environment.
57+
*/
5158
override fun get(environment: DataFetchingEnvironment): Any? {
5259
val instance = target ?: environment.getSource<Any>()
5360

5461
return instance?.let {
55-
val parameterValues = fn.valueParameters
56-
.map { param -> mapParameterToValue(param, environment) }
57-
.toTypedArray()
62+
val parameterValues = getParameterValues(fn, environment)
5863

5964
if (fn.isSuspend) {
6065
runSuspendingFunction(it, parameterValues)
@@ -64,36 +69,70 @@ open class FunctionDataFetcher(
6469
}
6570
}
6671

67-
private fun mapParameterToValue(param: KParameter, environment: DataFetchingEnvironment): Any? =
72+
/**
73+
* Iterate over all the function parameters and map them to the proper input values from the environment
74+
*/
75+
protected open fun getParameterValues(fn: KFunction<*>, environment: DataFetchingEnvironment): Array<Any?> = fn.valueParameters
76+
.map { param -> mapParameterToValue(param, environment) }
77+
.toTypedArray()
78+
79+
/**
80+
* Retreives the provided parameter value in the operation input to pass to the function to execute.
81+
* If the parameter is of a special type then we do not read the input and instead just pass on that value.
82+
*
83+
* The special values include:
84+
* - If the parameter is annotated with [com.expediagroup.graphql.annotations.GraphQLContext],
85+
* then return the environment context
86+
*
87+
* - The entire environment is returned if the parameter is of type [DataFetchingEnvironment]
88+
*/
89+
protected open fun mapParameterToValue(param: KParameter, environment: DataFetchingEnvironment): Any? =
6890
when {
6991
param.isGraphQLContext() -> environment.getContext()
7092
param.isDataFetchingEnvironment() -> environment
7193
else -> convertParameterValue(param, environment)
7294
}
7395

74-
private fun convertParameterValue(param: KParameter, environment: DataFetchingEnvironment): Any? {
96+
/**
97+
* Called to convert the generic input object to the parameter class.
98+
*
99+
* This is currently achieved by using a Jackson [ObjectMapper].
100+
*/
101+
protected open fun convertParameterValue(param: KParameter, environment: DataFetchingEnvironment): Any? {
75102
val name = param.getName()
76103
val klazz = param.javaTypeClass()
77104
val argument = environment.arguments[name]
78105

79106
return objectMapper.convertValue(argument, klazz)
80107
}
81108

82-
@Suppress("Detekt.SpreadOperator")
83-
private fun runSuspendingFunction(it: Any, parameterValues: Array<Any?>): CompletableFuture<Any?> {
84-
return GlobalScope.async {
109+
/**
110+
* Once all parameters values are properly converted, this function will be called to run a suspendable function.
111+
* If you need to override the exception handling you can override the entire method.
112+
* You can also call it from [get] with different values to override the default corotuine context or start parameter.
113+
*/
114+
protected open fun runSuspendingFunction(
115+
instance: Any,
116+
parameterValues: Array<Any?>,
117+
coroutineContext: CoroutineContext = EmptyCoroutineContext,
118+
coroutineStart: CoroutineStart = CoroutineStart.DEFAULT
119+
): CompletableFuture<Any?> {
120+
return GlobalScope.async(context = coroutineContext, start = coroutineStart) {
85121
try {
86-
fn.callSuspend(it, *parameterValues)
122+
fn.callSuspend(instance, *parameterValues)
87123
} catch (exception: InvocationTargetException) {
88124
throw exception.cause ?: exception
89125
}
90126
}.asCompletableFuture()
91127
}
92128

93-
@Suppress("Detekt.SpreadOperator")
94-
private fun runBlockingFunction(it: Any, parameterValues: Array<Any?>): Any? {
129+
/**
130+
* Once all parameters values are properly converted, this function will be called to run a simple blocking function.
131+
* If you need to override the exception handling you can override this method.
132+
*/
133+
protected open fun runBlockingFunction(instance: Any, parameterValues: Array<Any?>): Any? {
95134
try {
96-
return fn.call(it, *parameterValues)
135+
return fn.call(instance, *parameterValues)
97136
} catch (exception: InvocationTargetException) {
98137
throw exception.cause ?: exception
99138
}

0 commit comments

Comments
 (0)