Repository: graphql-java-kickstart/graphql-java-tools
Pull request #348 (2020-01-01 16:07:19)

Ignore proxies when scanning for field resolvers.
I use java proxies to auto generate resolvers.
But the field resolver complain about the missing generic information.

The problem is, that java proxies declares all interface methods but all generic types are erased.
So the scanning ends with an `Unable to match type definition (...) with java type (interface java.lang.Iterable): Java class is not a List or generic type information was lost: interface java.lang.Iterable` error.



Patch:
diff --git a/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt b/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt
index 8ca3a82..b16b5e9 100644
--- a/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt
+++ b/src/main/kotlin/com/coxautodev/graphql/tools/FieldResolverScanner.kt
@@ -9,6 +9,7 @@ import org.apache.commons.lang3.reflect.FieldUtils
 import org.slf4j.LoggerFactory
 import java.lang.reflect.Modifier
 import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Proxy
 import java.lang.reflect.Type
 import kotlin.reflect.full.valueParameters
 import kotlin.reflect.jvm.javaType
@@ -25,7 +26,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
         private val log = LoggerFactory.getLogger(FieldResolverScanner::class.java)
 
         fun getAllMethods(type: JavaType) =
-                (type.unwrap().declaredMethods.toList()
+                (type.unwrap().declaredNonProxyMethods.toList()
                         + ClassUtils.getAllInterfaces(type.unwrap()).flatMap { it.methods.toList() }
                         + ClassUtils.getAllSuperclasses(type.unwrap()).flatMap { it.methods.toList() })
                         .asSequence()
diff --git a/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt b/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt
index 2efd256..4677ab8 100644
--- a/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt
+++ b/src/main/kotlin/com/coxautodev/graphql/tools/Utils.kt
@@ -8,6 +8,7 @@ import graphql.language.ObjectTypeExtensionDefinition
 import graphql.language.Type
 import java.lang.reflect.Method
 import java.lang.reflect.ParameterizedType
+import java.lang.reflect.Proxy
 
 /**
  * @author Andrew Potter
@@ -16,6 +17,7 @@ import java.lang.reflect.ParameterizedType
 internal typealias GraphQLRootResolver = GraphQLResolver<Void>
 
 internal typealias JavaType = java.lang.reflect.Type
+internal typealias JavaMethod = java.lang.reflect.Method
 internal typealias GraphQLLangType = graphql.language.Type<*>
 
 internal fun Type<*>.unwrap(): Type<*> = when (this) {
@@ -35,6 +37,15 @@ internal fun JavaType.unwrap(): Class<out Any> =
             this as Class<*>
         }
 
+
+internal val Class<*>.declaredNonProxyMethods: List<JavaMethod>
+    get() {
+        return when {
+            Proxy.isProxyClass(this) -> emptyList()
+            else -> this.declaredMethods.toList()
+        }
+    }
+
 /**
  * Simple heuristic to check is a method is a trivial data fetcher.
  *
@@ -51,4 +62,5 @@ internal fun isTrivialDataFetcher(method: Method): Boolean {
 
 private fun isBooleanGetter(method: Method) = (method.name.startsWith("is")
         && (method.returnType == java.lang.Boolean::class.java)
-        || method.returnType == Boolean::class.java)
\ No newline at end of file
+        || method.returnType == Boolean::class.java)
+


Test patch:
diff --git a/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt b/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt
index b10074a..f894fd7 100644
--- a/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt
+++ b/src/test/kotlin/com/coxautodev/graphql/tools/MethodFieldResolverTest.kt
@@ -7,11 +7,15 @@ import graphql.schema.Coercing
 import graphql.schema.GraphQLScalarType
 import org.junit.Assert
 import org.junit.Test
+import java.lang.reflect.InvocationHandler
+import java.lang.reflect.Method
+import java.lang.reflect.Proxy
+import java.util.*
 
 class MethodFieldResolverTest {
 
     @Test
-    fun `should handle scalar types as method input argument`() {
+    fun shouldHandleScalarTypesAsMethodInputArgument() {
         val schema = SchemaParser.newParser()
                 .schemaString("""
                     scalar CustomScalar
@@ -44,7 +48,7 @@ class MethodFieldResolverTest {
     }
 
     @Test
-    fun `should handle lists of scalar types`() {
+    fun shouldHandleListsOfScalarTypes() {
         val schema = SchemaParser.newParser()
                 .schemaString("""
                     scalar CustomScalar
@@ -76,6 +80,55 @@ class MethodFieldResolverTest {
         Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
     }
 
+    @Test
+    fun shouldHandleProxies() {
+        val invocationHandler = object : InvocationHandler {
+            override fun invoke(proxy: Any, method: Method, args: Array<out Any>): Any {
+                return when (method.name) {
+                    "toString" -> "Proxy$" + System.identityHashCode(this)
+                    "hashCode" -> System.identityHashCode(this)
+                    "equals" -> Proxy.isProxyClass(args[0].javaClass)
+                    "test" -> (args[0] as List<*>).map { (it as CustomScalar).value.length }.sum()
+                    else -> UnsupportedOperationException()
+                }
+            }
+        }
+
+        val resolver = Proxy.newProxyInstance(
+                MethodFieldResolverTest::class.java.classLoader,
+                arrayOf(Resolver::class.java, GraphQLQueryResolver::class.java),
+                invocationHandler
+        )  as GraphQLQueryResolver
+
+        val schema = SchemaParser.newParser()
+                .schemaString("""
+                    scalar CustomScalar
+                    type Query {
+                        test(input: [CustomScalar]): Int
+                    }
+                    """.trimIndent()
+                )
+                .scalars(customScalarType)
+                .resolvers(resolver)
+                .build()
+                .makeExecutableSchema()
+
+        val gql = GraphQL.newGraphQL(schema).build()
+
+        val result = gql
+                .execute(ExecutionInput.newExecutionInput()
+                        .query("""
+                            query Test(${"$"}input: [CustomScalar]) {
+                                test(input: ${"$"}input)
+                            }
+                            """.trimIndent())
+                        .variables(mapOf("input" to listOf("Foo", "Bar")))
+                        .context(Object())
+                        .root(Object()))
+
+        Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
+    }
+
     /**
      * Custom Scalar Class type that doesn't work with Jackson serialization/deserialization
      */
@@ -90,6 +143,10 @@ class MethodFieldResolverTest {
         }
     }
 
+    interface Resolver {
+        fun test(scalars: List<CustomScalar>): Int
+    }
+
     private val customScalarType: GraphQLScalarType = GraphQLScalarType.newScalar()
             .name("CustomScalar")
             .description("customScalar")