diff --git a/src/main/kotlin/graphql/kickstart/tools/GenericType.kt b/src/main/kotlin/graphql/kickstart/tools/GenericType.kt index 60a58454..7771c5fd 100644 --- a/src/main/kotlin/graphql/kickstart/tools/GenericType.kt +++ b/src/main/kotlin/graphql/kickstart/tools/GenericType.kt @@ -121,9 +121,17 @@ internal open class GenericType(protected val mostSpecificType: JavaType, protec } } - private fun unwrapGenericType(declaringType: ParameterizedType, type: TypeVariable<*>) = - unwrapGenericType(TypeUtils.determineTypeArguments(getRawClass(mostSpecificType), declaringType)[type] - ?: error("No type variable found for: ${TypeUtils.toLongString(type)}")) + private fun unwrapGenericType(declaringType: ParameterizedType, type: TypeVariable<*>): JavaType { + val rawClass = getRawClass(mostSpecificType) + val arguments = TypeUtils.determineTypeArguments(rawClass, declaringType) + val matchingType = arguments + .filter { it.key.name == type.name } + .values + .firstOrNull() + ?: error("No type variable found for: ${TypeUtils.toLongString(type)}") + + return unwrapGenericType(matchingType) + } private fun replaceTypeVariable(type: JavaType): JavaType { return when (type) { diff --git a/src/test/groovy/graphql/kickstart/tools/SchemaParserSpec.groovy b/src/test/groovy/graphql/kickstart/tools/SchemaParserSpec.groovy index 78e486ec..f98e20f4 100644 --- a/src/test/groovy/graphql/kickstart/tools/SchemaParserSpec.groovy +++ b/src/test/groovy/graphql/kickstart/tools/SchemaParserSpec.groovy @@ -33,7 +33,7 @@ class SchemaParserSpec extends Specification { def "builder doesn't throw FileNotFound exception when file is present"() { when: - SchemaParser.newParser().file("test.graphqls") + SchemaParser.newParser().file("Test.graphqls") .resolvers(new GraphQLQueryResolver() { String getId() { "1" } }) @@ -286,7 +286,7 @@ class SchemaParserSpec extends Specification { def "parser should include source location for field definition when loaded from single classpath file"() { when: GraphQLSchema schema = SchemaParser.newParser() - .file("test.graphqls") + .file("Test.graphqls") .resolvers(new QueryWithIdResolver()) .build() .makeExecutableSchema() @@ -298,7 +298,7 @@ class SchemaParserSpec extends Specification { sourceLocation != null sourceLocation.line == 2 sourceLocation.column == 5 - sourceLocation.sourceName == "test.graphqls" + sourceLocation.sourceName == "Test.graphqls" } def "support enum types if only used as input type"() { diff --git a/src/test/kotlin/graphql/kickstart/tools/DeepGenericsHierarchyTest.kt b/src/test/kotlin/graphql/kickstart/tools/DeepGenericsHierarchyTest.kt new file mode 100644 index 00000000..72a6e35f --- /dev/null +++ b/src/test/kotlin/graphql/kickstart/tools/DeepGenericsHierarchyTest.kt @@ -0,0 +1,55 @@ +package graphql.kickstart.tools + +import graphql.ExecutionInput +import graphql.GraphQL +import org.junit.Test + +class PlaceTest { + + @Test + fun shouldHandleGenericsDeepHierarchy() { + val schema = SchemaParser.newParser() + .file("Place.graphqls") + .resolvers(PlaceQuery()) + .build().makeExecutableSchema() + + val graphql = GraphQL.newGraphQL(schema).build() + val query = "query { places1 { id } places2 { id } }" + val executionInput = ExecutionInput.newExecutionInput().query(query).build() + val result = graphql.execute(executionInput) + + assert(result.getData>>()["places1"]?.size == 3) + assert(result.getData>>()["places2"]?.size == 2) + } +} + +private class PlaceQuery : GraphQLQueryResolver { + + fun places1(): List = listOf(Place1("1"), Place1("2"), Place1("3")) + + fun places2(): List = listOf(Place2("4"), Place2("5")) +} + +private abstract class Entity(val id: String? = null) + +private abstract class OtherPlace>(id: String? = null) : Place(id) { + val other: String? = null +} + +private abstract class Place>(id: String? = null) : Entity(id) { + val name: String? = null + val reviews: MutableSet? = null +} + +private class Place1(id: String? = null) : OtherPlace(id) + +private class Place2(id: String? = null) : OtherPlace(id) + +private abstract class Review(id: String? = null) : Entity(id) { + val rating: Int? = null + val content: T? = null +} + +private class Review1(id: String? = null) : Review(id) + +private class Review2(id: String? = null) : Review(id) diff --git a/src/test/kotlin/graphql/kickstart/tools/UtilsTest.kt b/src/test/kotlin/graphql/kickstart/tools/UtilsTest.kt index 38f9cc61..5294300c 100644 --- a/src/test/kotlin/graphql/kickstart/tools/UtilsTest.kt +++ b/src/test/kotlin/graphql/kickstart/tools/UtilsTest.kt @@ -24,7 +24,7 @@ class UtilsTest { } @Test - fun `isTrivialDataFetcher`() { + fun isTrivialDataFetcher() { val clazz = Bean::class.java Assert.assertTrue(isTrivialDataFetcher(clazz.getMethod("getterValid"))) diff --git a/src/test/resources/Place.graphqls b/src/test/resources/Place.graphqls new file mode 100644 index 00000000..dc9c7c6b --- /dev/null +++ b/src/test/resources/Place.graphqls @@ -0,0 +1,51 @@ +type Query { + places1: [Place1!] + places2: [Place2!] +} + +interface Entity { + id: ID! +} + +interface Place { + name: String + reviews: [Review!] +} + +interface OtherPlace { + name: String + other: String + reviews: [Review!] +} + +type Place1 implements Entity, Place, OtherPlace { + id: ID! + name: String + other: String + reviews: [Review1!] +} + +type Place2 implements Entity, Place, OtherPlace { + id: ID! + name: String + other: String + reviews: [Review2!] +} + +interface Review { + id: ID! + rating: Int + content: Entity +} + +type Review1 implements Review { + id: ID! + rating: Int + content: Place1 +} + +type Review2 implements Review { + id: ID! + rating: Int + content: Place2 +} diff --git a/src/test/resources/test.graphqls b/src/test/resources/Test.graphqls similarity index 100% rename from src/test/resources/test.graphqls rename to src/test/resources/Test.graphqls