Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Improve error message when data-fetching method is missing.
  • Loading branch information
apottere committed May 30, 2017
commit 60e1d04a9f4522b3c6c97adf6d4d3bcf162fb8b0
69 changes: 50 additions & 19 deletions src/main/kotlin/com/coxautodev/graphql/tools/Resolver.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package com.coxautodev.graphql.tools

import graphql.Scalars
import graphql.language.FieldDefinition
import graphql.language.ListType
import graphql.language.NonNullType
import graphql.language.Type
import graphql.language.TypeName
import graphql.schema.DataFetchingEnvironment
import ru.vyarus.java.generics.resolver.GenericsResolver
import java.lang.reflect.Method
import java.lang.reflect.Type

open class Resolver @JvmOverloads constructor(val resolver: GraphQLResolver<*>, dataClass: Class<*>? = null) {

Expand All @@ -22,12 +26,21 @@ open class Resolver @JvmOverloads constructor(val resolver: GraphQLResolver<*>,
return type
}

private fun isBoolean(returnType: Class<*>): Boolean {
return returnType.isAssignableFrom(Boolean::class.java) || returnType.isPrimitive && returnType.javaClass.name == "boolean"
private fun isBoolean(type: Type): Boolean {
return when(type) {
is NonNullType -> isBoolean(type.type)
is ListType -> isBoolean(type.type)
is TypeName -> type.name == Scalars.GraphQLBoolean.name
else -> false
}
}

private fun getMethod(clazz: Class<*>, name: String, argumentCount: Int, isResolverMethod: Boolean = false): Method? {
private fun getMethod(clazz: Class<*>, field: FieldDefinition, isResolverMethod: Boolean = false): Method? {
val methods = clazz.methods
val argumentCount = field.inputValueDefinitions.size + if(isResolverMethod && !isRootResolver()) 1 else 0
val name = field.name

val isBoolean = isBoolean(field.type)

// Check for the following one by one:
// 1. Method with exact field name
Expand All @@ -36,7 +49,7 @@ open class Resolver @JvmOverloads constructor(val resolver: GraphQLResolver<*>,
return methods.find {
it.name == name && verifyMethodArguments(it, argumentCount, isResolverMethod)
} ?: methods.find {
(isBoolean(it.returnType) && it.name == "is${name.capitalize()}") && verifyMethodArguments(it, argumentCount, isResolverMethod)
(isBoolean && it.name == "is${name.capitalize()}") && verifyMethodArguments(it, argumentCount, isResolverMethod)
} ?: methods.find {
it.name == "get${name.capitalize()}" && verifyMethodArguments(it, argumentCount, isResolverMethod)
}
Expand All @@ -49,7 +62,7 @@ open class Resolver @JvmOverloads constructor(val resolver: GraphQLResolver<*>,
}

open fun getMethod(field: FieldDefinition): ResolverMethod {
val method = getMethod(resolverType, field.name, field.inputValueDefinitions.size + if(!isRootResolver()) 1 else 0, true)
val method = getMethod(resolverType, field, true)

if(method != null) {
return ResolverMethod(this, field, method, resolverType, true, !isRootResolver())
Expand All @@ -60,33 +73,51 @@ open class Resolver @JvmOverloads constructor(val resolver: GraphQLResolver<*>,

protected fun getDataClassMethod(field: FieldDefinition): ResolverMethod {
if(!isRootResolver()) {
val method = getMethod(dataClassType, field.name, field.inputValueDefinitions.size)
val method = getMethod(dataClassType, field)
if(method != null) {
return ResolverMethod(this, field, method, dataClassType, false, false)
}
}

throw ResolverError(getMissingMethodMessage(field.name, field.inputValueDefinitions.size))
throw ResolverError(getMissingMethodMessage(field))
}

fun getMissingMethodMessage(name: String, argumentCount: Int): String {
var msg = "No method found with name: '$name' and '$argumentCount' argument${if(argumentCount == 1) "" else "s"} (+1 for resolver methods and +1 if injecting ${DataFetchingEnvironment::class.java.simpleName}) on "
var hadResolver = false
fun getMissingMethodMessage(field: FieldDefinition): String {
val signatures = mutableListOf<String>()
val isBoolean = isBoolean(field.type)
val sep = "\n "

if(resolverType != NoopResolver::class.java) {
msg += "resolver ${resolverType.name}"
hadResolver = true
signatures.addAll(getMissingMethodSignatures(resolverType, field, isBoolean, true))
}

if(!isRootResolver()) {
if(hadResolver) {
msg += " or its "
}
signatures.addAll(getMissingMethodSignatures(dataClassType, field, isBoolean, false))
}

return "No method found with any of the following signatures (in priority order):$sep${signatures.joinToString(sep)}"
}

fun getMissingMethodSignatures(baseType: Class<*>, field: FieldDefinition, isBoolean: Boolean, isResolver: Boolean): List<String> {
val signatures = mutableListOf<String>()
val args = mutableListOf<String>()
val sep = ", "

if(isResolver && !isRootResolver()) {
args.add(dataClassType.name)
}

args.addAll(field.inputValueDefinitions.map { "~${it.name}" })

val argString = args.joinToString(sep) + " [, ${DataFetchingEnvironment::class.java.name}]"

msg += "data class ${dataClassType.name}"
signatures.add("${baseType.name}.${field.name}($argString)")
if(isBoolean) {
signatures.add("${baseType.name}.is${field.name.capitalize()}($argString)")
}
signatures.add("${baseType.name}.get${field.name.capitalize()}($argString)")

return msg
return signatures
}

fun isRootResolver() = dataClassType == Void::class.java
Expand All @@ -100,7 +131,7 @@ open class Resolver @JvmOverloads constructor(val resolver: GraphQLResolver<*>,
private fun getIndexOffset() = if(sourceArgument) 1 else 0
fun getJavaMethodParameterIndex(index: Int) = index + getIndexOffset()

fun getJavaMethodParameterType(index: Int): Type? {
fun getJavaMethodParameterType(index: Int): JavaType? {
val methodIndex = getJavaMethodParameterIndex(index)
val parameters = javaMethod.parameterTypes
if(parameters.size > methodIndex) {
Expand Down