@@ -2,7 +2,6 @@ package graphql.kickstart.tools.resolver
22
33import graphql.GraphQLContext
44import graphql.Scalars
5- import graphql.kickstart.tools.GraphQLSubscriptionResolver
65import graphql.kickstart.tools.ResolverInfo
76import graphql.kickstart.tools.RootResolverInfo
87import graphql.kickstart.tools.SchemaParserOptions
@@ -13,10 +12,10 @@ import graphql.schema.DataFetchingEnvironment
1312import kotlinx.coroutines.channels.ReceiveChannel
1413import org.apache.commons.lang3.ClassUtils
1514import org.apache.commons.lang3.reflect.FieldUtils
15+ import org.apache.commons.lang3.reflect.TypeUtils
1616import org.reactivestreams.Publisher
1717import org.slf4j.LoggerFactory
1818import java.lang.reflect.*
19- import java.util.concurrent.CompletableFuture
2019import kotlin.reflect.full.valueParameters
2120import kotlin.reflect.jvm.javaType
2221import kotlin.reflect.jvm.kotlinFunction
@@ -124,27 +123,27 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
124123 // to avoid issues with duplicate method declarations
125124 .filter { it.declaringClass != Object ::class .java }
126125 // subscription resolvers must return a publisher
127- .filter { search.source !is GraphQLSubscriptionResolver || resolverMethodReturnsPublisher(it) }
126+ .filter { ! search.isSubscription || resolverMethodReturnsPublisher(it) }
128127 .toList()
129128 }
130129
131130 private fun resolverMethodReturnsPublisher (method : Method ) =
132- method.returnType.isAssignableFrom(Publisher ::class .java)
131+ // suspend functions and unbounded type variables are erased to Object, so the actual return type is unknown here
132+ method.returnType == Any ::class .java
133+ || Publisher ::class .java.isAssignableFrom(method.returnType)
133134 || resolverMethodReturnsPublisherFuture(method)
134135 || receiveChannelToPublisherWrapper(method)
135136
136137 private fun resolverMethodReturnsPublisherFuture (method : Method ) =
137- method.returnType.isAssignableFrom(CompletableFuture ::class .java)
138- && method.genericReturnType is ParameterizedType
139- && (method.genericReturnType as ParameterizedType ).actualTypeArguments
140- .any {
141- it is ParameterizedType && it.unwrap().isAssignableFrom(Publisher ::class .java)
142- }
138+ method.genericReturnType.futureValueType()
139+ ?.let { if (it is WildcardType ) it.upperBounds.first() else it }
140+ ?.let { TypeUtils .isAssignable(it, Publisher ::class .java) } == true
143141
144142 private fun receiveChannelToPublisherWrapper (method : Method ) =
145- method.returnType.isAssignableFrom( ReceiveChannel ::class .java)
143+ ReceiveChannel ::class .java.isAssignableFrom(method.returnType )
146144 && options.genericWrappers.any { wrapper ->
147145 val isReceiveChannelWrapper = wrapper.type == method.returnType
146+ // lambdas are erased to return Object, so this only checks that the transformer can return a Publisher
148147 val hasPublisherTransformer = wrapper
149148 .transformer.javaClass
150149 .declaredMethods
@@ -199,7 +198,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
199198
200199 searches.forEach { search ->
201200 signatures.addAll(getMissingMethodSignatures(field, search, isBoolean, scannedProperties))
202- isSubscription = isSubscription || search.source is GraphQLSubscriptionResolver
201+ isSubscription = isSubscription || search.isSubscription
203202 }
204203
205204 val sourceName = field.sourceLocation?.sourceName ? : " <unknown>"
@@ -241,7 +240,9 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
241240 val resolverInfo : ResolverInfo ,
242241 val source : Any? ,
243242 val requiredFirstParameterType : Class <* >? = null
244- )
243+ ) {
244+ val isSubscription get() = resolverInfo is RootResolverInfo && resolverInfo.isSubscription
245+ }
245246}
246247
247248internal class FieldResolverError (msg : String ) : RuntimeException(msg)
0 commit comments