Skip to content

Commit 6208907

Browse files
committed
Resolve type variables in resolver return types
1 parent d4232ba commit 6208907

3 files changed

Lines changed: 28 additions & 10 deletions

File tree

‎src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,9 @@ internal class MethodFieldResolver(
130130
}
131131

132132
// the class the return type unwraps to, so a value that happens to implement Future isn't mistaken for a wrapper.
133-
// Object (e.g. for a union) can't tell them apart, so then every future of a method declared to return Future is checked
133+
// Object (e.g. for a union) can't tell them apart, so then every future of a method whose return type is Future is checked
134134
val valueClass = (TypeUtils.getRawType(getResolvedReturnType(), null) ?: Any::class.java)
135-
.takeUnless { it == Any::class.java && TypeUtils.getRawType(getReturnType(), null) == Future::class.java }
135+
.takeUnless { it == Any::class.java && TypeUtils.getRawType(returnType, null) == Future::class.java }
136136

137137
return if (numberOfParameters > 0 || isSuspendFunction) {
138138
// requires arguments and environment or is a suspend function
@@ -180,15 +180,18 @@ internal class MethodFieldResolver(
180180
?.let { TypeUtils.parameterize(Publisher::class.java, it) }
181181
?: this
182182

183-
private fun getReturnType(): JavaType =
184-
try {
183+
// the declared return type with type variables resolved, e.g. Future<Item> for get(): T in a subclass of Base<Future<Item>>
184+
private val returnType: JavaType by lazy {
185+
val declaredType = try {
185186
method.kotlinFunction?.returnType?.javaType ?: method.genericReturnType
186187
} catch (e: InternalError) {
187188
method.genericReturnType
188189
}
190+
typeResolver.resolveTypeVariables(declaredType)
191+
}
189192

190193
private fun getResolvedReturnType(): JavaType =
191-
getReturnType().let { typeResolver.resolve(if (search.isSubscription) it.asPublisher() else it) }
194+
typeResolver.resolve(if (search.isSubscription) returnType.asPublisher() else returnType)
192195

193196
private fun getIndexOffset(): Int {
194197
return if (resolverInfo is DataClassTypeResolverInfo && !method.declaringClass.isAssignableFrom(resolverInfo.dataClassType)) {

‎src/test/kotlin/graphql/kickstart/tools/ReactiveTest.kt‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class ReactiveTest {
127127
item: Item
128128
boxedItem: Item
129129
search: SearchResult
130+
inheritedSearch: SearchResult
130131
}
131132
132133
union SearchResult = Item
@@ -151,16 +152,21 @@ class ReactiveTest {
151152
search {
152153
... on Item { id }
153154
}
155+
inheritedSearch {
156+
... on Item { id }
157+
}
154158
}
155159
""")
156160

157-
assertEquals(result.getData(), mapOf("item" to null, "boxedItem" to null, "search" to null))
161+
assertEquals(result.getData(), mapOf("item" to null, "boxedItem" to null, "search" to null, "inheritedSearch" to null))
158162
assertEquals(result.errorMessagesByPath(), mapOf(
159163
"item" to "Resolver method '${ItemQuery::class.java.name}.item' resolved to a java.util.concurrent.FutureTask, " +
160164
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead.",
161165
"boxedItem" to "Resolver method '${FutureItemQuery::class.java.name}.boxedItem' resolved to a java.util.concurrent.FutureTask, " +
162166
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead.",
163167
"search" to "Resolver method '${FutureItemQuery::class.java.name}.search' resolved to a java.util.concurrent.FutureTask, " +
168+
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead.",
169+
"inheritedSearch" to "Resolver method '${ItemQuery::class.java.name}.inheritedSearch' resolved to a java.util.concurrent.FutureTask, " +
164170
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead."
165171
))
166172
}
@@ -232,11 +238,15 @@ class ReactiveTest {
232238
fun nullItem(): Future<Item?> = FutureTask<Item?> { null }.also { it.run() }
233239
}
234240

235-
open class ItemQuery<R>(private val item: () -> R) {
241+
open class ItemQuery<R, S>(private val item: () -> R, private val search: () -> S) {
236242
fun item(): R = item.invoke()
243+
fun inheritedSearch(): S = search.invoke()
237244
}
238245

239-
private class FutureItemQuery : ItemQuery<Future<Item>>({ FutureTask { Item(1) }.also { it.run() } }), GraphQLQueryResolver {
246+
private class FutureItemQuery : ItemQuery<Future<Item>, Future<Any>>(
247+
{ FutureTask { Item(1) }.also { it.run() } },
248+
{ FutureTask<Any> { Item(4) }.also { it.run() } }
249+
), GraphQLQueryResolver {
240250
fun boxedItem(): Box<Item> = Box(Item(2))
241251
fun search(): Future<Any> = FutureTask<Any> { Item(3) }.also { it.run() }
242252
}

‎src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1297,7 +1297,7 @@ class SchemaParserTest {
12971297

12981298
@Test
12991299
fun `parser should accept subscription resolvers returning publisher subtypes`() {
1300-
class Subscription : OverriddenPublisherSubscription {
1300+
class Subscription : InheritedPublisherSubscription<SingleEventPublisher>(SingleEventPublisher("onInheritedPublisher")), OverriddenPublisherSubscription {
13011301
fun onCustomPublisher(): EventPublisher<Event> = SingleEventPublisher("onCustomPublisher")
13021302
fun onConcretePublisher() = SingleEventPublisher("onConcretePublisher")
13031303
override fun onOverriddenPublisher() = SingleEventPublisher("onOverriddenPublisher")
@@ -1314,6 +1314,7 @@ class SchemaParserTest {
13141314
onCustomPublisher: Event!
13151315
onConcretePublisher: Event!
13161316
onOverriddenPublisher: Event!
1317+
onInheritedPublisher: Event!
13171318
}
13181319
13191320
type Event {
@@ -1330,7 +1331,7 @@ class SchemaParserTest {
13301331
.assertEveryFieldResolves()
13311332
val gql = GraphQL.newGraphQL(schema).build()
13321333

1333-
listOf("onCustomPublisher", "onConcretePublisher", "onOverriddenPublisher").forEach { field ->
1334+
listOf("onCustomPublisher", "onConcretePublisher", "onOverriddenPublisher", "onInheritedPublisher").forEach { field ->
13341335
assertEquals(gql.firstSubscriptionEvent("subscription { $field { name } }"), mapOf(field to mapOf("name" to field)))
13351336
}
13361337
}
@@ -1435,4 +1436,8 @@ class SchemaParserTest {
14351436
interface OverriddenPublisherSubscription : GraphQLSubscriptionResolver {
14361437
fun onOverriddenPublisher(): Publisher<Event>
14371438
}
1439+
1440+
open class InheritedPublisherSubscription<P>(private val publisher: P) {
1441+
fun onInheritedPublisher(): P = publisher
1442+
}
14381443
}

0 commit comments

Comments
 (0)