Skip to content

Commit d4f29ec

Browse files
committed
Resolve type variables in resolver parameter types
1 parent 9a8908a commit d4f29ec

4 files changed

Lines changed: 56 additions & 18 deletions

File tree

‎src/main/kotlin/graphql/kickstart/tools/GenericTypeResolver.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ internal class GenericTypeResolver(private val containingType: JavaType, private
5252
}
5353
}
5454

55-
private fun resolveTypeVariables(type: JavaType, resolving: Set<TypeVariable<*>> = emptySet()): JavaType {
55+
fun resolveTypeVariables(type: JavaType, resolving: Set<TypeVariable<*>> = emptySet()): JavaType {
5656
return when (type) {
5757
is ParameterizedType -> {
5858
val actualTypeArguments = type.actualTypeArguments.map { resolveTypeVariables(it, resolving) }.toTypedArray()

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@ internal class MethodFieldResolver(
7474
this.field.inputValueDefinitions.forEachIndexed { index, definition ->
7575

7676
val parameterType = this.getMethodParameterType(index)
77-
?.apply { typeResolver.getRawClass(this) }
7877
?: throw ResolverError("Missing method type at position ${this.getJavaMethodParameterIndex(index)}, this is most likely a bug with graphql-java-tools")
7978

8079
val isNonNull = definition.type is NonNullType
@@ -206,7 +205,7 @@ internal class MethodFieldResolver(
206205
val parameters = method.parameterTypes
207206

208207
return if (parameters.size > methodIndex) {
209-
method.genericParameterTypes[methodIndex]
208+
typeResolver.resolveTypeVariables(method.genericParameterTypes[methodIndex])
210209
} else {
211210
null
212211
}

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

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,48 @@ class GenericResolverTest {
7070

7171
class CarResolver : FooGraphQLResolver<Car>()
7272

73+
@Test
74+
fun `inherited methods taking a type variable of their resolver are resolved`() {
75+
val schema = SchemaParser.newParser()
76+
.schemaString(
77+
"""
78+
type Query {
79+
generic(input: GreetingInput!): String!
80+
plain(input: GreetingInput!): String!
81+
}
82+
83+
input GreetingInput {
84+
name: String!
85+
}
86+
""")
87+
.resolvers(GreetingQuery())
88+
.build()
89+
.makeExecutableSchema()
90+
.assertEveryFieldResolves()
91+
val gql = GraphQL.newGraphQL(schema).build()
92+
93+
val data = assertNoGraphQlErrors(gql) {
94+
"""
95+
query {
96+
generic(input: { name: "a" })
97+
plain(input: { name: "b" })
98+
}
99+
"""
100+
}
101+
102+
assertEquals(data, mapOf("generic" to "generic got GreetingInput(name=a)", "plain" to "plain got GreetingInput(name=b)"))
103+
}
104+
105+
data class GreetingInput(val name: String)
106+
107+
abstract class InputResolver<I> : GraphQLQueryResolver {
108+
fun generic(input: I): String = "generic got $input"
109+
}
110+
111+
class GreetingQuery : InputResolver<GreetingInput>() {
112+
fun plain(input: GreetingInput): String = "plain got $input"
113+
}
114+
73115
@Test
74116
fun `star projected resolvers are applied to parameterized data classes`() {
75117
val gql = GraphQL.newGraphQL(pageSchema(PageResolver())).build()

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

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ class UnmappedInputFieldsTest {
328328
}
329329

330330
@Test
331-
fun `arguments are matched to their parameters past sources, environments and continuations`() {
331+
fun `arguments are matched to their parameters past sources, environments and continuations, and through type variables`() {
332332
val error = assertThrows(SchemaError::class.java) {
333333
SchemaParser.newParser()
334334
.schemaString(
@@ -337,6 +337,7 @@ class UnmappedInputFieldsTest {
337337
owner: Owner
338338
withEnvironment(input: EnvironmentInput!): String
339339
suspending(input: SuspendInput!): String
340+
generic(input: GenericInput!): String
340341
}
341342
342343
type Owner {
@@ -354,6 +355,10 @@ class UnmappedInputFieldsTest {
354355
input ChildInput {
355356
name: String
356357
}
358+
359+
input GenericInput {
360+
name: String
361+
}
357362
""")
358363
.resolvers(object : GraphQLQueryResolver {
359364
fun owner(): Owner = Owner()
@@ -362,7 +367,7 @@ class UnmappedInputFieldsTest {
362367
suspend fun suspending(input: SuspendInput): String? = input.nmae
363368
}, object : GraphQLResolver<Owner> {
364369
fun child(owner: Owner, id: String, input: ChildInput): String? = input.nmae
365-
})
370+
}, GenericInputResolver())
366371
.options(failOptions().build())
367372
.build()
368373
.makeExecutableSchema()
@@ -371,7 +376,8 @@ class UnmappedInputFieldsTest {
371376
assertEquals(error.message, listOf(
372377
unmapped("ChildInput.name", ChildInput::class.java, "argument 'input' of field 'Owner.child'", "nmae"),
373378
unmapped("EnvironmentInput.name", EnvironmentInput::class.java, "argument 'input' of field 'Query.withEnvironment'", "nmae"),
374-
unmapped("SuspendInput.name", SuspendInput::class.java, "argument 'input' of field 'Query.suspending'", "nmae")
379+
unmapped("SuspendInput.name", SuspendInput::class.java, "argument 'input' of field 'Query.suspending'", "nmae"),
380+
unmapped("GenericInput.name", GenericInput::class.java, "argument 'input' of field 'Query.generic'", "nmae")
375381
).joinToString("\n"))
376382
}
377383

@@ -690,7 +696,6 @@ class UnmappedInputFieldsTest {
690696
injected(input: InjectedInput!): String
691697
builder(input: BuilderTypoInput!): String
692698
unwrapped(input: KotlinUnwrappedInput!): String
693-
generic(input: GenericInput!): String
694699
}
695700
696701
input InjectedInput {
@@ -705,27 +710,19 @@ class UnmappedInputFieldsTest {
705710
name: String
706711
street: String
707712
}
708-
709-
input GenericInput {
710-
name: String
711-
}
712713
""")
713714
.resolvers(object : GraphQLQueryResolver {
714715
fun injected(input: InjectedInput): String? = input.nmae
715716
fun builder(input: BuilderTypoInput): String? = input.name
716717
fun unwrapped(input: KotlinUnwrappedInput): String = "${input.name} ${input.address?.stret}"
717-
}, GenericInputResolver())
718+
})
718719
.options(failOptions()
719720
.objectMapperConfigurer { mapper, _ -> mapper.setInjectableValues(InjectableValues.Std().addValue("tenant", "acme")) }
720721
.build())
721722
.build()
722723
.makeExecutableSchema()
723-
.assertEveryFieldResolves(except = setOf(
724-
// the input classes misspell name on purpose
725-
"Query.injected", "Query.builder",
726-
// TODO query generic once the type variable of its inherited parameter is resolved (#880)
727-
"Query.generic"
728-
))
724+
// the input classes misspell name on purpose
725+
.assertEveryFieldResolves(except = setOf("Query.injected", "Query.builder"))
729726

730727
val result = GraphQL.newGraphQL(schema).build().execute(
731728
"""

0 commit comments

Comments
 (0)