Skip to content

Commit d050dd7

Browse files
oryan-blockclaude
andcommitted
Prefer getters over fluent setters for input field types
To find the Java type of an input object field, the scanner took any public method named `x` or `getX`, picked the one with the shortest name and used its return type. A fluent setter such as `RepairApply repairMan(RepairMan)`, as generated by JHipster or Lombok @accessors(fluent = true), has the shortest name, so the input type of the field was mapped to the enclosing class. That fails with "Two different classes used for type" as soon as the same input type is also used as a resolver parameter. Look for a getter without parameters first, then for a public field. Methods with parameters, such as `getX(DataFetchingEnvironment)`, are only used when neither exists, so nested input types that can only be found through them are still discovered. Fixes #446 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 62c5146 commit d050dd7

2 files changed

Lines changed: 122 additions & 4 deletions

File tree

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

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -436,13 +436,15 @@ internal class SchemaClassScanner(
436436
private fun findInputValueTypeInType(name: String, clazz: Class<*>): JavaType? {
437437
val methods = clazz.methods
438438

439-
val filteredMethods = methods.filter {
439+
val (getters, methodsWithParameters) = methods.filter {
440440
it.name == name || it.name == "get${name.replaceFirstChar(Char::titlecase)}"
441-
}.sortedBy { it.name.length }
441+
}.sortedBy { it.name.length }.partition { it.parameterCount == 0 }
442442

443-
return filteredMethods.find { !it.isSynthetic }?.genericReturnType
444-
?: filteredMethods.firstOrNull()?.genericReturnType
443+
return getters.find { !it.isSynthetic }?.genericReturnType
444+
?: getters.firstOrNull()?.genericReturnType
445445
?: clazz.fields.find { it.name == name }?.genericType
446+
?: methodsWithParameters.find { !it.isSynthetic }?.genericReturnType
447+
?: methodsWithParameters.firstOrNull()?.genericReturnType
446448
}
447449

448450
private data class QueueItem(val type: ObjectTypeDefinition, val clazz: JavaType)

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

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,122 @@ class SchemaClassScannerTest {
146146
}
147147
}
148148

149+
@Test
150+
fun `scanner ignores fluent setters when finding input field types`() {
151+
SchemaParser.newParser()
152+
.resolvers(FluentSetterMutation(), object : GraphQLQueryResolver {
153+
fun test(): Boolean = true
154+
})
155+
.schemaString(
156+
"""
157+
type Query {
158+
test: Boolean
159+
}
160+
161+
type Mutation {
162+
createRepairApply(body: RepairApplyInput): Boolean
163+
createRepairMan(body: RepairManInput): Boolean
164+
}
165+
166+
input RepairApplyInput {
167+
id: ID
168+
repairMan: RepairManInput
169+
reviewer: RepairManInput
170+
approver: RepairManInput
171+
}
172+
173+
input RepairManInput {
174+
id: ID
175+
userName: String
176+
}
177+
""")
178+
.build()
179+
.makeExecutableSchema()
180+
}
181+
182+
private class FluentSetterMutation : GraphQLMutationResolver {
183+
fun createRepairApply(body: RepairApply): Boolean = true
184+
fun createRepairMan(body: RepairMan): Boolean = true
185+
186+
class RepairApply {
187+
var id: String? = null
188+
var repairMan: RepairMan? = null
189+
private var reviewer: RepairMan? = null
190+
@JvmField
191+
var approver: RepairMan? = null
192+
193+
fun repairMan(repairMan: RepairMan?): RepairApply {
194+
this.repairMan = repairMan
195+
return this
196+
}
197+
198+
fun reviewer(): RepairMan? = reviewer
199+
200+
fun reviewer(reviewer: RepairMan?): RepairApply {
201+
this.reviewer = reviewer
202+
return this
203+
}
204+
205+
fun approver(approver: RepairMan?): RepairApply {
206+
this.approver = approver
207+
return this
208+
}
209+
}
210+
211+
class RepairMan {
212+
var id: String? = null
213+
var userName: String? = null
214+
}
215+
}
216+
217+
@Test
218+
fun `scanner finds input field types through getters with arguments`() {
219+
SchemaParser.newParser()
220+
.resolvers(GetterWithArgumentsQuery())
221+
.schemaString(
222+
"""
223+
type Query {
224+
foo(input: FooInput): Foo
225+
}
226+
227+
type Foo {
228+
bar: Bar
229+
}
230+
231+
type Bar {
232+
name: String
233+
}
234+
235+
input FooInput {
236+
bar: BarInput
237+
}
238+
239+
input BarInput {
240+
name: String
241+
}
242+
""")
243+
.build()
244+
.makeExecutableSchema()
245+
}
246+
247+
private class GetterWithArgumentsQuery : GraphQLQueryResolver {
248+
fun foo(input: Foo): Foo = input
249+
250+
class Foo {
251+
private var bar: Bar? = null
252+
253+
fun getBar(env: DataFetchingEnvironment): Bar? = bar
254+
255+
fun setBar(bar: Bar?) {
256+
this.bar = bar
257+
}
258+
}
259+
260+
class Bar {
261+
var name: String? = null
262+
}
263+
}
264+
149265
@Test
150266
fun `scanner handles input types extensions`() {
151267
val schema = SchemaParser.newParser()

0 commit comments

Comments
 (0)