Skip to content

Commit ff2a70a

Browse files
smyrickShane Myrick
and
Shane Myrick
authored
Add new hook to override the isValidAdditionalType check (ExpediaGroup#826)
* Add new hook to override the isValidAdditionalType check * Add filter method locations where we add other additional types Co-authored-by: Shane Myrick <[email protected]>
1 parent c938d38 commit ff2a70a

File tree

9 files changed

+98
-16
lines changed

9 files changed

+98
-16
lines changed

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package com.expediagroup.graphql.generator
1919
import com.expediagroup.graphql.SchemaGeneratorConfig
2020
import com.expediagroup.graphql.TopLevelObject
2121
import com.expediagroup.graphql.exceptions.InvalidPackagesException
22-
import com.expediagroup.graphql.generator.extensions.isValidAdditionalType
2322
import com.expediagroup.graphql.generator.state.AdditionalType
2423
import com.expediagroup.graphql.generator.state.ClassScanner
2524
import com.expediagroup.graphql.generator.state.TypesCache
@@ -98,12 +97,12 @@ open class SchemaGenerator(internal val config: SchemaGeneratorConfig) : Closeab
9897
/**
9998
* Add all types with the following annotation to the schema.
10099
*
101-
* This is helpful for things like federation or combining external schemas
100+
* This is helpful for things like federation or combining external schemas.
102101
*/
103102
protected fun addAdditionalTypesWithAnnotation(annotation: KClass<*>, inputType: Boolean = false) {
104-
classScanner.getClassesWithAnnotation(annotation).forEach {
105-
if (it.isValidAdditionalType(inputType)) {
106-
additionalTypes.add(AdditionalType(it.createType(), inputType))
103+
classScanner.getClassesWithAnnotation(annotation).forEach { kClass ->
104+
if (config.hooks.isValidAdditionalType(kClass, inputType)) {
105+
additionalTypes.add(AdditionalType(kClass.createType(), inputType))
107106
}
108107
}
109108
}

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/extensions/kClassExtensions.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,10 @@ internal fun KClass<*>.isUnion(): Boolean =
6767
* Do not add interfaces as additional types if it expects all the types
6868
* to be input types. The isInteface() check works for both
6969
* GraphQL Interfaces and GraphQL Unions
70+
*
71+
* Also do not add any classes that are marked as @GraphQLIgnore
7072
*/
71-
internal fun KClass<*>.isValidAdditionalType(inputType: Boolean): Boolean = !(inputType && this.isInterface())
73+
internal fun KClass<*>.isValidAdditionalType(inputType: Boolean): Boolean = !(inputType && this.isInterface()) && !this.isGraphQLIgnored()
7274

7375
internal fun KClass<*>.isEnum(): Boolean = this.isSubclassOf(Enum::class)
7476

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/types/generateInputProperty.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
package com.expediagroup.graphql.generator.types
1818

19+
import com.expediagroup.graphql.extensions.unwrapType
1920
import com.expediagroup.graphql.generator.SchemaGenerator
2021
import com.expediagroup.graphql.generator.extensions.getPropertyDescription
2122
import com.expediagroup.graphql.generator.extensions.getPropertyName
@@ -27,7 +28,11 @@ import kotlin.reflect.KProperty
2728

2829
internal fun generateInputProperty(generator: SchemaGenerator, prop: KProperty<*>, parentClass: KClass<*>): GraphQLInputObjectField {
2930
val builder = GraphQLInputObjectField.newInputObjectField()
30-
val graphQLInputType = generateGraphQLType(generator = generator, type = prop.returnType, inputType = true).safeCast<GraphQLInputType>()
31+
32+
// Verfiy that the unwrapped GraphQL type is a valid input type
33+
val graphQLInputType = generateGraphQLType(generator = generator, type = prop.returnType, inputType = true)
34+
.unwrapType()
35+
.safeCast<GraphQLInputType>()
3136

3237
builder.description(prop.getPropertyDescription(parentClass))
3338
builder.name(prop.getPropertyName(parentClass))

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/types/generateInterface.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,15 @@
1616

1717
package com.expediagroup.graphql.generator.types
1818

19-
import com.expediagroup.graphql.generator.state.AdditionalType
2019
import com.expediagroup.graphql.extensions.unwrapType
2120
import com.expediagroup.graphql.generator.SchemaGenerator
2221
import com.expediagroup.graphql.generator.extensions.getGraphQLDescription
2322
import com.expediagroup.graphql.generator.extensions.getSimpleName
2423
import com.expediagroup.graphql.generator.extensions.getValidFunctions
2524
import com.expediagroup.graphql.generator.extensions.getValidProperties
2625
import com.expediagroup.graphql.generator.extensions.getValidSuperclasses
27-
import com.expediagroup.graphql.generator.extensions.isGraphQLIgnored
2826
import com.expediagroup.graphql.generator.extensions.safeCast
27+
import com.expediagroup.graphql.generator.state.AdditionalType
2928
import graphql.TypeResolutionEnvironment
3029
import graphql.schema.GraphQLInterfaceType
3130
import graphql.schema.GraphQLTypeReference
@@ -58,7 +57,7 @@ internal fun generateInterface(generator: SchemaGenerator, kClass: KClass<*>): G
5857
.forEach { builder.field(generateFunction(generator, it, kClass.getSimpleName(), null, abstract = true)) }
5958

6059
generator.classScanner.getSubTypesOf(kClass)
61-
.filter { it.isGraphQLIgnored().not() }
60+
.filter { generator.config.hooks.isValidAdditionalType(it, inputType = false) }
6261
.forEach { generator.additionalTypes.add(AdditionalType(it.createType(), inputType = false)) }
6362

6463
val interfaceType = builder.build()

graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/hooks/SchemaGeneratorHooks.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import com.expediagroup.graphql.exceptions.EmptyObjectTypeException
2424
import com.expediagroup.graphql.exceptions.EmptyQueryTypeException
2525
import com.expediagroup.graphql.exceptions.EmptySubscriptionTypeException
2626
import com.expediagroup.graphql.generator.extensions.isSubclassOf
27+
import com.expediagroup.graphql.generator.extensions.isValidAdditionalType
2728
import graphql.schema.FieldCoordinates
2829
import graphql.schema.GraphQLCodeRegistry
2930
import graphql.schema.GraphQLFieldDefinition
@@ -102,6 +103,14 @@ interface SchemaGeneratorHooks {
102103
*/
103104
fun isValidSubscriptionReturnType(kClass: KClass<*>, function: KFunction<*>): Boolean = function.returnType.isSubclassOf(Publisher::class)
104105

106+
/**
107+
* Allow for custom logic when adding additional types to filter out specific classes
108+
* or classes with other annotations or metadata.
109+
*
110+
* The default logic just filters out interfaces if inputType is true.
111+
*/
112+
fun isValidAdditionalType(kClass: KClass<*>, inputType: Boolean): Boolean = kClass.isValidAdditionalType(inputType)
113+
105114
/**
106115
* Called after `willGenerateGraphQLType` and before `didGenerateGraphQLType`.
107116
* Enables you to change the wiring, e.g. apply directives to alter the target type.

graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/PolymorphicTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ import kotlin.test.assertNotNull
3535
import kotlin.test.assertNull
3636
import kotlin.test.assertTrue
3737

38-
internal class PolymorphicTests {
38+
class PolymorphicTests {
3939

4040
@Test
4141
fun `Schema generator creates union types from marked up interface`() {

graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/SchemaGeneratorTest.kt

Lines changed: 31 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,15 @@ package com.expediagroup.graphql.generator
1919
import com.expediagroup.graphql.SchemaGeneratorConfig
2020
import com.expediagroup.graphql.exceptions.InvalidPackagesException
2121
import com.expediagroup.graphql.extensions.deepName
22+
import com.expediagroup.graphql.hooks.SchemaGeneratorHooks
23+
import graphql.schema.GraphQLNamedType
24+
import graphql.schema.GraphQLObjectType
2225
import org.junit.jupiter.api.Test
2326
import kotlin.reflect.KClass
27+
import kotlin.reflect.full.findAnnotation
2428
import kotlin.test.assertEquals
2529
import kotlin.test.assertFailsWith
30+
import kotlin.test.assertNotNull
2631

2732
class SchemaGeneratorTest {
2833

@@ -79,15 +84,29 @@ class SchemaGeneratorTest {
7984

8085
@Test
8186
fun generateBothInputAndOutputTypesWithSameName() {
82-
val config = SchemaGeneratorConfig(listOf("com.expediagroup.graphql.generator"))
87+
val customHooks = object : SchemaGeneratorHooks {
88+
override fun isValidAdditionalType(kClass: KClass<*>, inputType: Boolean): Boolean {
89+
val allowedForInput = kClass.findAnnotation<AnnotationOnAllTypes>()?.allowedForInput ?: true
90+
91+
if (inputType && !allowedForInput) {
92+
return false
93+
}
94+
95+
return super.isValidAdditionalType(kClass, inputType)
96+
}
97+
}
98+
99+
val config = SchemaGeneratorConfig(listOf("com.expediagroup.graphql.generator"), hooks = customHooks)
83100
val generator = CustomSchemaGenerator(config)
84101
generator.addTypes(AnnotationOnAllTypes::class)
85102
generator.addInputTypes(AnnotationOnAllTypes::class)
86103

87104
val result = generator.generateCustomAdditionalTypes()
88105

89106
// Verify there are no duplicates
90-
assertEquals(8, result.size)
107+
assertEquals(7, result.size)
108+
val interfaceImpl = assertNotNull(result.find { (it as? GraphQLNamedType)?.name == "InterfaceImpl" } as? GraphQLObjectType)
109+
assertEquals(6, interfaceImpl.fieldDefinitions.size)
91110
}
92111

93112
@Test
@@ -109,7 +128,7 @@ class SchemaGeneratorTest {
109128
annotation class MyCustomAnnotation
110129
annotation class MyOtherCustomAnnotation
111130
annotation class MyInterfaceAnnotation
112-
annotation class AnnotationOnAllTypes
131+
annotation class AnnotationOnAllTypes(val allowedForInput: Boolean = true)
113132

114133
@MyCustomAnnotation
115134
@AnnotationOnAllTypes
@@ -128,6 +147,13 @@ class SchemaGeneratorTest {
128147
val id: String
129148
}
130149

131-
@AnnotationOnAllTypes
132-
data class InterfaceImpl(override val id: String) : SomeInterface
150+
@AnnotationOnAllTypes(allowedForInput = false)
151+
data class InterfaceImpl(
152+
override val id: String,
153+
val listField: List<String>,
154+
val optionalListField: List<String>?,
155+
val optionalListOptionalField: List<String?>?,
156+
val requiredUnionField: SomeUnion,
157+
val optionalUnionField: SomeUnion?
158+
) : SomeInterface
133159
}

graphql-kotlin-schema-generator/src/test/kotlin/com/expediagroup/graphql/generator/extensions/KClassExtensionsTest.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ open class KClassExtensionsTest {
9292
@GraphQLIgnore
9393
interface IgnoredSecondLevelInterface : SomeInterface
9494

95+
@GraphQLIgnore
96+
class IgnoredClass(val value: String)
97+
9598
internal class ClassWithSecondLevelInterface : IgnoredSecondLevelInterface {
9699
override val someField: String = "hello"
97100

@@ -348,5 +351,11 @@ open class KClassExtensionsTest {
348351
// Invalid cases
349352
assertFalse(SomeInterface::class.isValidAdditionalType(true))
350353
assertFalse(TestUnion::class.isValidAdditionalType(true))
354+
assertFalse(IgnoredInterface::class.isValidAdditionalType(true))
355+
assertFalse(IgnoredInterface::class.isValidAdditionalType(false))
356+
assertFalse(IgnoredSecondLevelInterface::class.isValidAdditionalType(true))
357+
assertFalse(IgnoredSecondLevelInterface::class.isValidAdditionalType(false))
358+
assertFalse(IgnoredClass::class.isValidAdditionalType(true))
359+
assertFalse(IgnoredClass::class.isValidAdditionalType(false))
351360
}
352361
}

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

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@
1616

1717
package com.expediagroup.graphql.hooks
1818

19+
import com.expediagroup.graphql.SchemaGeneratorConfig
1920
import com.expediagroup.graphql.TopLevelObject
2021
import com.expediagroup.graphql.annotations.GraphQLIgnore
2122
import com.expediagroup.graphql.exceptions.EmptyInputObjectTypeException
2223
import com.expediagroup.graphql.exceptions.EmptyInterfaceTypeException
2324
import com.expediagroup.graphql.exceptions.EmptyObjectTypeException
2425
import com.expediagroup.graphql.extensions.deepName
26+
import com.expediagroup.graphql.generator.SchemaGenerator
2527
import com.expediagroup.graphql.generator.extensions.getSimpleName
2628
import com.expediagroup.graphql.getTestSchemaConfigWithHooks
2729
import com.expediagroup.graphql.test.utils.graphqlUUIDType
@@ -122,6 +124,34 @@ class SchemaGeneratorHooksTest {
122124
assertTrue(schema.queryType.fieldDefinitions.isEmpty())
123125
}
124126

127+
@Test
128+
fun `calls hook to filter additionalTypes`() {
129+
class MockSchemaGeneratorHooks : SchemaGeneratorHooks {
130+
var calledFilterFunction = false
131+
132+
override fun isValidAdditionalType(kClass: KClass<*>, inputType: Boolean): Boolean {
133+
calledFilterFunction = true
134+
return true
135+
}
136+
}
137+
138+
class CustomGenerator(config: SchemaGeneratorConfig) : SchemaGenerator(config) {
139+
fun addTypesWithAnnotation(annotation: KClass<*>) = super.addAdditionalTypesWithAnnotation(annotation, false)
140+
fun getAdditionalTypesCount() = additionalTypes.size
141+
}
142+
143+
val hooks = MockSchemaGeneratorHooks()
144+
val generator = CustomGenerator(getTestSchemaConfigWithHooks(hooks))
145+
146+
assertFalse(hooks.calledFilterFunction)
147+
assertEquals(0, generator.getAdditionalTypesCount())
148+
149+
generator.addTypesWithAnnotation(CustomAnnotation::class)
150+
151+
assertTrue(hooks.calledFilterFunction)
152+
assertEquals(1, generator.getAdditionalTypesCount())
153+
}
154+
125155
@Test
126156
fun `calls hook after generating object type`() {
127157
class MockSchemaGeneratorHooks : SchemaGeneratorHooks {
@@ -348,6 +378,9 @@ class SchemaGeneratorHooksTest {
348378
fun emptyInterface(): EmptyInterface = EmptyImplementation("123")
349379
}
350380

381+
annotation class CustomAnnotation
382+
383+
@CustomAnnotation
351384
interface EmptyInterface {
352385
@GraphQLIgnore
353386
val id: String

0 commit comments

Comments
 (0)