Skip to content

Commit 5b3cd85

Browse files
committed
Search dictionary for input type as backup (fix #168)
1 parent e052d89 commit 5b3cd85

File tree

1 file changed

+53
-39
lines changed

1 file changed

+53
-39
lines changed

src/main/kotlin/com/coxautodev/graphql/tools/SchemaClassScanner.kt

Lines changed: 53 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ import graphql.language.SchemaDefinition
1515
import graphql.language.TypeDefinition
1616
import graphql.language.TypeName
1717
import graphql.language.UnionTypeDefinition
18-
import graphql.schema.GraphQLDirective
1918
import graphql.schema.GraphQLScalarType
2019
import graphql.schema.idl.ScalarInfo
2120
import org.slf4j.LoggerFactory
2221
import java.lang.reflect.Field
2322
import java.lang.reflect.Method
24-
import java.util.ArrayList
2523

2624
/**
2725
* @author Andrew Potter
@@ -59,12 +57,12 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
5957

6058
init {
6159
initialDictionary.forEach { (name, clazz) ->
62-
if(!definitionsByName.containsKey(name)) {
60+
if (!definitionsByName.containsKey(name)) {
6361
throw SchemaClassScannerError("Class in supplied dictionary '${clazz.name}' specified type name '$name', but a type definition with that name was not found!")
6462
}
6563
}
6664

67-
if(options.allowUnimplementedResolvers) {
65+
if (options.allowUnimplementedResolvers) {
6866
log.warn("Option 'allowUnimplementedResolvers' should only be set to true during development, as it can cause schema errors to be moved to query time instead of schema creation time. Make sure this is turned off in production.")
6967
}
7068
}
@@ -98,7 +96,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
9896
}
9997

10098
private fun scanQueue(): Boolean {
101-
if(queue.isEmpty()) {
99+
if (queue.isEmpty()) {
102100
return false
103101
}
104102

@@ -113,7 +111,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
113111
* Adds all root resolvers for a type to the list of classes to scan
114112
*/
115113
private fun handleRootType(rootType: RootType?) {
116-
if(rootType == null) {
114+
if (rootType == null) {
117115
return
118116
}
119117

@@ -135,7 +133,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
135133
// Union types can also be excluded, as their possible types are resolved recursively later
136134
val dictionary = try {
137135
Maps.unmodifiableBiMap(HashBiMap.create<TypeDefinition<*>, Class<*>>().also {
138-
dictionary.filter { it.value.typeClass != null && it.key !is InputObjectTypeDefinition && it.key !is UnionTypeDefinition}.mapValuesTo(it) { it.value.typeClass }
136+
dictionary.filter { it.value.typeClass != null && it.key !is InputObjectTypeDefinition && it.key !is UnionTypeDefinition }.mapValuesTo(it) { it.value.typeClass }
139137
})
140138
} catch (t: Throwable) {
141139
throw SchemaClassScannerError("Error creating bimap of type => class", t)
@@ -147,8 +145,10 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
147145
// Filter for any defined scalars OR scalars that aren't defined but also aren't standard
148146
scalars.containsKey(it.name) || !ScalarInfo.STANDARD_SCALAR_DEFINITIONS.containsKey(it.name)
149147
}.map { definition ->
150-
val provided = scalars[definition.name] ?: throw SchemaClassScannerError("Expected a user-defined GraphQL scalar type with name '${definition.name}' but found none!")
151-
GraphQLScalarType(provided.name, SchemaParser.getDocumentation(definition) ?: provided.description, provided.coercing, listOf(), definition)
148+
val provided = scalars[definition.name]
149+
?: throw SchemaClassScannerError("Expected a user-defined GraphQL scalar type with name '${definition.name}' but found none!")
150+
GraphQLScalarType(provided.name, SchemaParser.getDocumentation(definition)
151+
?: provided.description, provided.coercing, listOf(), definition)
152152
}.associateBy { it.name!! }
153153

154154
(definitionsByName.values - observedDefinitions).forEach { definition ->
@@ -171,13 +171,13 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
171171
}
172172

173173
private fun validateRootResolversWereUsed(rootType: RootType?, fieldResolvers: List<FieldResolver>) {
174-
if(rootType == null) {
174+
if (rootType == null) {
175175
return
176176
}
177177

178178
val observedRootTypes = fieldResolvers.filter { it.resolverInfo is RootResolverInfo && it.resolverInfo == rootType.resolverInfo }.map { it.search.type }.toSet()
179179
rootType.resolvers.forEach { resolver ->
180-
if(rootType.resolverInfo.getRealResolverClass(resolver, options) !in observedRootTypes) {
180+
if (rootType.resolverInfo.getRealResolverClass(resolver, options) !in observedRootTypes) {
181181
log.warn("Root ${rootType.name} resolver was provided but no methods on it were used in data fetchers for GraphQL type '${rootType.type.name}'! Either remove the ${rootType.resolverInterface.name} interface from the resolver or remove the resolver entirely: $resolver")
182182
}
183183
}
@@ -186,20 +186,23 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
186186
private fun getAllObjectTypesImplementingDiscoveredInterfaces(): List<ObjectTypeDefinition> {
187187
return dictionary.keys.filterIsInstance<InterfaceTypeDefinition>().map { iface ->
188188
objectDefinitions.filter { obj -> obj.implements.filterIsInstance<TypeName>().any { it.name == iface.name } }
189-
}.flatten().distinctBy{ it.name }
189+
}.flatten().distinctBy { it.name }
190190
}
191191

192192
private fun getAllObjectTypeMembersOfDiscoveredUnions(): List<ObjectTypeDefinition> {
193193
val unionTypeNames = dictionary.keys.filterIsInstance<UnionTypeDefinition>().map { union -> union.name }.toSet()
194194
return dictionary.keys.filterIsInstance<UnionTypeDefinition>().map { union ->
195-
union.memberTypes.filterIsInstance<TypeName>().filter { !unionTypeNames.contains(it.name) }.map { objectDefinitionsByName[it.name] ?: throw SchemaClassScannerError("No object type found with name '${it.name}' for union: $union") }
195+
union.memberTypes.filterIsInstance<TypeName>().filter { !unionTypeNames.contains(it.name) }.map {
196+
objectDefinitionsByName[it.name]
197+
?: throw SchemaClassScannerError("No object type found with name '${it.name}' for union: $union")
198+
}
196199
}.flatten().distinct()
197200
}
198201

199202
private fun handleInterfaceOrUnionSubTypes(types: List<ObjectTypeDefinition>, failureMessage: (ObjectTypeDefinition) -> String) {
200203
types.forEach { type ->
201-
val dictionaryContainsType = dictionary.filter{ it.key.name == type.name }.isNotEmpty()
202-
if(!unvalidatedTypes.contains(type) && !dictionaryContainsType) {
204+
val dictionaryContainsType = dictionary.filter { it.key.name == type.name }.isNotEmpty()
205+
if (!unvalidatedTypes.contains(type) && !dictionaryContainsType) {
203206
val initialEntry = initialDictionary[type.name] ?: throw SchemaClassScannerError(failureMessage(type))
204207
handleFoundType(type, initialEntry.get(), DictionaryReference())
205208
}
@@ -234,7 +237,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
234237
}
235238

236239
private fun handleFoundType(match: TypeClassMatcher.Match) {
237-
when(match) {
240+
when (match) {
238241
is TypeClassMatcher.ScalarMatch -> {
239242
handleFoundScalarType(match.type)
240243
}
@@ -261,7 +264,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
261264

262265
if (realEntry.typeClass != clazz) {
263266
if (options.preferGraphQLResolver && realEntry.hasResolverRef()) {
264-
log.warn("The real entry ${realEntry.joinReferences()} is a GraphQLResolver so ignoring this one $clazz $reference")
267+
log.warn("The real entry ${realEntry.joinReferences()} is a GraphQLResolver so ignoring this one $clazz $reference")
265268
} else {
266269
throw SchemaClassScannerError("Two different classes used for type ${type.name}:\n${realEntry.joinReferences()}\n\n- $clazz:\n| ${reference.getDescription()}")
267270
}
@@ -280,7 +283,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
280283
* Handle a newly found type, adding it to the list of actually used types and putting it in the scanning queue if it's an object type.
281284
*/
282285
private fun handleNewType(graphQLType: TypeDefinition<*>, javaType: Class<*>) {
283-
when(graphQLType) {
286+
when (graphQLType) {
284287
is ObjectTypeDefinition -> {
285288
enqueue(graphQLType, javaType)
286289
scanInterfacesOfType(graphQLType)
@@ -290,7 +293,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
290293
graphQLType.inputValueDefinitions.forEach { inputValueDefinition ->
291294
val inputGraphQLType = inputValueDefinition.type.unwrap()
292295
if (inputGraphQLType is TypeName && !ScalarInfo.STANDARD_SCALAR_DEFINITIONS.containsKey(inputGraphQLType.name)) {
293-
val inputValueJavaType = findInputValueType(inputValueDefinition.name, javaType)
296+
val inputValueJavaType = findInputValueType(inputValueDefinition.name, inputGraphQLType, javaType)
294297
if (inputValueJavaType != null) {
295298
handleFoundType(typeClassMatcher.match(TypeClassMatcher.PotentialMatch.parameterType(
296299
inputValueDefinition.type,
@@ -300,11 +303,11 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
300303
false
301304
)))
302305
} else {
303-
var mappingAdvice = ""
306+
var mappingAdvice = "Try adding it manually to the dictionary"
304307
if (javaType.name.contains("Map")) {
305-
mappingAdvice = ". Try using a class to represent your input type instead of a Map."
308+
mappingAdvice = " or add a class to represent your input type instead of a Map."
306309
}
307-
log.warn("Cannot find definition for field '${inputValueDefinition.name}: ${inputGraphQLType.name}' on input type '${graphQLType.name}' -> ${javaType.name}$mappingAdvice")
310+
log.warn("Cannot find definition for field '${inputValueDefinition.name}: ${inputGraphQLType.name}' on input type '${graphQLType.name}' -> ${javaType.name}. $mappingAdvice")
308311
}
309312
}
310313
}
@@ -314,8 +317,9 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
314317

315318
private fun scanInterfacesOfType(graphQLType: ObjectTypeDefinition) {
316319
graphQLType.implements.forEach {
317-
if(it is TypeName) {
318-
handleFoundType(interfaceDefinitionsByName[it.name] ?: throw SchemaClassScannerError("Object type ${graphQLType.name} declared interface ${it.name}, but no interface with that name was found in the schema!"), null, InterfaceReference(graphQLType))
320+
if (it is TypeName) {
321+
handleFoundType(interfaceDefinitionsByName[it.name]
322+
?: throw SchemaClassScannerError("Object type ${graphQLType.name} declared interface ${it.name}, but no interface with that name was found in the schema!"), null, InterfaceReference(graphQLType))
319323
}
320324
}
321325
}
@@ -324,7 +328,16 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
324328
queue.add(QueueItem(graphQLType, javaType))
325329
}
326330

327-
private fun findInputValueType(name: String, clazz: Class<*>): JavaType? {
331+
private fun findInputValueType(name: String, inputGraphQLType: TypeName, clazz: Class<*>): JavaType? {
332+
val inputValueType = findInputValueTypeInType(name, clazz)
333+
if (inputValueType != null) {
334+
return inputValueType
335+
}
336+
337+
return initialDictionary[inputGraphQLType.name]?.get()
338+
}
339+
340+
private fun findInputValueTypeInType(name: String, clazz: Class<*>): JavaType? {
328341
val methods = clazz.methods
329342

330343
val filteredMethods = methods.filter {
@@ -346,7 +359,7 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
346359
private set
347360

348361
fun setTypeIfMissing(typeClass: Class<*>): Boolean {
349-
if(this.typeClass == null) {
362+
if (this.typeClass == null) {
350363
this.typeClass = typeClass
351364
return true
352365
}
@@ -357,10 +370,11 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
357370
fun addReference(reference: Reference) {
358371
references.add(reference)
359372
}
373+
360374
fun joinReferences() = "- $typeClass:\n| " + references.joinToString("\n| ") { it.getDescription() }
361375

362-
fun hasResolverRef():Boolean {
363-
references.filterIsInstance<ReturnValueReference>().forEach{ reference ->
376+
fun hasResolverRef(): Boolean {
377+
references.filterIsInstance<ReturnValueReference>().forEach { reference ->
364378
if (GraphQLResolver::class.java.isAssignableFrom(reference.getMethod().declaringClass)) {
365379
return true
366380
}
@@ -374,15 +388,15 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
374388
override fun toString() = getDescription()
375389
}
376390

377-
private class DictionaryReference: Reference() {
391+
private class DictionaryReference : Reference() {
378392
override fun getDescription() = "provided dictionary"
379393
}
380394

381-
private class InterfaceReference(private val type: ObjectTypeDefinition): Reference() {
395+
private class InterfaceReference(private val type: ObjectTypeDefinition) : Reference() {
382396
override fun getDescription() = "interface declarations of ${type.name}"
383397
}
384398

385-
private class InputObjectReference(private val type: InputValueDefinition): Reference() {
399+
private class InputObjectReference(private val type: InputValueDefinition) : Reference() {
386400
override fun getDescription() = "input object $type"
387401
}
388402

@@ -396,16 +410,16 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
396410
}
397411
}
398412

399-
class ReturnValueReference(private val method: Method): Reference() {
400-
fun getMethod () = method;
413+
class ReturnValueReference(private val method: Method) : Reference() {
414+
fun getMethod() = method;
401415
override fun getDescription() = "return type of method $method"
402416
}
403417

404-
class MethodParameterReference(private val method: Method, private val index: Int): Reference() {
418+
class MethodParameterReference(private val method: Method, private val index: Int) : Reference() {
405419
override fun getDescription() = "parameter $index of method $method"
406420
}
407421

408-
class FieldTypeReference(private val field: Field): Reference() {
422+
class FieldTypeReference(private val field: Field) : Reference() {
409423
override fun getDescription() = "type of field $field"
410424
}
411425

@@ -427,20 +441,20 @@ internal class SchemaClassScanner(initialDictionary: BiMap<String, Class<*>>, al
427441
val subscription = createRootType("subscription", subscriptionDefinition, subscriptionName, rootInfo.isSubscriptionRequired(), subscriptionResolvers, GraphQLSubscriptionResolver::class.java, subscriptionResolverInfo)
428442

429443
private fun createRootType(name: String, type: TypeDefinition<*>?, typeName: String, required: Boolean, resolvers: List<GraphQLRootResolver>, resolverInterface: Class<*>, resolverInfo: RootResolverInfo): RootType? {
430-
if(type == null) {
431-
if(required) {
444+
if (type == null) {
445+
if (required) {
432446
throw SchemaClassScannerError("Type definition for root $name type '$typeName' not found!")
433447
}
434448

435449
return null
436450
}
437451

438-
if(type !is ObjectTypeDefinition) {
452+
if (type !is ObjectTypeDefinition) {
439453
throw SchemaClassScannerError("Expected root query type's type to be ${ObjectTypeDefinition::class.java.simpleName}, but it was ${type.javaClass.simpleName}")
440454
}
441455

442456
// Find query resolver class
443-
if(resolvers.isEmpty()) {
457+
if (resolvers.isEmpty()) {
444458
throw SchemaClassScannerError("No Root resolvers for $name type '$typeName' found! Provide one or more ${resolverInterface.name} to the builder.")
445459
}
446460

0 commit comments

Comments
 (0)