Skip to content

Commit c9242bf

Browse files
samuelAndalonSamuel Vazquez
and
Samuel Vazquez
committed
fix: register getter on runtime when field is of a parent type (#2091)
### 📝 Description Adding a missing use case when the registered resolver during schema generation is from a Parent class, while on runtime the resolve object could be an instance of a Child class, as a result, the resolver won't be available in the getters cache. This PR will attempt to resolve the getter using reflection and it will store it in the getters cache, if not getter found after checking the cache and checking the object class with reflection then a message log will be printed Co-authored-by: Samuel Vazquez <samvazquez@expediagroup.com>
1 parent cbab563 commit c9242bf

File tree

3 files changed

+48
-7
lines changed

3 files changed

+48
-7
lines changed

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -66,10 +66,12 @@ open class SimpleKotlinDataFetcherFactoryProvider : KotlinDataFetcherFactoryProv
6666
}
6767

6868
/**
69-
* [SimpleSingletonKotlinDataFetcherFactoryProvider] is a specialization of [SimpleKotlinDataFetcherFactoryProvider] that will provide a
69+
* [SimpleSingletonKotlinDataFetcherFactoryProvider] is a specialization of [SimpleKotlinDataFetcherFactoryProvider] that will provide
7070
* a [SingletonPropertyDataFetcher] that should be used to target property resolutions without allocating a DataFetcher per property
7171
*/
7272
open class SimpleSingletonKotlinDataFetcherFactoryProvider : SimpleKotlinDataFetcherFactoryProvider() {
7373
override fun propertyDataFetcherFactory(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> =
74-
SingletonPropertyDataFetcher.getFactoryAndRegister(kClass, kProperty)
74+
SingletonPropertyDataFetcher.factory.also {
75+
SingletonPropertyDataFetcher.register(kClass, kProperty)
76+
}
7577
}

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

+16-5
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,26 @@ import graphql.schema.DataFetcherFactory
55
import graphql.schema.DataFetchingEnvironment
66
import graphql.schema.GraphQLFieldDefinition
77
import graphql.schema.LightDataFetcher
8+
import org.slf4j.LoggerFactory
89
import java.util.concurrent.ConcurrentHashMap
910
import java.util.function.Supplier
1011
import kotlin.reflect.KClass
1112
import kotlin.reflect.KProperty
13+
import kotlin.reflect.full.memberProperties
1214

1315
/**
1416
* Singleton Property [DataFetcher] that stores references to underlying properties getters.
1517
*/
1618
internal object SingletonPropertyDataFetcher : LightDataFetcher<Any?> {
1719

18-
private val factory: DataFetcherFactory<Any?> = DataFetcherFactory<Any?> { SingletonPropertyDataFetcher }
19-
20+
private val logger = LoggerFactory.getLogger(SingletonPropertyDataFetcher::class.java)
21+
val factory: DataFetcherFactory<Any?> = DataFetcherFactory<Any?> { SingletonPropertyDataFetcher }
2022
private val getters: ConcurrentHashMap<String, KProperty.Getter<*>> = ConcurrentHashMap()
2123

22-
fun getFactoryAndRegister(kClass: KClass<*>, kProperty: KProperty<*>): DataFetcherFactory<Any?> {
24+
fun register(kClass: KClass<*>, kProperty: KProperty<*>) {
2325
getters.computeIfAbsent("${kClass.java.name}.${kProperty.name}") {
2426
kProperty.getter
2527
}
26-
return factory
2728
}
2829

2930
override fun get(
@@ -32,7 +33,17 @@ internal object SingletonPropertyDataFetcher : LightDataFetcher<Any?> {
3233
environmentSupplier: Supplier<DataFetchingEnvironment>
3334
): Any? =
3435
sourceObject?.let {
35-
getters["${sourceObject.javaClass.name}.${fieldDefinition.name}"]?.call(sourceObject)
36+
getters["${sourceObject.javaClass.name}.${fieldDefinition.name}"]?.call(sourceObject) ?: run {
37+
sourceObject::class.memberProperties
38+
.find { it.name == fieldDefinition.name }
39+
?.let { kProperty ->
40+
kProperty.getter.call(sourceObject).also {
41+
register(sourceObject::class, kProperty)
42+
}
43+
}
44+
} ?: run {
45+
logger.error("getter method not found: ${sourceObject.javaClass.name}.${fieldDefinition.name}")
46+
}
3647
}
3748

3849
override fun get(environment: DataFetchingEnvironment): Any? =

generator/graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/ToSchemaTest.kt

+28
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,22 @@ class ToSchemaTest {
8686
assertEquals(1, geo?.get("query")?.get("id"))
8787
}
8888

89+
@ParameterizedTest(name = "{index} ==> {1}")
90+
@MethodSource("toSchemaTestArguments")
91+
fun `SchemaGenerator generates resolvers for parent classes`(provider: KotlinDataFetcherFactoryProvider, name: String) {
92+
val schema = toSchema(
93+
queries = listOf(TopLevelObject(QueryObject())),
94+
mutations = listOf(TopLevelObject(MutationObject())),
95+
config = testSchemaConfig(provider)
96+
)
97+
val graphQL = GraphQL.newGraphQL(schema).build()
98+
99+
val result = graphQL.execute(" { range { start { day } end { day } } }")
100+
val data: Map<String, Map<String, Map<String, Any>>>? = result.getData()
101+
assertEquals(30, data?.get("range")?.get("start")?.get("day"))
102+
assertEquals(14, data?.get("range")?.get("end")?.get("day"))
103+
}
104+
89105
@ParameterizedTest(name = "{index} ==> {1}")
90106
@MethodSource("toSchemaTestArguments")
91107
fun `SchemaGenerator generates a simple GraphQL schema with default builder`(provider: KotlinDataFetcherFactoryProvider, name: String) {
@@ -405,9 +421,21 @@ class ToSchemaTest {
405421
fun foo(): String = "bar"
406422
}
407423

424+
open class ParentDate(val day: Int, val month: Int, val year: Int)
425+
426+
data class DateRange(val start: ParentDate, val end: ParentDate)
427+
428+
class ChildDate(day: Int, month: Int, year: Int) : ParentDate(day, month, year)
429+
408430
class QueryObject {
409431
@GraphQLDescription("A GraphQL query method")
410432
fun query(@GraphQLDescription("A GraphQL value") value: Int): Geography = Geography(value, GeoType.CITY, listOf())
433+
fun range(): DateRange {
434+
return DateRange(
435+
ChildDate(30, 5, 1992),
436+
ChildDate(14, 6, 1992),
437+
)
438+
}
411439
}
412440

413441
class QueryWithIgnored {

0 commit comments

Comments
 (0)