Skip to content

Commit 75136a5

Browse files
authored
Support recurise types (#43)
1 parent 59396a0 commit 75136a5

File tree

3 files changed

+68
-22
lines changed

3 files changed

+68
-22
lines changed

src/main/kotlin/com/expedia/graphql/schema/generator/SchemaGenerator.kt

Lines changed: 28 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import graphql.schema.GraphQLObjectType
2727
import graphql.schema.GraphQLOutputType
2828
import graphql.schema.GraphQLSchema
2929
import graphql.schema.GraphQLType
30+
import graphql.schema.GraphQLTypeReference
3031
import graphql.schema.GraphQLUnionType
3132
import org.reflections.Reflections
3233
import kotlin.reflect.KClass
@@ -240,33 +241,39 @@ internal class SchemaGenerator(
240241
GraphQLList.list(graphQLTypeOf(type.arguments.first().type!!, inputType))
241242

242243
private fun objectType(kClass: KClass<*>, interfaceType: GraphQLInterfaceType? = null): GraphQLType {
243-
val builder = GraphQLObjectType.newObject()
244+
if (cache.isTypeUnderConstruction(kClass)) {
245+
return GraphQLTypeReference.typeRef(kClass.simpleName)
246+
} else {
247+
cache.putTypeUnderConstruction(kClass)
244248

245-
builder.name(kClass.simpleName)
246-
builder.description(kClass.graphQLDescription())
249+
val builder = GraphQLObjectType.newObject()
247250

248-
kClass.directives().map {
249-
builder.withDirective(it)
250-
directives.add(it)
251-
}
251+
builder.name(kClass.simpleName)
252+
builder.description(kClass.graphQLDescription())
252253

253-
if (interfaceType != null) {
254-
builder.withInterface(interfaceType)
255-
} else {
256-
kClass.superclasses
257-
.asSequence()
258-
.filter { it.canBeGraphQLInterface() && !it.canBeGraphQLUnion() }
259-
.map { objectFromReflection(it.createType(), false) as GraphQLInterfaceType }
260-
.forEach { builder.withInterface(it) }
261-
}
254+
kClass.directives().map {
255+
builder.withDirective(it)
256+
directives.add(it)
257+
}
262258

263-
kClass.getValidProperties(config.hooks)
264-
.forEach { builder.field(property(it)) }
259+
if (interfaceType != null) {
260+
builder.withInterface(interfaceType)
261+
} else {
262+
kClass.superclasses
263+
.asSequence()
264+
.filter { it.canBeGraphQLInterface() && !it.canBeGraphQLUnion() }
265+
.map { objectFromReflection(it.createType(), false) as GraphQLInterfaceType }
266+
.forEach { builder.withInterface(it) }
267+
}
265268

266-
kClass.getValidFunctions(config.hooks)
267-
.forEach { builder.field(function(it)) }
269+
kClass.getValidProperties(config.hooks)
270+
.forEach { builder.field(property(it)) }
268271

269-
return builder.build()
272+
kClass.getValidFunctions(config.hooks)
273+
.forEach { builder.field(function(it)) }
274+
275+
return builder.build()
276+
}
270277
}
271278

272279
private fun inputObjectType(kClass: KClass<*>): GraphQLType {

src/main/kotlin/com/expedia/graphql/schema/generator/TypesCache.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
@file:Suppress("TooManyFunctions")
12
package com.expedia.graphql.schema.generator
23

34
import com.expedia.graphql.schema.exceptions.ConflictingTypesException
@@ -16,6 +17,7 @@ internal data class TypesCacheKey(val type: KType, val inputType: Boolean)
1617
internal class TypesCache(private val supportedPackages: List<String>) {
1718

1819
private val cache: MutableMap<String, KGraphQLType> = mutableMapOf()
20+
private val typeUnderConstruction: MutableSet<KClass<*>> = mutableSetOf()
1921

2022
fun get(cacheKey: TypesCacheKey): GraphQLType? {
2123
val cacheKeyString = getCacheKeyString(cacheKey)
@@ -33,7 +35,10 @@ internal class TypesCache(private val supportedPackages: List<String>) {
3335
return null
3436
}
3537

36-
fun put(key: TypesCacheKey, kGraphQLType: KGraphQLType) = cache.put(getCacheKeyString(key), kGraphQLType)
38+
fun put(key: TypesCacheKey, kGraphQLType: KGraphQLType): KGraphQLType? {
39+
typeUnderConstruction.remove(kGraphQLType.kClass)
40+
return cache.put(getCacheKeyString(key), kGraphQLType)
41+
}
3742

3843
fun doesNotContainGraphQLType(graphQLType: GraphQLType) =
3944
cache.none { (_, v) -> v.graphQLType.name == graphQLType.name }
@@ -82,4 +87,8 @@ internal class TypesCache(private val supportedPackages: List<String>) {
8287
throw TypeNotSupportedException(qualifiedName, supportedPackages)
8388
}
8489
}
90+
91+
fun putTypeUnderConstruction(kClass: KClass<*>) = typeUnderConstruction.add(kClass)
92+
93+
fun isTypeUnderConstruction(kClass: KClass<*>) = typeUnderConstruction.contains(kClass)
8594
}

src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@ import com.expedia.graphql.schema.testSchemaConfig
1010
import com.expedia.graphql.toSchema
1111
import graphql.GraphQL
1212
import graphql.schema.GraphQLObjectType
13+
import org.junit.jupiter.api.Assertions.assertNull
1314
import org.junit.jupiter.api.Assertions.assertThrows
1415
import java.net.CookieManager
1516
import kotlin.test.Test
1617
import kotlin.test.assertEquals
18+
import kotlin.test.assertNotNull
1719
import kotlin.test.assertTrue
1820

1921
@Suppress("Detekt.UnusedPrivateMember", "Detekt.FunctionOnlyReturningConstant")
@@ -207,6 +209,25 @@ class SchemaGeneratorTest {
207209
}
208210
}
209211

212+
@Test
213+
fun `SchemaGenerator supports type references`() {
214+
val schema = toSchema(queries = listOf(TopLevelObjectDef(QueryWithParentChildRelationship())), config = testSchemaConfig)
215+
216+
val graphQL = GraphQL.newGraphQL(schema).build()
217+
val result = graphQL.execute("{ query { name children { name } } }")
218+
val data = result.getData<Map<String, Map<String, Any>>>()
219+
220+
assertNotNull(data)
221+
val res = data["query"]
222+
assertEquals("Bob", res?.get("name").toString())
223+
val bobChildren = res?.get("children") as? List<Map<String, Any>>
224+
assertNotNull(bobChildren)
225+
226+
val firstChild = bobChildren?.get(0)
227+
assertEquals("Alice", firstChild?.get("name"))
228+
assertNull(firstChild?.get("children"))
229+
}
230+
210231
class QueryObject {
211232
@GraphQLDescription("A GraphQL query method")
212233
fun query(@GraphQLDescription("A GraphQL value") value: Int): Geography = Geography(value, GeoType.CITY, listOf())
@@ -325,4 +346,13 @@ class SchemaGeneratorTest {
325346
@GraphQLDescription("A second conflicting GraphQL query method")
326347
fun type2() = com.expedia.graphql.conflicts.GeoType.CITY
327348
}
349+
350+
class QueryWithParentChildRelationship {
351+
fun query(): Person {
352+
val children = listOf(Person("Alice"))
353+
return Person("Bob", children)
354+
}
355+
}
356+
357+
data class Person(val name: String, val children: List<Person>? = null)
328358
}

0 commit comments

Comments
 (0)