Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {

private val allowedLastArgumentTypes = listOfNotNull(DataFetchingEnvironment::class.java, GraphQLContext::class.java, options.contextClass)

private val methodsByNameCache = mutableMapOf<Pair<Class<out Any>, Boolean>, Map<String, List<Method>>>()

fun findFieldResolver(field: FieldDefinition, resolverInfo: ResolverInfo): FieldResolver {
val searches = resolverInfo.getFieldSearches()

Expand Down Expand Up @@ -86,29 +88,34 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
}

private fun findResolverMethod(field: FieldDefinition, search: Search): Method? {
val methods = getAllMethods(search)
val methodsByName = getMethodsByName(search)
val argumentCount = field.inputValueDefinitions.size + if (search.requiredFirstParameterType != null) 1 else 0
val name = field.name
val capitalizedName = name.replaceFirstChar(Char::titlecase)

// Check for the following one by one:
// 1. Method with exact field name
// 2. Method that returns a boolean with "is" style getter
// 3. Method with "get" style getter
// 4. Method with "getField" style getter
// 5. Method with "get" style getter with the field name converted from snake_case to camelCased. ex: key_ops -> getKeyOps()
return methods.find {
it.name == name && verifyMethodArguments(it, argumentCount, search)
} ?: methods.find {
(isBoolean(field.type) && it.name == "is${name.replaceFirstChar(Char::titlecase)}") && verifyMethodArguments(it, argumentCount, search)
} ?: methods.find {
it.name == "get${name.replaceFirstChar(Char::titlecase)}" && verifyMethodArguments(it, argumentCount, search)
} ?: methods.find {
it.name == "getField${name.replaceFirstChar(Char::titlecase)}" && verifyMethodArguments(it, argumentCount, search)
} ?: methods.find {
it.name == "get${name.snakeToCamelCase()}" && verifyMethodArguments(it, argumentCount, search)
return listOfNotNull(
name,
if (isBoolean(field.type)) "is$capitalizedName" else null,
"get$capitalizedName",
"getField$capitalizedName",
"get${name.snakeToCamelCase()}"
).firstNotNullOfOrNull { methodName ->
methodsByName[methodName]?.find { verifyMethodArguments(it, argumentCount, search) }
}
}

// root fields are searched on every root resolver, so each class's methods are only indexed once
private fun getMethodsByName(search: Search): Map<String, List<Method>> =
methodsByNameCache.getOrPut(search.type.unwrap() to search.isSubscription) {
getAllMethods(search).groupBy { it.name }
}

private fun getAllMethods(search: Search): List<Method> {
val type = search.type.unwrap()
val declaredMethods = type.declaredNonProxyMethods
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,19 @@ class FieldResolverScannerTest {
Locale.setDefault(default)
}

@Test
fun `scanner finds field resolver methods in priority order`() {
val resolverInfo = RootResolverInfo(listOf(PriorityQuery()), options)

fun methodName(name: String, type: String) =
(scanner.findFieldResolver(FieldDefinition(name, TypeName(type)), resolverInfo) as MethodFieldResolver).method.name

assertEquals(methodName("name", "String"), "name")
assertEquals(methodName("active", "Boolean"), "isActive")
assertEquals(methodName("enabled", "String"), "getEnabled")
assertEquals(methodName("count", "Int"), "getFieldCount")
}

class RootQuery1 : GraphQLQueryResolver {
fun field1() {}
}
Expand All @@ -118,6 +131,18 @@ class FieldResolverScannerTest {
fun getId(): HullType = HullType()
}

class PriorityQuery : GraphQLQueryResolver {
fun name(): String = "name"
fun getName(): String = "name"
fun getFieldName(): String = "name"
fun active(flag: Boolean): Boolean = flag
fun isActive(): Boolean = true
fun getActive(): Boolean = true
fun isEnabled(): Boolean = true
fun getEnabled(): String = "enabled"
fun getFieldCount(): Int = 1
}

class HullType

open class ParentPropertyQuery {
Expand Down
Loading