Skip to content
Open
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
23 changes: 23 additions & 0 deletions src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,9 @@ internal class SchemaClassScanner(
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." }
if (scanQueue()) continue

// Discover unions listed as members of discovered unions, even when no field returns them.
if (handleNestedUnions()) continue

// Require all members of discovered unions to be discovered.
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." }
if (scanQueue()) continue
Expand Down Expand Up @@ -359,6 +362,22 @@ internal class SchemaClassScanner(
}.flatten().distinct()
}

/**
* Unions can list other unions as members. Returns whether any new unions were found.
*/
private fun handleNestedUnions(): Boolean {
val newUnions = dictionary.keys.filterIsInstance<UnionTypeDefinition>().flatMap { union ->
union.withExtensions(typeExtensions).flatMap { it.memberTypes }.filterIsInstance<TypeName>()
.map { definitionsByName[it.name] }
.filterIsInstance<UnionTypeDefinition>()
.filter { !dictionary.containsKey(it) }
.map { it to UnionMemberReference(union) }
}

newUnions.forEach { (type, reference) -> handleFoundType(type, null, reference) }
return newUnions.isNotEmpty()
}

/**
* Implementations can narrow an interface field to a member of its union or an implementor of its interface, so such a
* union or interface may not be reachable from any object field. Returns whether any new types were found.
Expand Down Expand Up @@ -755,6 +774,10 @@ internal class SchemaClassScanner(
override fun getDescription() = "interface declarations of ${type.name}"
}

private class UnionMemberReference(private val union: UnionTypeDefinition) : Reference() {
override fun getDescription() = "member of union ${union.name}"
}

private class SealedSubclassReference(private val sealedType: JavaType) : Reference() {
override fun getDescription() = "subclass of sealed type ${sealedType.typeName}"
}
Expand Down
8 changes: 4 additions & 4 deletions src/test/kotlin/graphql/kickstart/tools/TypeExtensionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,17 @@ class TypeExtensionTest {
}

@Test
fun `union extensions can add nested unions`() {
fun `union extensions can add nested unions that no field returns`() {
val schema = parser(
"""
type Query {
fooBar: FooBar
barBaz: BarBaz
}

union FooBar = Foo
extend union FooBar = BarBaz
union BarBaz = Bar | Baz
union BarBaz = Bar | BazUnion
union BazUnion = Baz

$fooBarBaz
""") {
Expand All @@ -111,6 +111,7 @@ class TypeExtensionTest {
.assertEveryFieldResolves()

assertEquals((schema.getType("FooBar") as GraphQLUnionType).types.map { it.name }, listOf("Foo", "Bar", "Baz"))
assertEquals((schema.getType("BarBaz") as GraphQLUnionType).types.map { it.name }, listOf("Bar", "Baz"))
}

@Test
Expand Down Expand Up @@ -475,7 +476,6 @@ class TypeExtensionTest {

class Query : GraphQLQueryResolver {
fun fooBar(): FooBar = Baz("baz")
fun barBaz(): FooBar? = null
fun color(): Color = Color.GREEN
fun node(): Node = Item("1", "item")
fun search(filter: Filter?): String? = null
Expand Down
Loading