Skip to content
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 @@ -130,9 +130,9 @@ internal class MethodFieldResolver(
}

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

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

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

private fun getResolvedReturnType(): JavaType =
getReturnType().let { typeResolver.resolve(if (search.isSubscription) it.asPublisher() else it) }
typeResolver.resolve(if (search.isSubscription) returnType.asPublisher() else returnType)

private fun getIndexOffset(): Int {
return if (resolverInfo is DataClassTypeResolverInfo && !method.declaringClass.isAssignableFrom(resolverInfo.dataClassType)) {
Expand Down
16 changes: 13 additions & 3 deletions src/test/kotlin/graphql/kickstart/tools/ReactiveTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class ReactiveTest {
item: Item
boxedItem: Item
search: SearchResult
inheritedSearch: SearchResult
}

union SearchResult = Item
Expand All @@ -151,16 +152,21 @@ class ReactiveTest {
search {
... on Item { id }
}
inheritedSearch {
... on Item { id }
}
}
""")

assertEquals(result.getData(), mapOf("item" to null, "boxedItem" to null, "search" to null))
assertEquals(result.getData(), mapOf("item" to null, "boxedItem" to null, "search" to null, "inheritedSearch" to null))
assertEquals(result.errorMessagesByPath(), mapOf(
"item" to "Resolver method '${ItemQuery::class.java.name}.item' resolved to a java.util.concurrent.FutureTask, " +
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead.",
"boxedItem" to "Resolver method '${FutureItemQuery::class.java.name}.boxedItem' resolved to a java.util.concurrent.FutureTask, " +
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead.",
"search" to "Resolver method '${FutureItemQuery::class.java.name}.search' resolved to a java.util.concurrent.FutureTask, " +
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead.",
"inheritedSearch" to "Resolver method '${ItemQuery::class.java.name}.inheritedSearch' resolved to a java.util.concurrent.FutureTask, " +
"which graphql-java can't wait on. Return a CompletionStage, such as a CompletableFuture, from the method or its generic wrapper transformer instead."
))
}
Expand Down Expand Up @@ -232,11 +238,15 @@ class ReactiveTest {
fun nullItem(): Future<Item?> = FutureTask<Item?> { null }.also { it.run() }
}

open class ItemQuery<R>(private val item: () -> R) {
open class ItemQuery<R, S>(private val item: () -> R, private val search: () -> S) {
fun item(): R = item.invoke()
fun inheritedSearch(): S = search.invoke()
}

private class FutureItemQuery : ItemQuery<Future<Item>>({ FutureTask { Item(1) }.also { it.run() } }), GraphQLQueryResolver {
private class FutureItemQuery : ItemQuery<Future<Item>, Future<Any>>(
{ FutureTask { Item(1) }.also { it.run() } },
{ FutureTask<Any> { Item(4) }.also { it.run() } }
), GraphQLQueryResolver {
fun boxedItem(): Box<Item> = Box(Item(2))
fun search(): Future<Any> = FutureTask<Any> { Item(3) }.also { it.run() }
}
Expand Down
9 changes: 7 additions & 2 deletions src/test/kotlin/graphql/kickstart/tools/SchemaParserTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1297,7 +1297,7 @@ class SchemaParserTest {

@Test
fun `parser should accept subscription resolvers returning publisher subtypes`() {
class Subscription : OverriddenPublisherSubscription {
class Subscription : InheritedPublisherSubscription<SingleEventPublisher>(SingleEventPublisher("onInheritedPublisher")), OverriddenPublisherSubscription {
fun onCustomPublisher(): EventPublisher<Event> = SingleEventPublisher("onCustomPublisher")
fun onConcretePublisher() = SingleEventPublisher("onConcretePublisher")
override fun onOverriddenPublisher() = SingleEventPublisher("onOverriddenPublisher")
Expand All @@ -1314,6 +1314,7 @@ class SchemaParserTest {
onCustomPublisher: Event!
onConcretePublisher: Event!
onOverriddenPublisher: Event!
onInheritedPublisher: Event!
}

type Event {
Expand All @@ -1330,7 +1331,7 @@ class SchemaParserTest {
.assertEveryFieldResolves()
val gql = GraphQL.newGraphQL(schema).build()

listOf("onCustomPublisher", "onConcretePublisher", "onOverriddenPublisher").forEach { field ->
listOf("onCustomPublisher", "onConcretePublisher", "onOverriddenPublisher", "onInheritedPublisher").forEach { field ->
assertEquals(gql.firstSubscriptionEvent("subscription { $field { name } }"), mapOf(field to mapOf("name" to field)))
}
}
Expand Down Expand Up @@ -1435,4 +1436,8 @@ class SchemaParserTest {
interface OverriddenPublisherSubscription : GraphQLSubscriptionResolver {
fun onOverriddenPublisher(): Publisher<Event>
}

open class InheritedPublisherSubscription<P>(private val publisher: P) {
fun onInheritedPublisher(): P = publisher
}
}
Loading