Skip to content

Commit bde0e71

Browse files
smyrickamandaducrou
authored andcommitted
fix: recursive interfaces (#143)
Fixes #141
1 parent 6ebed14 commit bde0e71

File tree

5 files changed

+130
-5
lines changed

5 files changed

+130
-5
lines changed

src/main/kotlin/com/expedia/graphql/generator/types/InterfaceTypeBuilder.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
package com.expedia.graphql.generator.types
22

3-
import com.expedia.graphql.generator.extensions.getValidFunctions
4-
import com.expedia.graphql.generator.extensions.getValidProperties
5-
import com.expedia.graphql.generator.extensions.getGraphQLDescription
63
import com.expedia.graphql.generator.SchemaGenerator
74
import com.expedia.graphql.generator.TypeBuilder
5+
import com.expedia.graphql.generator.extensions.getGraphQLDescription
86
import com.expedia.graphql.generator.extensions.getSimpleName
7+
import com.expedia.graphql.generator.extensions.getValidFunctions
8+
import com.expedia.graphql.generator.extensions.getValidProperties
99
import graphql.TypeResolutionEnvironment
1010
import graphql.schema.GraphQLInterfaceType
1111
import graphql.schema.GraphQLType
@@ -33,10 +33,11 @@ internal class InterfaceTypeBuilder(generator: SchemaGenerator) : TypeBuilder(ge
3333
implementations.forEach {
3434
val objectType = generator.objectType(it.kotlin, interfaceType)
3535

36+
// Only update the state if the object is fully constructed and not a reference
3637
if (objectType !is GraphQLTypeReference) {
3738
state.additionalTypes.add(objectType)
39+
state.cache.removeTypeUnderConstruction(it.kotlin)
3840
}
39-
state.cache.removeTypeUnderConstruction(it.kotlin)
4041
}
4142

4243
interfaceType

src/main/kotlin/com/expedia/graphql/generator/types/ObjectTypeBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal class ObjectTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gener
3030
builder.withInterface(interfaceType)
3131
} else {
3232
kClass.getValidSuperclasses(config.hooks)
33-
.map { objectFromReflection(it.createType(), false) as? GraphQLInterfaceType }
33+
.mapNotNull { objectFromReflection(it.createType(), false) as? GraphQLInterfaceType }
3434
.forEach { builder.withInterface(it) }
3535
}
3636

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.expedia.graphql.integration
2+
3+
import com.expedia.graphql.TopLevelObject
4+
import com.expedia.graphql.testSchemaConfig
5+
import com.expedia.graphql.toSchema
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
9+
class NodeGraphTest {
10+
11+
@Test
12+
fun nodeGraph() {
13+
val root = Node(id = 0, value = "root", parent = null, children = emptyList())
14+
val nodeA = Node(id = 1, value = "A", parent = root, children = emptyList())
15+
val nodeB = Node(id = 2, value = "B", parent = root, children = emptyList())
16+
val nodeC = Node(id = 3, value = "C", parent = nodeB, children = emptyList())
17+
18+
root.children = listOf(nodeA, nodeB)
19+
nodeB.children = listOf(nodeC)
20+
21+
val queries = listOf(TopLevelObject(NodeQuery()))
22+
23+
val schema = toSchema(queries = queries, config = testSchemaConfig)
24+
25+
assertEquals(expected = 1, actual = schema.queryType.fieldDefinitions.size)
26+
assertEquals(expected = "nodeGraph", actual = schema.queryType.fieldDefinitions.first().name)
27+
}
28+
}
29+
30+
/**
31+
* Represents a recursive type that references itself,
32+
* similar to a tree node structure.
33+
*/
34+
data class Node(
35+
val id: Int,
36+
val value: String,
37+
val parent: Node?,
38+
var children: List<Node>
39+
)
40+
41+
class NodeQuery {
42+
43+
private val root = Node(id = 0, value = "root", parent = null, children = emptyList())
44+
private val nodeA = Node(id = 1, value = "A", parent = root, children = emptyList())
45+
private val nodeB = Node(id = 2, value = "B", parent = root, children = emptyList())
46+
private val nodeC = Node(id = 3, value = "C", parent = nodeB, children = emptyList())
47+
48+
init {
49+
root.children = listOf(nodeA, nodeB)
50+
nodeB.children = listOf(nodeC)
51+
}
52+
53+
fun nodeGraph() = root
54+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.expedia.graphql.integration
2+
3+
import com.expedia.graphql.TopLevelObject
4+
import com.expedia.graphql.testSchemaConfig
5+
import com.expedia.graphql.toSchema
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
9+
class RecursiveInterfaceTest {
10+
11+
@Test
12+
fun recursiveInterface() {
13+
val queries = listOf(TopLevelObject(RecursiveInterfaceQuery()))
14+
val schema = toSchema(queries = queries, config = testSchemaConfig)
15+
assertEquals(1, schema.queryType.fieldDefinitions.size)
16+
val field = schema.queryType.fieldDefinitions.first()
17+
assertEquals("getRoot", field.name)
18+
}
19+
}
20+
21+
class RecursiveInterfaceQuery {
22+
fun getRoot() = RecursiveInterfaceA()
23+
}
24+
25+
interface SomeInterface {
26+
val id: String
27+
}
28+
29+
class RecursiveInterfaceA : SomeInterface {
30+
override val id = "A"
31+
fun getB() = RecursiveInterfaceB()
32+
}
33+
34+
class RecursiveInterfaceB : SomeInterface {
35+
override val id = "B"
36+
fun getA() = RecursiveInterfaceA()
37+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.expedia.graphql.integration
2+
3+
import com.expedia.graphql.TopLevelObject
4+
import com.expedia.graphql.testSchemaConfig
5+
import com.expedia.graphql.toSchema
6+
import kotlin.test.Test
7+
import kotlin.test.assertEquals
8+
9+
class RecursiveUnionTest {
10+
11+
@Test
12+
fun recursiveUnion() {
13+
val queries = listOf(TopLevelObject(RecursiveUnionQuery()))
14+
val schema = toSchema(queries = queries, config = testSchemaConfig)
15+
assertEquals(1, schema.queryType.fieldDefinitions.size)
16+
val field = schema.queryType.fieldDefinitions.first()
17+
assertEquals("getRoot", field.name)
18+
}
19+
}
20+
21+
class RecursiveUnionQuery {
22+
fun getRoot() = RecursiveUnionA()
23+
}
24+
25+
interface SomeUnion
26+
27+
class RecursiveUnionA : SomeUnion {
28+
fun getB() = RecursiveUnionB()
29+
}
30+
31+
class RecursiveUnionB : SomeUnion {
32+
fun getA() = RecursiveUnionA()
33+
}

0 commit comments

Comments
 (0)