Skip to content

Commit 57ce7eb

Browse files
authored
Merge branch 'master' into federation_batch
2 parents 9bff430 + 7ec9140 commit 57ce7eb

File tree

17 files changed

+160
-68
lines changed

17 files changed

+160
-68
lines changed

graphql-kotlin-federation/src/main/kotlin/com/expedia/graphql/federation/FederatedSchemaGeneratorHooks.kt

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import kotlin.reflect.full.findAnnotation
2727
open class FederatedSchemaGeneratorHooks(private val federatedTypeRegistry: FederatedTypeRegistry) : SchemaGeneratorHooks {
2828
private val directiveRegex = "(^#.+$[\\r\\n])?^directive @\\w+.+$[\\r\\n]*".toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE))
2929
private val scalarRegex = "(^#.+$[\\r\\n])?^scalar (_FieldSet|_Any)$[\\r\\n]*".toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE))
30+
private val emptyQuery = "^type Query \\{$\\s+^\\}$\\s+".toRegex(setOf(RegexOption.MULTILINE, RegexOption.IGNORE_CASE))
3031
private val validator = FederatedSchemaValidator()
3132

3233
override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier) {
@@ -60,10 +61,16 @@ open class FederatedSchemaGeneratorHooks(private val federatedTypeRegistry: Fede
6061
val entityField = generateEntityFieldDefinition(entityTypeNames)
6162
federatedQuery.field(entityField)
6263

63-
// SDL returned by _service query should not contain directives or new scalars
64-
val sdl = originalSchema.print()
64+
// SDL returned by _service query should NOT contain
65+
// - default schema definition
66+
// - empty Query type
67+
// - directives
68+
// - new scalars
69+
val sdl = originalSchema.print(includeDefaultSchemaDefinition = false)
6570
.replace(directiveRegex, "")
6671
.replace(scalarRegex, "")
72+
.replace(emptyQuery, "")
73+
.replace("type ${originalQuery.name}", "type ${originalQuery.name} @extends")
6774
.trim()
6875
federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, SERVICE_FIELD_DEFINITION.name), DataFetcher { _Service(sdl) })
6976
federatedCodeRegistry.dataFetcher(FieldCoordinates.coordinates(originalQuery.name, entityField.name), EntityResolver(federatedTypeRegistry))

graphql-kotlin-federation/src/test/kotlin/com/expedia/graphql/federation/execution/ServiceQueryResolverTest.kt

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,7 @@ import kotlin.test.assertEquals
1010
import kotlin.test.assertNotNull
1111

