Skip to content

Commit 404b3e7

Browse files
authored
Merge pull request #844 from graphql-java-kickstart/bugfix/597
Discover unions and interfaces used only by interface fields
2 parents 60adaff + 06b0d68 commit 404b3e7

2 files changed

Lines changed: 524 additions & 7 deletions

File tree

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

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -75,16 +75,24 @@ internal class SchemaClassScanner(
7575

7676
scanQueue()
7777

78-
// Loop over all objects scanning each one only once for more objects to discover.
79-
do {
80-
do {
81-
// Require all implementors of discovered interfaces to be discovered or provided.
82-
handleDictionaryTypes(getAllObjectTypesImplementingDiscoveredInterfaces()) { "Object type '${it.name}' implements a known interface, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
83-
} while (scanQueue())
78+
// Loop over all objects scanning each one only once for more objects to discover. Each step only runs once the steps
79+
// above it have nothing left, and anything new starts over from the top.
80+
while (true) {
81+
// Require all implementors of discovered interfaces to be discovered or provided.
82+
handleDictionaryTypes(getAllObjectTypesImplementingDiscoveredInterfaces()) { "Object type '${it.name}' implements a known interface, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
83+
if (scanQueue()) continue
8484

8585
// Require all members of discovered unions to be discovered.
8686
handleDictionaryTypes(getAllObjectTypeMembersOfDiscoveredUnions()) { "Object type '${it.name}' is a member of a known union, but no class could be found for that type name. Please pass a class for type '${it.name}' in the parser's dictionary." }
87-
} while (scanQueue())
87+
if (scanQueue()) continue
88+
89+
// Look for unions and interfaces used only by interface fields last, so types that can be reached some other way
90+
// are still discovered in the same order as before. With includeUnusedTypes the unused types loop below already
91+
// picks these up one at a time, scanning in between, so leave that path unchanged.
92+
if (!options.includeUnusedTypes && handleInterfaceFieldTypes()) continue
93+
94+
break
95+
}
8896

8997
handleDirectives()
9098

@@ -270,6 +278,26 @@ internal class SchemaClassScanner(
270278
}.flatten().distinct()
271279
}
272280

281+
/**
282+
* Implementations can narrow an interface field to a member of its union or an implementor of its interface, so such a
283+
* union or interface may not be reachable from any object field. Returns whether any new types were found.
284+
*/
285+
private fun handleInterfaceFieldTypes(): Boolean {
286+
val newTypes = dictionary.keys.filterIsInstance<InterfaceTypeDefinition>().flatMap { iface ->
287+
iface.fieldDefinitions.mapNotNull { field ->
288+
val type = definitionsByName[(field.type.unwrap() as TypeName).name]
289+
if ((type is UnionTypeDefinition || type is InterfaceTypeDefinition) && !dictionary.containsKey(type)) {
290+
type to FieldTypeReference("${iface.name}.${field.name}")
291+
} else {
292+
null
293+
}
294+
}
295+
}
296+
297+
newTypes.forEach { (type, reference) -> handleFoundType(type, null, reference) }
298+
return newTypes.isNotEmpty()
299+
}
300+
273301
private fun handleDictionaryTypes(types: List<TypeDefinition<*>>, failureMessage: (TypeDefinition<*>) -> String) {
274302
types.forEach { type ->
275303
val dictionaryContainsType = dictionary.filter { it.key.name == type.name }.isNotEmpty()

0 commit comments

Comments
 (0)