Skip to content

Commit 3497692

Browse files
authored
Backport GraphQLUnion to 4.x.x (#1234)
1 parent edad0b4 commit 3497692

File tree

16 files changed

+368
-51
lines changed

16 files changed

+368
-51
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright 2021 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.generator.annotations
18+
19+
import kotlin.reflect.KClass
20+
21+
@Target(AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION)
22+
annotation class GraphQLUnion(
23+
val name: String,
24+
val possibleTypes: Array<KClass<*>>,
25+
val description: String = ""
26+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright 2021 Expedia, Inc
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.expediagroup.graphql.generator.exceptions
18+
19+
import kotlin.reflect.KType
20+
21+
class InvalidCustomUnionException(returnType: KType) :
22+
GraphQLKotlinException("The custom union annotation was used but the return type was $returnType instead of Any")

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package com.expediagroup.graphql.generator.internal.extensions
1919
import com.expediagroup.graphql.generator.annotations.GraphQLDescription
2020
import com.expediagroup.graphql.generator.annotations.GraphQLIgnore
2121
import com.expediagroup.graphql.generator.annotations.GraphQLName
22+
import com.expediagroup.graphql.generator.annotations.GraphQLUnion
2223
import kotlin.reflect.KAnnotatedElement
2324
import kotlin.reflect.full.findAnnotation
2425

@@ -30,7 +31,9 @@ internal fun KAnnotatedElement.getDeprecationReason(): String? = this.findAnnota
3031

3132
internal fun KAnnotatedElement.isGraphQLIgnored(): Boolean = this.findAnnotation<GraphQLIgnore>() != null
3233

33-
internal fun Deprecated.getReason(): String? {
34+
internal fun List<Annotation>.getUnionAnnotation(): GraphQLUnion? = this.filterIsInstance(GraphQLUnion::class.java).firstOrNull()
35+
36+
internal fun Deprecated.getReason(): String {
3437
val builder = StringBuilder()
3538
builder.append(this.message)
3639

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,11 @@ internal fun KClass<*>.findConstructorParameter(name: String): KParameter? =
6060
internal fun KClass<*>.isInterface(): Boolean =
6161
this.java.isInterface || this.isAbstract || this.isSealed
6262

63-
internal fun KClass<*>.isUnion(): Boolean =
64-
this.isInterface() && this.declaredMemberProperties.isEmpty() && this.declaredMemberFunctions.isEmpty()
63+
internal fun KClass<*>.isUnion(fieldAnnotations: List<Annotation> = emptyList()): Boolean = this.isDeclaredUnion() || this.isAnnotationUnion(fieldAnnotations)
64+
65+
private fun KClass<*>.isDeclaredUnion() = this.isInterface() && this.declaredMemberProperties.isEmpty() && this.declaredMemberFunctions.isEmpty()
66+
67+
internal fun KClass<*>.isAnnotationUnion(fieldAnnotations: List<Annotation>): Boolean = this.isInstance(Any::class) && fieldAnnotations.getUnionAnnotation() != null
6568

6669
/**
6770
* Do not add interfaces as additional types if it expects all the types

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

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,14 @@
1616

1717
package com.expediagroup.graphql.generator.internal.state
1818

19+
import com.expediagroup.graphql.generator.annotations.GraphQLUnion
1920
import com.expediagroup.graphql.generator.exceptions.ConflictingTypesException
21+
import com.expediagroup.graphql.generator.exceptions.InvalidCustomUnionException
2022
import com.expediagroup.graphql.generator.exceptions.TypeNotSupportedException
2123
import com.expediagroup.graphql.generator.internal.extensions.getKClass
2224
import com.expediagroup.graphql.generator.internal.extensions.getSimpleName
25+
import com.expediagroup.graphql.generator.internal.extensions.getUnionAnnotation
26+
import com.expediagroup.graphql.generator.internal.extensions.isAnnotationUnion
2327
import com.expediagroup.graphql.generator.internal.extensions.isListType
2428
import com.expediagroup.graphql.generator.internal.extensions.qualifiedName
2529
import graphql.schema.GraphQLNamedType
@@ -36,6 +40,11 @@ internal class TypesCache(private val supportedPackages: List<String>) : Closeab
3640
private val cache: MutableMap<String, KGraphQLType> = mutableMapOf()
3741
private val typesUnderConstruction: MutableSet<TypesCacheKey> = mutableSetOf()
3842

43+
internal fun get(type: KType, inputType: Boolean, annotations: List<Annotation>): GraphQLNamedType? {
44+
val cacheKey = generateCacheKey(type, inputType, annotations)
45+
return get(cacheKey)
46+
}
47+
3948
@Throws(ConflictingTypesException::class)
4049
internal fun get(cacheKey: TypesCacheKey): GraphQLNamedType? {
4150
val cacheKeyString = getCacheKeyString(cacheKey) ?: return null
@@ -64,6 +73,28 @@ internal class TypesCache(private val supportedPackages: List<String>) : Closeab
6473
return null
6574
}
6675

76+
private fun generateCacheKey(type: KType, inputType: Boolean, annotations: List<Annotation> = emptyList()): TypesCacheKey {
77+
if (type.getKClass().isListType()) {
78+
return TypesCacheKey(type, inputType)
79+
}
80+
81+
val unionAnnotation = annotations.getUnionAnnotation()
82+
83+
return if (unionAnnotation != null) {
84+
if (type.getKClass().isAnnotationUnion(annotations)) {
85+
TypesCacheKey(type = type, inputType = inputType, name = getCustomUnionNameKey(unionAnnotation))
86+
} else {
87+
throw InvalidCustomUnionException(type)
88+
}
89+
} else {
90+
TypesCacheKey(type = type, inputType = inputType)
91+
}
92+
}
93+
94+
private fun getCustomUnionNameKey(union: GraphQLUnion): String {
95+
return union.name + union.possibleTypes.joinToString(prefix = "[", postfix = "]", separator = ",") { it.getSimpleName() }
96+
}
97+
6798
/**
6899
* Clear the cache of all saved values
69100
*/
@@ -80,25 +111,29 @@ internal class TypesCache(private val supportedPackages: List<String>) : Closeab
80111
* Enums do not have a different name for input and output.
81112
*/
82113
private fun getCacheKeyString(cacheKey: TypesCacheKey): String? {
83-
val type = cacheKey.type
84-
val kClass = type.getKClass()
114+
return if (cacheKey.name != null) {
115+
cacheKey.name
116+
} else {
117+
val type = cacheKey.type
118+
val kClass = type.getKClass()
85119

86-
return when {
87-
kClass.isListType() -> null
88-
kClass.isSubclassOf(Enum::class) -> kClass.getSimpleName()
89-
isTypeNotSupported(type) -> throw TypeNotSupportedException(type, supportedPackages)
90-
else -> type.getSimpleName(cacheKey.inputType)
120+
when {
121+
kClass.isListType() -> null
122+
kClass.isSubclassOf(Enum::class) -> kClass.getSimpleName()
123+
isTypeNotSupported(type) -> throw TypeNotSupportedException(type, supportedPackages)
124+
else -> type.getSimpleName(cacheKey.inputType)
125+
}
91126
}
92127
}
93128

94129
private fun isTypeNotSupported(type: KType): Boolean = supportedPackages.none { type.qualifiedName.startsWith(it) }
95130

96-
internal fun buildIfNotUnderConstruction(kClass: KClass<*>, inputType: Boolean, build: (KClass<*>) -> GraphQLType): GraphQLType {
131+
internal fun buildIfNotUnderConstruction(kClass: KClass<*>, inputType: Boolean, annotations: List<Annotation>, build: (KClass<*>) -> GraphQLType): GraphQLType {
97132
if (kClass.isListType()) {
98133
return build(kClass)
99134
}
100135

101-
val cacheKey = TypesCacheKey(kClass.starProjectedType, inputType)
136+
val cacheKey = generateCacheKey(kClass.starProjectedType, inputType, annotations)
102137
val cachedType = get(cacheKey)
103138
return when {
104139
cachedType != null -> cachedType

generator/graphql-kotlin-schema-generator/src/main/kotlin/com/expediagroup/graphql/generator/internal/state/TypesCacheKey.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,8 @@ package com.expediagroup.graphql.generator.internal.state
1818

1919
import kotlin.reflect.KType
2020

21-
internal data class TypesCacheKey(val type: KType, val inputType: Boolean)
21+
internal data class TypesCacheKey(
22+
val type: KType,
23+
val inputType: Boolean = false,
24+
val name: String? = null
25+
)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ internal fun generateFunction(generator: SchemaGenerator, fn: KFunction<*>, pare
5151

5252
val typeFromHooks = generator.config.hooks.willResolveMonad(fn.returnType)
5353
val returnType = getWrappedReturnType(typeFromHooks)
54-
val graphQLOutputType = generateGraphQLType(generator = generator, type = returnType).safeCast<GraphQLOutputType>()
54+
val graphQLOutputType = generateGraphQLType(generator = generator, type = returnType, annotations = fn.annotations).safeCast<GraphQLOutputType>()
5555
val graphQLType = builder.type(graphQLOutputType).build()
5656
val coordinates = FieldCoordinates.coordinates(parentName, functionName)
5757

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

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ package com.expediagroup.graphql.generator.internal.types
1919
import com.expediagroup.graphql.generator.SchemaGenerator
2020
import com.expediagroup.graphql.generator.extensions.unwrapType
2121
import com.expediagroup.graphql.generator.internal.extensions.getKClass
22+
import com.expediagroup.graphql.generator.internal.extensions.getUnionAnnotation
2223
import com.expediagroup.graphql.generator.internal.extensions.isEnum
2324
import com.expediagroup.graphql.generator.internal.extensions.isInterface
2425
import com.expediagroup.graphql.generator.internal.extensions.isListType
2526
import com.expediagroup.graphql.generator.internal.extensions.isUnion
2627
import com.expediagroup.graphql.generator.internal.extensions.wrapInNonNull
27-
import com.expediagroup.graphql.generator.internal.state.TypesCacheKey
2828
import graphql.schema.GraphQLType
2929
import graphql.schema.GraphQLTypeReference
3030
import kotlin.reflect.KClass
@@ -33,11 +33,11 @@ import kotlin.reflect.KType
3333
/**
3434
* Return a basic GraphQL type given all the information about the kotlin type.
3535
*/
36-
internal fun generateGraphQLType(generator: SchemaGenerator, type: KType, inputType: Boolean = false): GraphQLType {
36+
internal fun generateGraphQLType(generator: SchemaGenerator, type: KType, inputType: Boolean = false, annotations: List<Annotation> = emptyList()): GraphQLType {
3737
val hookGraphQLType = generator.config.hooks.willGenerateGraphQLType(type)
3838
val graphQLType = hookGraphQLType
3939
?: generateScalar(generator, type)
40-
?: objectFromReflection(generator, type, inputType)
40+
?: objectFromReflection(generator, type, inputType, annotations)
4141

4242
// Do not call the hook on GraphQLTypeReference as we have not generated the type yet
4343
val unwrappedType = graphQLType.unwrapType()
@@ -49,26 +49,25 @@ internal fun generateGraphQLType(generator: SchemaGenerator, type: KType, inputT
4949
return typeWithNullability
5050
}
5151

52-
private fun objectFromReflection(generator: SchemaGenerator, type: KType, inputType: Boolean): GraphQLType {
53-
val cacheKey = TypesCacheKey(type, inputType)
54-
val cachedType = generator.cache.get(cacheKey)
52+
private fun objectFromReflection(generator: SchemaGenerator, type: KType, inputType: Boolean, annotations: List<Annotation>): GraphQLType {
53+
val cachedType = generator.cache.get(type, inputType, annotations)
5554

5655
if (cachedType != null) {
5756
return cachedType
5857
}
5958

6059
val kClass = type.getKClass()
6160

62-
return generator.cache.buildIfNotUnderConstruction(kClass, inputType) {
63-
val graphQLType = getGraphQLType(generator, kClass, inputType, type)
61+
return generator.cache.buildIfNotUnderConstruction(kClass, inputType, annotations) {
62+
val graphQLType = getGraphQLType(generator, kClass, inputType, type, annotations)
6463
generator.config.hooks.willAddGraphQLTypeToSchema(type, graphQLType)
6564
}
6665
}
6766

68-
private fun getGraphQLType(generator: SchemaGenerator, kClass: KClass<*>, inputType: Boolean, type: KType): GraphQLType = when {
67+
private fun getGraphQLType(generator: SchemaGenerator, kClass: KClass<*>, inputType: Boolean, type: KType, fieldAnnotations: List<Annotation>): GraphQLType = when {
6968
kClass.isEnum() -> @Suppress("UNCHECKED_CAST") (generateEnum(generator, kClass as KClass<Enum<*>>))
70-
kClass.isListType() -> generateList(generator, type, inputType)
71-
kClass.isUnion() -> generateUnion(generator, kClass)
69+
kClass.isListType() -> generateList(generator, type, inputType, fieldAnnotations)
70+
kClass.isUnion(fieldAnnotations) -> generateUnion(generator, kClass, fieldAnnotations.getUnionAnnotation())
7271
kClass.isInterface() -> generateInterface(generator, kClass)
7372
inputType -> generateInputObject(generator, kClass)
7473
else -> generateObject(generator, kClass)

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ internal fun generateInterface(generator: SchemaGenerator, kClass: KClass<*>): G
6161
.filter { generator.config.hooks.isValidAdditionalType(it, inputType = false) }
6262
.forEach { generator.additionalTypes.add(AdditionalType(it.createType(), inputType = false)) }
6363

64-
val interfaceType = builder.build()
64+
val interfaceType: GraphQLInterfaceType = builder.build()
6565
generator.codeRegistry.typeResolver(interfaceType) { env: TypeResolutionEnvironment -> env.schema.getObjectType(env.getObject<Any>().javaClass.kotlin.getSimpleName()) }
6666
return generator.config.hooks.onRewireGraphQLType(interfaceType).safeCast()
6767
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import com.expediagroup.graphql.generator.internal.extensions.getWrappedType
2121
import graphql.schema.GraphQLList
2222
import kotlin.reflect.KType
2323

24-
internal fun generateList(generator: SchemaGenerator, type: KType, inputType: Boolean): GraphQLList {
25-
val wrappedType = generateGraphQLType(generator, type.getWrappedType(), inputType)
24+
internal fun generateList(generator: SchemaGenerator, type: KType, inputType: Boolean, annotations: List<Annotation> = emptyList()): GraphQLList {
25+
val wrappedType = generateGraphQLType(generator, type.getWrappedType(), inputType, annotations)
2626
return GraphQLList.list(wrappedType)
2727
}

0 commit comments

Comments
 (0)