Skip to content

Commit c938d38

Browse files
smyrickShane Myrick
andauthored
[generator] Do not add interfaces for input additional types (#821)
* Generate unique input additional types * Do not add additional type if they are input and interfaces * Simplify isValidAdditionalType Co-authored-by: Shane Myrick <[email protected]>
1 parent 8be44f3 commit c938d38

File tree

4 files changed

+86
-14
lines changed

4 files changed

+86
-14
lines changed

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ 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
2223
import com.expediagroup.graphql.generator.state.AdditionalType
2324
import com.expediagroup.graphql.generator.state.ClassScanner
2425
import com.expediagroup.graphql.generator.state.TypesCache
@@ -30,6 +31,7 @@ import graphql.schema.GraphQLCodeRegistry
3031
import graphql.schema.GraphQLDirective
3132
import graphql.schema.GraphQLSchema
3233
import graphql.schema.GraphQLType
34+
import graphql.schema.GraphQLTypeUtil
3335
import graphql.schema.visibility.NoIntrospectionGraphqlFieldVisibility
3436
import java.io.Closeable
3537
import java.util.concurrent.ConcurrentHashMap
@@ -100,7 +102,9 @@ open class SchemaGenerator(internal val config: SchemaGeneratorConfig) : Closeab
100102
*/
101103
protected fun addAdditionalTypesWithAnnotation(annotation: KClass<*>, inputType: Boolean = false) {
102104
classScanner.getClassesWithAnnotation(annotation).forEach {
103-
additionalTypes.add(AdditionalType(it.createType(), inputType))
105+
if (it.isValidAdditionalType(inputType)) {
106+
additionalTypes.add(AdditionalType(it.createType(), inputType))
107+
}
104108
}
105109
}
106110

@@ -117,10 +121,14 @@ open class SchemaGenerator(internal val config: SchemaGeneratorConfig) : Closeab
117121
while (this.additionalTypes.isNotEmpty()) {
118122
val currentlyProcessedTypes = LinkedHashSet(this.additionalTypes)
119123
this.additionalTypes.clear()
120-
graphqlTypes.addAll(currentlyProcessedTypes.map { generateGraphQLType(this, it.kType, it.inputType) })
124+
graphqlTypes.addAll(
125+
currentlyProcessedTypes.map {
126+
GraphQLTypeUtil.unwrapNonNull(generateGraphQLType(this, it.kType, it.inputType))
127+
}
128+
)
121129
}
122130

123-
return graphqlTypes.toSet()
131+
return graphqlTypes
124132
}
125133

126134
/**

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,19 @@ internal fun KClass<*>.getValidSuperclasses(hooks: SchemaGeneratorHooks): List<K
5757
internal fun KClass<*>.findConstructorParameter(name: String): KParameter? =
5858
this.primaryConstructor?.findParameterByName(name)
5959

60-
internal fun KClass<*>.isInterface(): Boolean = this.java.isInterface || this.isAbstract || this.isSealed
60+
internal fun KClass<*>.isInterface(): Boolean =
61+
this.java.isInterface || this.isAbstract || this.isSealed
6162

6263
internal fun KClass<*>.isUnion(): Boolean =
6364
this.isInterface() && this.declaredMemberProperties.isEmpty() && this.declaredMemberFunctions.isEmpty()
6465

66+
/**
67+
* Do not add interfaces as additional types if it expects all the types
68+
* to be input types. The isInteface() check works for both
69+
* GraphQL Interfaces and GraphQL Unions
70+
*/
71+
internal fun KClass<*>.isValidAdditionalType(inputType: Boolean): Boolean = !(inputType && this.isInterface())
72+
6573
internal fun KClass<*>.isEnum(): Boolean = this.isSubclassOf(Enum::class)
6674

6775
internal fun KClass<*>.isListType(): Boolean = this.isSubclassOf(List::class) || this.java.isArray

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

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,17 @@ class SchemaGeneratorTest {
4040
generator.addTypes(MyCustomAnnotation::class)
4141
assertEquals(1, generator.additionalTypes.size)
4242

43-
generator.addInputTypes(MyCustomAnnotation::class)
44-
assertEquals(2, generator.additionalTypes.size)
43+
// Verify interfaces and unions are not added when input types
44+
generator.addInputTypes(MyInterfaceAnnotation::class)
45+
assertEquals(1, generator.additionalTypes.size)
46+
47+
// Interfaces and unions can be added when not input types
48+
generator.addTypes(MyInterfaceAnnotation::class)
49+
assertEquals(3, generator.additionalTypes.size)
50+
51+
// Verify the interface implementations are picked up at generation time
52+
val result = generator.generateCustomAdditionalTypes()
53+
assertEquals(4, result.size)
4554
}
4655

4756
@Test
@@ -53,7 +62,7 @@ class SchemaGeneratorTest {
5362
val result = generator.generateCustomAdditionalTypes()
5463

5564
assertEquals(1, result.size)
56-
assertEquals("SomeObjectWithAnnotation!", result.first().deepName)
65+
assertEquals("SomeObjectWithAnnotation", result.first().deepName)
5766
}
5867

