Skip to content

Commit 9d7f940

Browse files
author
smyrick
committed
fix: *generateGraphQLType hooks are now called on interfaces
Fixes #294 Interfaces now go through the internal TypeBuilder method 'graphQLTypeOf' instead of 'objectFromReflection'. This means we can make 'objectFromReflection' private. But the type may be (if not always) wrapped in a GraphQLNonNull which means we have to cast it back to the interface type before adding it to the ObjectBuilder.
1 parent 4c2652f commit 9d7f940

File tree

9 files changed

+87
-24
lines changed

9 files changed

+87
-24
lines changed

graphql-kotlin-schema-generator/src/main/kotlin/com/expedia/graphql/generator/TypeBuilder.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ internal open class TypeBuilder constructor(protected val generator: SchemaGener
3232
return config.hooks.didGenerateGraphQLType(type, typeWithNullability)
3333
}
3434

35-
internal fun objectFromReflection(type: KType, inputType: Boolean): GraphQLType {
35+
private fun objectFromReflection(type: KType, inputType: Boolean): GraphQLType {
3636
val cacheKey = TypesCacheKey(type, inputType)
3737
val cachedType = state.cache.get(cacheKey)
3838

graphql-kotlin-schema-generator/src/main/kotlin/com/expedia/graphql/generator/extensions/graphQLExtensions.kt

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,18 @@ internal fun GraphQLDirectiveContainer.getAllDirectives(): List<GraphQLDirective
3939

4040
return mutableList
4141
}
42+
43+
/**
44+
* If the GraphQLType is of type T or GraphQLNonNull<T>, return the class as T.
45+
* Otherwise return null.
46+
*/
47+
internal inline fun <reified T : GraphQLType> GraphQLType.unwrapNonNullCast(): T? {
48+
return when (this) {
49+
is T -> this
50+
is GraphQLNonNull -> when (val wrappedType = this.wrappedType) {
51+
is T -> wrappedType
52+
else -> null
53+
}
54+
else -> null
55+
}
56+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,8 @@ internal class TypesCache(private val supportedPackages: List<String>) {
3939
val cacheKey = getCacheKeyString(key)
4040

4141
if (cacheKey != null) {
42-
typeUnderConstruction.remove(kGraphQLType.kClass)
43-
return cache.put(cacheKey, kGraphQLType)
42+
cache[cacheKey] = kGraphQLType
43+
return kGraphQLType
4444
}
4545

4646
return null

graphql-kotlin-schema-generator/src/main/kotlin/com/expedia/graphql/generator/types/InterfaceBuilder.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import graphql.schema.GraphQLInterfaceType
1212
import graphql.schema.GraphQLType
1313
import graphql.schema.GraphQLTypeReference
1414
import kotlin.reflect.KClass
15+
import kotlin.reflect.full.createType
1516

1617
internal class InterfaceBuilder(generator: SchemaGenerator) : TypeBuilder(generator) {
1718
internal fun interfaceType(kClass: KClass<*>): GraphQLType {
@@ -31,15 +32,17 @@ internal class InterfaceBuilder(generator: SchemaGenerator) : TypeBuilder(genera
3132
kClass.getValidFunctions(config.hooks)
3233
.forEach { builder.field(generator.function(it, kClass.getSimpleName(), abstract = true)) }
3334

34-
val interfaceType = builder.build()
3535
val implementations = subTypeMapper.getSubTypesOf(kClass)
3636
implementations.forEach { implementation ->
37-
val objectType = generator.objectType(implementation.kotlin, interfaceType)
37+
val kType = implementation.kotlin.createType()
38+
val objectType = graphQLTypeOf(kType)
3839
// skip objects currently under construction
3940
if (objectType !is GraphQLTypeReference) {
4041
state.additionalTypes.add(objectType)
4142
}
4243
}
44+
45+
val interfaceType = builder.build()
4346
codeRegistry.typeResolver(interfaceType) { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject<Any>().javaClass.kotlin.getSimpleName()) }
4447
config.hooks.onRewireGraphQLType(interfaceType).safeCast()
4548
}

graphql-kotlin-schema-generator/src/main/kotlin/com/expedia/graphql/generator/types/ObjectBuilder.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import com.expedia.graphql.generator.extensions.getValidFunctions
88
import com.expedia.graphql.generator.extensions.getValidProperties
99
import com.expedia.graphql.generator.extensions.getValidSuperclasses
1010
import com.expedia.graphql.generator.extensions.safeCast
11+
import com.expedia.graphql.generator.extensions.unwrapNonNullCast
1112
import graphql.schema.GraphQLInterfaceType
1213
import graphql.schema.GraphQLObjectType
1314
import graphql.schema.GraphQLType
@@ -34,10 +35,14 @@ internal class ObjectBuilder(generator: SchemaGenerator) : TypeBuilder(generator
3435
builder.withInterface(GraphQLTypeReference(interfaceType.name))
3536
} else {
3637
kClass.getValidSuperclasses(config.hooks)
37-
.map { objectFromReflection(it.createType(), false) }
38+
.map { graphQLTypeOf(type = it.createType(), inputType = false, annotatedAsID = false) }
3839
.forEach {
39-
when (it) {
40-
is GraphQLInterfaceType -> builder.withInterface(it)
40+
val possibleTypeReferece = it.unwrapNonNullCast<GraphQLTypeReference>()
41+
val possibleInterface = it.unwrapNonNullCast<GraphQLInterfaceType>()
42+
43+
when {
44+
possibleTypeReferece != null -> builder.withInterface(possibleTypeReferece)
45+
possibleInterface != null -> builder.withInterface(possibleInterface)
4146
}
4247
}
4348
}

graphql-kotlin-schema-generator/src/main/kotlin/com/expedia/graphql/generator/types/UnionBuilder.kt

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import com.expedia.graphql.generator.SchemaGenerator
44
import com.expedia.graphql.generator.TypeBuilder
55
import com.expedia.graphql.generator.extensions.getGraphQLDescription
66
import com.expedia.graphql.generator.extensions.getSimpleName
7-
import com.expedia.graphql.generator.state.TypesCacheKey
7+
import com.expedia.graphql.generator.extensions.unwrapNonNullCast
88
import graphql.TypeResolutionEnvironment
99
import graphql.schema.GraphQLObjectType
1010
import graphql.schema.GraphQLType
@@ -25,15 +25,20 @@ internal class UnionBuilder(generator: SchemaGenerator) : TypeBuilder(generator)
2525
}
2626

2727
val implementations = subTypeMapper.getSubTypesOf(kClass)
28-
implementations.forEach {
29-
val objectType = state.cache.get(TypesCacheKey(it.kotlin.createType(), false))
30-
?: generator.objectType(it.kotlin)
28+
implementations.map {
29+
val kType = it.kotlin.createType()
30+
graphQLTypeOf(kType, inputType = false, annotatedAsID = false)
31+
}
32+
.forEach {
33+
val possibleTypeReferece = it.unwrapNonNullCast<GraphQLTypeReference>()
34+
val possibleObjectType = it.unwrapNonNullCast<GraphQLObjectType>()
3135

32-
when (objectType) {
33-
is GraphQLTypeReference -> builder.possibleType(objectType)
34-
is GraphQLObjectType -> builder.possibleType(objectType)
36+
when {
37+
possibleTypeReferece != null -> builder.possibleType(possibleTypeReferece)
38+
possibleObjectType != null -> builder.possibleType(possibleObjectType)
3539
}
3640
}
41+
3742
val unionType = builder.build()
3843
codeRegistry.typeResolver(unionType) { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject<Any>().javaClass.kotlin.getSimpleName()) }
3944
config.hooks.onRewireGraphQLType(unionType)

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/generator/extensions/GraphQLExtensionsTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import graphql.schema.GraphQLFieldDefinition
99
import graphql.schema.GraphQLInterfaceType
1010
import graphql.schema.GraphQLNonNull
1111
import graphql.schema.GraphQLObjectType
12+
import graphql.schema.GraphQLScalarType
1213
import graphql.schema.GraphQLType
1314
import graphql.schema.GraphQLTypeVisitor
1415
import graphql.util.TraversalControl
@@ -20,6 +21,8 @@ import kotlin.reflect.KType
2021
import kotlin.test.assertEquals
2122
import kotlin.test.assertFailsWith
2223
import kotlin.test.assertFalse
24+
import kotlin.test.assertNotNull
25+
import kotlin.test.assertNull
2326
import kotlin.test.assertTrue
2427

2528
@Suppress("Detekt.LargeClass")
@@ -127,4 +130,34 @@ internal class GraphQLExtensionsTest {
127130
type.safeCast<GraphQLInterfaceType>()
128131
}
129132
}
133+
134+
@Test
135+
fun `unwrapNonNullCast returns the type on valid types`() {
136+
val objectType: GraphQLType = GraphQLObjectType.newObject().name("name").description("description").build()
137+
val unwrappedType = objectType.unwrapNonNullCast<GraphQLObjectType>()
138+
assertNotNull(unwrappedType)
139+
}
140+
141+
@Test
142+
fun `unwrapNonNullCast returns the wrapped type on GraphQLNonNull types`() {
143+
val objectType: GraphQLType = GraphQLObjectType.newObject().name("name").description("description").build()
144+
val nonNull = GraphQLNonNull.nonNull(objectType)
145+
val unwrappedType = nonNull.unwrapNonNullCast<GraphQLObjectType>()
146+
assertNotNull(unwrappedType)
147+
}
148+
149+
@Test
150+
fun `unwrapNonNullCast returns null on mismatched types`() {
151+
val objectType: GraphQLType = GraphQLObjectType.newObject().name("name").description("description").build()
152+
val unwrappedType = objectType.unwrapNonNullCast<GraphQLScalarType>()
153+
assertNull(unwrappedType)
154+
}
155+
156+
@Test
157+
fun `unwrapNonNullCast returns null on mismatched GraphQLNonNull types`() {
158+
val objectType: GraphQLType = GraphQLObjectType.newObject().name("name").description("description").build()
159+
val nonNull = GraphQLNonNull.nonNull(objectType)
160+
val unwrappedType = nonNull.unwrapNonNullCast<GraphQLScalarType>()
161+
assertNull(unwrappedType)
162+
}
130163
}

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/hooks/SchemaGeneratorHooksTest.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,13 +95,12 @@ class SchemaGeneratorHooksTest {
9595

9696
val hooks = MockSchemaGeneratorHooks()
9797
toSchema(
98-
queries = listOf(TopLevelObject(TestQuery())),
98+
queries = listOf(TopLevelObject(TestInterfaceQuery())),
9999
config = getTestSchemaConfigWithHooks(hooks)
100100
)
101101
assertTrue(hooks.seenTypes.contains(RandomData::class.createType()))
102102
assertTrue(hooks.seenTypes.contains(SomeData::class.createType()))
103-
// TODO bug: didGenerateGraphQLType hook is not applied on the object types build from the interface
104-
// assertTrue(hooks.seenTypes.contains(OtherData::class.createType()))
103+
assertTrue(hooks.seenTypes.contains(OtherData::class.createType()))
105104
}
106105

107106
@Test
@@ -199,6 +198,9 @@ class SchemaGeneratorHooksTest {
199198

200199
class TestQuery {
201200
fun query(): SomeData = SomeData("someData", 0)
201+
}
202+
203+
class TestInterfaceQuery {
202204
fun randomQuery(): RandomData = if (Random.nextBoolean()) {
203205
SomeData("random", 1)
204206
} else {

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/test/integration/RecursiveInterfaceTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,29 @@ class RecursiveInterfaceTest {
2828
}
2929

3030
class RecursiveInterfaceQuery {
31-
fun getRoot() = RecursiveInterfaceA()
31+
fun getRoot() = RecursiveClassA()
3232
}
3333

3434
class InterfaceWithSelfFieldQuery {
3535
fun getInterface() = InterfaceWithSelfFieldB()
3636
}
3737

38-
interface SomeInterface {
38+
interface SomeInterfaceWithId {
3939
val id: String
4040
}
4141

4242
interface InterfaceWithSelfField {
4343
val parent: InterfaceWithSelfField?
4444
}
4545

46-
class RecursiveInterfaceA : SomeInterface {
46+
class RecursiveClassA : SomeInterfaceWithId {
4747
override val id = "A"
48-
fun getB() = RecursiveInterfaceB()
48+
fun getB() = RecursiveClassB()
4949
}
5050

51-
class RecursiveInterfaceB : SomeInterface {
51+
class RecursiveClassB : SomeInterfaceWithId {
5252
override val id = "B"
53-
fun getA() = RecursiveInterfaceA()
53+
fun getA() = RecursiveClassA()
5454
}
5555

5656
class InterfaceWithSelfFieldA : InterfaceWithSelfField {

0 commit comments

Comments
 (0)