1212
// SDL is returned without _entity and _service queries
13-
const val FEDERATED_SERVICE_SDL = """schema {
14-
query: Query
15-
}
16-
13+
const val FEDERATED_SERVICE_SDL = """
1714
interface Product @extends @key(fields : "id") {
1815
id: String! @external
1916
reviews: [Review!]!
@@ -27,9 +24,6 @@ type Book implements Product @extends @key(fields : "id") {
2724
weight: Float! @external
2825
}
2926
30-
type Query {
31-
}
32-
3327
type Review {
3428
body: String!
3529
id: String!

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import com.expedia.graphql.generator.types.SubscriptionBuilder
1818
import com.expedia.graphql.generator.types.UnionBuilder
1919
import graphql.schema.GraphQLCodeRegistry
2020
import graphql.schema.GraphQLDirective
21-
import graphql.schema.GraphQLInterfaceType
2221
import graphql.schema.GraphQLSchema
2322
import java.lang.reflect.Field
2423
import kotlin.reflect.KAnnotatedElement
@@ -76,8 +75,8 @@ open class SchemaGenerator(val config: SchemaGeneratorConfig) {
7675
open fun listType(type: KType, inputType: Boolean) =
7776
listTypeBuilder.listType(type, inputType)
7877

79-
open fun objectType(kClass: KClass<*>, interfaceType: GraphQLInterfaceType? = null) =
80-
objectTypeBuilder.objectType(kClass, interfaceType)
78+
open fun objectType(kClass: KClass<*>) =
79+
objectTypeBuilder.objectType(kClass)
8180

8281
open fun inputObjectType(kClass: KClass<*>) =
8382
inputObjectTypeBuilder.inputObjectType(kClass)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@ internal class SubTypeMapper(supportedPackages: List<String>) {
77

88
private val reflections = Reflections(supportedPackages)
99

10-
fun getSubTypesOf(kclass: KClass<*>): List<Class<*>> = reflections.getSubTypesOf(kclass.java).filterNot { it.kotlin.isAbstract }
10+
fun getSubTypesOf(kclass: KClass<*>): List<KClass<*>> = reflections.getSubTypesOf(kclass.java).filterNot { it.kotlin.isAbstract }.map { it.kotlin }
1111
}

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import com.expedia.graphql.generator.state.TypesCacheKey
1313
import graphql.schema.GraphQLCodeRegistry
1414
import graphql.schema.GraphQLType
1515
import graphql.schema.GraphQLTypeReference
16+
import graphql.schema.GraphQLTypeUtil
1617
import kotlin.reflect.KClass
1718
import kotlin.reflect.KType
1819

@@ -28,11 +29,17 @@ internal open class TypeBuilder constructor(protected val generator: SchemaGener
2829
?: generator.scalarType(type, annotatedAsID)
2930
?: objectFromReflection(type, inputType)
3031

31-
val typeWithNullability = graphQLType.wrapInNonNull(type)
32-
return config.hooks.didGenerateGraphQLType(type, typeWithNullability)
32+
// Do not call the hook on GraphQLTypeReference as we have not generated the type yet
33+
val unwrappedType = GraphQLTypeUtil.unwrapNonNull(graphQLType)
34+
if (unwrappedType !is GraphQLTypeReference) {
35+
val typeWithNullability = graphQLType.wrapInNonNull(type)
36+
return config.hooks.didGenerateGraphQLType(type, typeWithNullability)
37+
}
38+
39+
return graphQLType
3340
}
3441

35-
internal fun objectFromReflection(type: KType, inputType: Boolean): GraphQLType {
42+
private fun objectFromReflection(type: KType, inputType: Boolean): GraphQLType {
3643
val cacheKey = TypesCacheKey(type, inputType)
3744
val cachedType = state.cache.get(cacheKey)
3845

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.expedia.graphql.generator.extensions
2+
3+
import kotlin.reflect.KFunction
4+
import kotlin.reflect.KParameter
5+
import kotlin.reflect.full.valueParameters
6+
7+
internal fun KFunction<*>.getValidArguments(): List<KParameter> =
8+
this.valueParameters
9+
.filterNot { it.isGraphQLContext() }
10+
.filterNot { it.isGraphQLIgnored() }
11+
.filterNot { it.isDataFetchingEnvironment() }

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/FunctionBuilder.kt

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ import com.expedia.graphql.generator.extensions.getGraphQLDescription
1010
import com.expedia.graphql.generator.extensions.getKClass
1111
import com.expedia.graphql.generator.extensions.getName
1212
import com.expedia.graphql.generator.extensions.getTypeOfFirstArgument
13-
import com.expedia.graphql.generator.extensions.isDataFetchingEnvironment
14-
import com.expedia.graphql.generator.extensions.isGraphQLContext
15-
import com.expedia.graphql.generator.extensions.isGraphQLIgnored
13+
import com.expedia.graphql.generator.extensions.getValidArguments
1614
import com.expedia.graphql.generator.extensions.isInterface
1715
import com.expedia.graphql.generator.extensions.safeCast
1816
import graphql.schema.FieldCoordinates
@@ -25,7 +23,6 @@ import kotlin.reflect.KFunction
2523
import kotlin.reflect.KParameter
2624
import kotlin.reflect.KType
2725
import kotlin.reflect.full.isSubclassOf
28-
import kotlin.reflect.full.valueParameters
2926

3027
internal class FunctionBuilder(generator: SchemaGenerator) : TypeBuilder(generator) {
3128

@@ -44,10 +41,7 @@ internal class FunctionBuilder(generator: SchemaGenerator) : TypeBuilder(generat
4441
builder.withDirective(it)
4542
}
4643

47-
fn.valueParameters
48-
.filterNot { it.isGraphQLContext() }
49-
.filterNot { it.isGraphQLIgnored() }
50-
.filterNot { it.isDataFetchingEnvironment() }
44+
fn.getValidArguments()
5145
.forEach {
5246
// deprecation of arguments is currently unsupported: https://github.com/facebook/graphql/issues/197
5347
builder.argument(argument(it))

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

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import graphql.TypeResolutionEnvironment
1111
import graphql.schema.GraphQLInterfaceType
1212
import graphql.schema.GraphQLType
1313
import graphql.schema.GraphQLTypeReference
14+
import graphql.schema.GraphQLTypeUtil
1415
import kotlin.reflect.KClass
16+
import kotlin.reflect.full.createType
1517

1618
internal class InterfaceBuilder(generator: SchemaGenerator) : TypeBuilder(generator) {
1719
internal fun interfaceType(kClass: KClass<*>): GraphQLType {
@@ -31,15 +33,17 @@ internal class InterfaceBuilder(generator: SchemaGenerator) : TypeBuilder(genera
3133
kClass.getValidFunctions(config.hooks)
3234
.forEach { builder.field(generator.function(it, kClass.getSimpleName(), abstract = true)) }
3335

34-
val interfaceType = builder.build()
35-
val implementations = subTypeMapper.getSubTypesOf(kClass)
36-
implementations.forEach { implementation ->
37-
val objectType = generator.objectType(implementation.kotlin, interfaceType)
38-
// skip objects currently under construction
39-
if (objectType !is GraphQLTypeReference) {
40-
state.additionalTypes.add(objectType)
36+
subTypeMapper.getSubTypesOf(kClass)
37+
.map { graphQLTypeOf(it.createType()) }
38+
.forEach {
39+
// Do not add objects currently under construction to the additional types
40+
val unwrappedType = GraphQLTypeUtil.unwrapNonNull(it)
41+
if (unwrappedType !is GraphQLTypeReference) {
42+
state.additionalTypes.add(it)
43+
}
4144
}
42-
}
45+
46+
val interfaceType = builder.build()
4347
codeRegistry.typeResolver(interfaceType) { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject<Any>().javaClass.kotlin.getSimpleName()) }
4448
config.hooks.onRewireGraphQLType(interfaceType).safeCast()
4549
}

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

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import graphql.schema.GraphQLInterfaceType
1212
import graphql.schema.GraphQLObjectType
1313
import graphql.schema.GraphQLType
1414
import graphql.schema.GraphQLTypeReference
15+
import graphql.schema.GraphQLTypeUtil
1516
import kotlin.reflect.KClass
1617
import kotlin.reflect.full.createType
1718

1819
internal class ObjectBuilder(generator: SchemaGenerator) : TypeBuilder(generator) {
1920

20-
internal fun objectType(kClass: KClass<*>, interfaceType: GraphQLInterfaceType? = null): GraphQLType {
21+
internal fun objectType(kClass: KClass<*>): GraphQLType {
2122
return state.cache.buildIfNotUnderConstruction(kClass) {
2223
val builder = GraphQLObjectType.newObject()
2324

@@ -29,18 +30,14 @@ internal class ObjectBuilder(generator: SchemaGenerator) : TypeBuilder(generator
2930
builder.withDirective(it)
3031
}
3132

32-
if (interfaceType != null) {
33-
// invoked from the interface builder which can still be modified by the hooks
34-
builder.withInterface(GraphQLTypeReference(interfaceType.name))
35-
} else {
36-
kClass.getValidSuperclasses(config.hooks)
37-
.map { objectFromReflection(it.createType(), false) }
38-
.forEach {
39-
when (it) {
40-
is GraphQLInterfaceType -> builder.withInterface(it)
41-
}
33+
kClass.getValidSuperclasses(config.hooks)
34+
.map { graphQLTypeOf(it.createType()) }
35+
.forEach {
36+
when (val unwrappedType = GraphQLTypeUtil.unwrapNonNull(it)) {
37+
is GraphQLTypeReference -> builder.withInterface(unwrappedType)
38+
is GraphQLInterfaceType -> builder.withInterface(unwrappedType)
4239
}
43-
}
40+
}
4441

4542
kClass.getValidProperties(config.hooks)
4643
.forEach { builder.field(generator.property(it, kClass)) }

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

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ 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
87
import graphql.TypeResolutionEnvironment
98
import graphql.schema.GraphQLObjectType
109
import graphql.schema.GraphQLType
1110
import graphql.schema.GraphQLTypeReference
11+
import graphql.schema.GraphQLTypeUtil
1212
import graphql.schema.GraphQLUnionType
1313
import kotlin.reflect.KClass
1414
import kotlin.reflect.full.createType
@@ -24,16 +24,15 @@ internal class UnionBuilder(generator: SchemaGenerator) : TypeBuilder(generator)
2424
builder.withDirective(it)
2525
}
2626

27-
val implementations = subTypeMapper.getSubTypesOf(kClass)
28-
implementations.forEach {
29-
val objectType = state.cache.get(TypesCacheKey(it.kotlin.createType(), false))
30-
?: generator.objectType(it.kotlin)
31-
32-
when (objectType) {
33-
is GraphQLTypeReference -> builder.possibleType(objectType)
34-
is GraphQLObjectType -> builder.possibleType(objectType)
27+
subTypeMapper.getSubTypesOf(kClass)
28+
.map { graphQLTypeOf(it.createType()) }
29+
.forEach {
30+
when (val unwrappedType = GraphQLTypeUtil.unwrapNonNull(it)) {
31+
is GraphQLTypeReference -> builder.possibleType(unwrappedType)
32+
is GraphQLObjectType -> builder.possibleType(unwrappedType)
33+
}
3534
}
36-
}
35+
3736
val unionType = builder.build()
3837
codeRegistry.typeResolver(unionType) { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject<Any>().javaClass.kotlin.getSimpleName()) }
3938
config.hooks.onRewireGraphQLType(unionType)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.expedia.graphql.generator.extensions
2+
3+
import com.expedia.graphql.annotations.GraphQLContext
4+
import com.expedia.graphql.annotations.GraphQLIgnore
5+
import graphql.schema.DataFetchingEnvironment
6+
import org.junit.jupiter.api.Test
7+
import kotlin.test.assertEquals
8+
9+
internal class KFunctionExtensionsKtTest {
10+
11+
@Test
12+
fun getValidArguments() {
13+
val args = TestingClass::happyPath.getValidArguments()
14+
assertEquals(expected = 1, actual = args.size)
15+
assertEquals(expected = "color", actual = args.first().getName())
16+
}
17+
18+
@Test
19+
fun `getValidArguments should ignore @GraphQLIgnore`() {
20+
val args = TestingClass::ignored.getValidArguments()
21+
assertEquals(expected = 1, actual = args.size)
22+
assertEquals(expected = "notIgnored", actual = args.first().getName())
23+
}
24+
25+
@Test
26+
fun `getValidArguments should ignore @GraphQLContext`() {
27+
val args = TestingClass::context.getValidArguments()
28+
assertEquals(expected = 1, actual = args.size)
29+
assertEquals(expected = "notContext", actual = args.first().getName())
30+
}
31+
32+
@Test
33+
fun `getValidArguments should ignore DataFetchingEnvironment`() {
34+
val args = TestingClass::dataFetchingEnvironment.getValidArguments()
35+
assertEquals(expected = 1, actual = args.size)
36+
assertEquals(expected = "notEnvironment", actual = args.first().getName())
37+
}
38+
39+
private class TestingClass {
40+
fun happyPath(color: String) = "You're color is $color"
41+
42+
fun ignored(@GraphQLIgnore ignoredArg: String, notIgnored: String) = "$ignoredArg and $notIgnored"
43+
44+
fun context(@GraphQLContext contextArg: String, notContext: String) = "$contextArg and $notContext"
45+
46+
fun dataFetchingEnvironment(environment: DataFetchingEnvironment, notEnvironment: String): String = "${environment.field.name} and $notEnvironment"
47+
}
48+
}

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/generator/types/TypeTestHelper.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import com.expedia.graphql.generator.state.SchemaGeneratorState
1111
import com.expedia.graphql.generator.state.TypesCache
1212
import com.expedia.graphql.hooks.SchemaGeneratorHooks
1313
import graphql.schema.GraphQLCodeRegistry
14-
import graphql.schema.GraphQLInterfaceType
1514
import io.mockk.every
1615
import io.mockk.mockk
1716
import io.mockk.spyk
@@ -48,6 +47,7 @@ internal open class TypeTestHelper {
4847
private var directiveBuilder: DirectiveBuilder? = null
4948
private var functionBuilder: FunctionBuilder? = null
5049
private var propertyBuilder: PropertyBuilder? = null
50+
private var unionBuilder: UnionBuilder? = null
5151

5252
@BeforeTest
5353
fun setup() {
@@ -84,8 +84,8 @@ internal open class TypeTestHelper {
8484
}
8585

8686
objectBuilder = spyk(ObjectBuilder(generator))
87-
every { generator.objectType(any(), any()) } answers {
88-
objectBuilder!!.objectType(it.invocation.args[0] as KClass<*>, it.invocation.args[1] as GraphQLInterfaceType?)
87+
every { generator.objectType(any()) } answers {
88+
objectBuilder!!.objectType(it.invocation.args[0] as KClass<*>)
8989
}
9090

9191
listBuilder = spyk(ListBuilder(generator))
@@ -98,6 +98,11 @@ internal open class TypeTestHelper {
9898
interfaceBuilder!!.interfaceType(it.invocation.args[0] as KClass<*>)
9999
}
100100

101+
unionBuilder = spyk(UnionBuilder(generator))
102+
every { generator.unionType(any()) } answers {
103+
unionBuilder!!.unionType(it.invocation.args[0] as KClass<*>)
104+
}
105+
101106
directiveBuilder = spyk(DirectiveBuilder(generator))
102107
every { generator.directives(any()) } answers {
103108
val directives = directiveBuilder!!.directives(it.invocation.args[0] as KAnnotatedElement)

graphql-kotlin-schema-generator/src/test/kotlin/com/expedia/graphql/generator/types/UnionBuilderTest.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,15 @@ internal class UnionBuilderTest : TypeTestHelper() {
3333
@GraphQLName("StrawBerryCakeRenamed")
3434
private class StrawBerryCakeCustomName : CakeCustomName
3535

36+
private interface NestedUnionA
37+
38+
private interface NestedUnionB
39+
40+
private class NestedClass : NestedUnionA, NestedUnionB {
41+
fun getUnionA(): NestedUnionA = NestedClass()
42+
fun getUnionB(): NestedUnionB = NestedClass()
43+
}
44+
3645
@Test
3746
fun `Test naming`() {
3847
val result = builder.unionType(Cake::class) as? GraphQLUnionType
@@ -84,4 +93,14 @@ internal class UnionBuilderTest : TypeTestHelper() {
8493
assertNotNull(second)
8594
assertEquals(first.hashCode(), second.hashCode())
8695
}
96+
97+
@Test
98+
fun `verify nested classes resovle the type reference in the gererator`() {
99+
val cache = generator.state.cache
100+
assertTrue(cache.doesNotContain(NestedUnionA::class))
101+
102+
val unionType = builder.unionType(NestedUnionA::class) as? GraphQLUnionType
103+
assertNotNull(unionType)
104+
assertFalse(cache.doesNotContain(NestedUnionA::class))
105+
}
87106
}

0 commit comments

Comments
 (0)