5968
@Test
@@ -65,7 +74,20 @@ class SchemaGeneratorTest {
6574
val result = generator.generateCustomAdditionalTypes()
6675

6776
assertEquals(1, result.size)
68-
assertEquals("SomeObjectWithAnnotationInput!", result.first().deepName)
77+
assertEquals("SomeObjectWithAnnotationInput", result.first().deepName)
78+
}
79+
80+
@Test
81+
fun generateBothInputAndOutputTypesWithSameName() {
82+
val config = SchemaGeneratorConfig(listOf("com.expediagroup.graphql.generator"))
83+
val generator = CustomSchemaGenerator(config)
84+
generator.addTypes(AnnotationOnAllTypes::class)
85+
generator.addInputTypes(AnnotationOnAllTypes::class)
86+
87+
val result = generator.generateCustomAdditionalTypes()
88+
89+
// Verify there are no duplicates
90+
assertEquals(8, result.size)
6991
}
7092

7193
@Test
@@ -86,7 +108,26 @@ class SchemaGeneratorTest {
86108

87109
annotation class MyCustomAnnotation
88110
annotation class MyOtherCustomAnnotation
111+
annotation class MyInterfaceAnnotation
112+
annotation class AnnotationOnAllTypes
89113

90114
@MyCustomAnnotation
115+
@AnnotationOnAllTypes
91116
data class SomeObjectWithAnnotation(val name: String)
117+
118+
@MyInterfaceAnnotation
119+
@AnnotationOnAllTypes
120+
interface SomeUnion
121+
122+
@AnnotationOnAllTypes
123+
data class UnionImpl(val id: String) : SomeUnion
124+
125+
@MyInterfaceAnnotation
126+
@AnnotationOnAllTypes
127+
interface SomeInterface {
128+
val id: String
129+
}
130+
131+
@AnnotationOnAllTypes
132+
data class InterfaceImpl(override val id: String) : SomeInterface
92133
}

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

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ open class KClassExtensionsTest {
6060
val publicProperty: String = "public",
6161
val filteredProperty: String = "filtered",
6262
private val privateVal: String = "hidden"
63-
) : TestInterface {
63+
) : TestUnion {
6464
fun publicFunction() = "public function"
6565

6666
fun filteredFunction() = "filtered function"
@@ -82,7 +82,7 @@ open class KClassExtensionsTest {
8282

8383
class MyPublicClass
8484

85-
internal class UnionSuperclass : TestInterface
85+
internal class UnionSuperclass : TestUnion
8686

8787
@GraphQLIgnore
8888
internal interface IgnoredInterface {
@@ -129,7 +129,7 @@ open class KClassExtensionsTest {
129129
val id = 1
130130
}
131131

132-
interface TestInterface
132+
interface TestUnion
133133

134134
sealed class Pet(val name: String) {
135135
class Dog(name: String, val goodBoysReceived: Int) : Pet(name)
@@ -242,7 +242,7 @@ open class KClassExtensionsTest {
242242
assertNotNull(MyTestClass::class.findConstructorParameter("publicProperty"))
243243
assertNull(MyTestClass::class.findConstructorParameter("foobar"))
244244
assertNull(EmptyConstructorClass::class.findConstructorParameter("id"))
245-
assertNull(TestInterface::class.findConstructorParameter("foobar"))
245+
assertNull(TestUnion::class.findConstructorParameter("foobar"))
246246
}
247247

248248
@Test
@@ -261,15 +261,15 @@ open class KClassExtensionsTest {
261261

262262
@Test
263263
fun `test graphql interface extension`() {
264-
assertTrue(TestInterface::class.isInterface())
264+
assertTrue(TestUnion::class.isInterface())
265265
assertTrue(SomeAbstractClass::class.isInterface())
266266
assertTrue(Pet::class.isInterface())
267267
assertFalse(MyTestClass::class.isInterface())
268268
}
269269

270270
@Test
271271
fun `test graphql union extension`() {
272-
assertTrue(TestInterface::class.isUnion())
272+
assertTrue(TestUnion::class.isUnion())
273273
assertFalse(InvalidPropertyUnionInterface::class.isUnion())
274274
assertFalse(InvalidFunctionUnionInterface::class.isUnion())
275275
assertFalse(Pet::class.isUnion())
@@ -334,4 +334,19 @@ open class KClassExtensionsTest {
334334
assertTrue(MyProtectedClass::class.isNotPublic())
335335
assertTrue(MyTestClass::class.isNotPublic())
336336
}
337+
338+
@Test
339+
fun isValidAdditionalType() {
340+
// Valid cases
341+
assertTrue(MyPublicClass::class.isValidAdditionalType(false))
342+
assertTrue(MyPublicClass::class.isValidAdditionalType(true))
343+
assertTrue(SomeInterface::class.isValidAdditionalType(false))
344+
assertTrue(TestUnion::class.isValidAdditionalType(false))
345+
assertTrue(MyTestEnum::class.isValidAdditionalType(false))
346+
assertTrue(MyTestEnum::class.isValidAdditionalType(true))
347+
348+
// Invalid cases
349+
assertFalse(SomeInterface::class.isValidAdditionalType(true))
350+
assertFalse(TestUnion::class.isValidAdditionalType(true))
351+
}
337352
}

0 commit comments

Comments
 (0)