Skip to content

Commit cff47bd

Browse files
dariuszkucsmyrick
authored andcommitted
Add support for Kotlin builtin arrays of primitives (#94)
Resolves #90
1 parent 496160e commit cff47bd

File tree

11 files changed

+105
-44
lines changed

11 files changed

+105
-44
lines changed

example/src/main/kotlin/com.expedia.graphql.sample/query/SimpleQuery.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@ class SimpleQuery: Query {
4747
return (1..10).map { random.nextInt(100) }.toList()
4848
}
4949

50+
@GraphQLDescription("generates pseudo random array of ints")
51+
fun generatePrimitiveArray(): IntArray {
52+
val random = Random()
53+
return (1..10).map { random.nextInt(100) }.toIntArray()
54+
}
55+
56+
@GraphQLDescription("query with array input")
57+
fun doSomethingWithIntArray(ints: IntArray) = "received ints=[${ints.joinToString()}]"
58+
5059
@GraphQLDescription("query with optional input")
5160
fun doSomethingWithOptionalInput(
5261
@GraphQLDescription("this field is required") requiredValue: Int,

src/main/kotlin/com/expedia/graphql/schema/extensions/kClassExtensions.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,6 @@ internal fun KClass<*>.isGraphQLUnion(): Boolean =
2323

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

26-
internal fun KClass<*>.isGraphQLList(): Boolean = this.isSubclassOf(List::class) || this.java.isArray
26+
internal fun KClass<*>.isList(): Boolean = this.isSubclassOf(List::class)
27+
28+
internal fun KClass<*>.isArray(): Boolean = this.java.isArray

src/main/kotlin/com/expedia/graphql/schema/extensions/kTypeExtensions.kt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,28 @@ package com.expedia.graphql.schema.extensions
33
import com.expedia.graphql.schema.exceptions.InvalidListTypeException
44
import kotlin.reflect.KClass
55
import kotlin.reflect.KType
6+
import kotlin.reflect.full.createType
7+
import kotlin.reflect.full.isSubclassOf
68

79
internal fun KType.graphQLDescription(): String? = (classifier as? KClass<*>)?.graphQLDescription()
810

911
@Throws(InvalidListTypeException::class)
1012
internal fun KType.getTypeOfFirstArgument(): KType =
11-
this.arguments.first().type ?: throw InvalidListTypeException(this)
13+
this.arguments.firstOrNull()?.type ?: throw InvalidListTypeException(this)
14+
15+
@Suppress("Detekt.UnsafeCast")
16+
internal fun KType.getKClass() = this.classifier as KClass<*>
17+
18+
internal fun KType.getArrayType(): KType {
19+
val kClass = this.getKClass()
20+
return when {
21+
kClass.isSubclassOf(IntArray::class) -> Int::class.createType()
22+
kClass.isSubclassOf(LongArray::class) -> Long::class.createType()
23+
kClass.isSubclassOf(ShortArray::class) -> Short::class.createType()
24+
kClass.isSubclassOf(FloatArray::class) -> Float::class.createType()
25+
kClass.isSubclassOf(DoubleArray::class) -> Double::class.createType()
26+
kClass.isSubclassOf(CharArray::class) -> Char::class.createType()
27+
kClass.isSubclassOf(BooleanArray::class) -> Boolean::class.createType()
28+
else -> this.getTypeOfFirstArgument()
29+
}
30+
}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import kotlin.reflect.KFunction
2121
import kotlin.reflect.KProperty
2222
import kotlin.reflect.KType
2323

24+
@Suppress("Detekt.TooManyFunctions")
2425
internal class SchemaGenerator(
2526
private val queries: List<TopLevelObjectDef>,
2627
private val mutations: List<TopLevelObjectDef>,
@@ -100,6 +101,9 @@ internal class SchemaGenerator(
100101
internal fun listType(type: KType, inputType: Boolean) =
101102
listTypeBuilder.listType(type, inputType)
102103

104+
internal fun arrayType(type: KType, inputType: Boolean) =
105+
listTypeBuilder.arrayType(type, inputType)
106+
103107
internal fun objectType(kClass: KClass<*>, interfaceType: GraphQLInterfaceType? = null) =
104108
objectTypeBuilder.objectType(kClass, interfaceType)
105109

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
package com.expedia.graphql.schema.generator
22

3+
import com.expedia.graphql.schema.extensions.isArray
4+
import com.expedia.graphql.schema.extensions.isEnum
35
import com.expedia.graphql.schema.extensions.isGraphQLInterface
4-
import com.expedia.graphql.schema.extensions.isGraphQLList
56
import com.expedia.graphql.schema.extensions.isGraphQLUnion
6-
import com.expedia.graphql.schema.extensions.isEnum
7+
import com.expedia.graphql.schema.extensions.isList
78
import com.expedia.graphql.schema.extensions.wrapInNonNull
89
import com.expedia.graphql.schema.generator.models.KGraphQLType
910
import graphql.schema.GraphQLType
@@ -43,7 +44,8 @@ internal open class TypeBuilder constructor(val generator: SchemaGenerator) {
4344

4445
private fun getGraphQLType(kClass: KClass<*>, inputType: Boolean, type: KType): GraphQLType = when {
4546
kClass.isEnum() -> @Suppress("UNCHECKED_CAST") (generator.enumType(kClass as KClass<Enum<*>>))
46-
kClass.isGraphQLList() -> generator.listType(type, inputType)
47+
kClass.isArray() -> generator.arrayType(type, inputType)
48+
kClass.isList() -> generator.listType(type, inputType)
4749
kClass.isGraphQLUnion() -> generator.unionType(kClass)
4850
kClass.isGraphQLInterface() -> generator.interfaceType(kClass)
4951
inputType -> generator.inputObjectType(kClass)

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

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import com.expedia.graphql.schema.exceptions.ConflictingTypesException
44
import com.expedia.graphql.schema.exceptions.CouldNotGetJvmNameOfKTypeException
55
import com.expedia.graphql.schema.exceptions.CouldNotGetNameOfEnumException
66
import com.expedia.graphql.schema.exceptions.TypeNotSupportedException
7+
import com.expedia.graphql.schema.extensions.getKClass
8+
import com.expedia.graphql.schema.extensions.getTypeOfFirstArgument
9+
import com.expedia.graphql.schema.extensions.isArray
10+
import com.expedia.graphql.schema.extensions.isList
711
import com.expedia.graphql.schema.generator.models.KGraphQLType
812
import graphql.schema.GraphQLType
913
import graphql.schema.GraphQLTypeReference
@@ -24,7 +28,7 @@ internal class TypesCache(private val supportedPackages: List<String>) {
2428
val cachedType = cache[cacheKeyString]
2529

2630
if (cachedType != null) {
27-
val kClass = getKClassFromKType(cacheKey.type)
31+
val kClass = cacheKey.type.getKClass()
2832
val isSameNameButNotSameClass = cachedType.kClass != kClass
2933
when {
3034
isSameNameButNotSameClass -> throw ConflictingTypesException(cachedType.kClass, kClass)
@@ -47,35 +51,43 @@ internal class TypesCache(private val supportedPackages: List<String>) {
4751

4852
@Throws(CouldNotGetNameOfEnumException::class)
4953
private fun getCacheKeyString(cacheKey: TypesCacheKey): String {
50-
val kClass = getKClassFromKType(cacheKey.type)
54+
val kClass = cacheKey.type.getKClass()
5155

5256
if (kClass.isSubclassOf(Enum::class)) {
5357
return kClass.simpleName ?: throw CouldNotGetNameOfEnumException(kClass)
5458
}
5559

5660
val cacheKeyFromTypeName = when {
57-
kClass.isSubclassOf(List::class) -> "List<${getJvmErasureNameFromList(cacheKey.type)}>"
58-
kClass.java.isArray -> "Array<${getJvmErasureNameFromList(cacheKey.type)}>"
61+
kClass.isList() -> "List<${getJvmErasureNameFromList(cacheKey.type)}>"
62+
kClass.isArray() -> getArrayTypeName(kClass, cacheKey.type)
5963
else -> getCacheTypeName(cacheKey.type)
6064
}
6165

6266
return "$cacheKeyFromTypeName:${cacheKey.inputType}"
6367
}
6468

65-
@Suppress("Detekt.UnsafeCast")
66-
private fun getKClassFromKType(kType: KType) = kType.classifier as KClass<*>
69+
private fun getArrayTypeName(kClass: KClass<*>, kType: KType) = when {
70+
kClass.isSubclassOf(IntArray::class) -> IntArray::class.simpleName
71+
kClass.isSubclassOf(LongArray::class) -> LongArray::class.simpleName
72+
kClass.isSubclassOf(ShortArray::class) -> ShortArray::class.simpleName
73+
kClass.isSubclassOf(FloatArray::class) -> FloatArray::class.simpleName
74+
kClass.isSubclassOf(DoubleArray::class) -> DoubleArray::class.simpleName
75+
kClass.isSubclassOf(CharArray::class) -> CharArray::class.simpleName
76+
kClass.isSubclassOf(BooleanArray::class) -> BooleanArray::class.simpleName
77+
else -> "Array<${getJvmErasureNameFromList(kType)}>"
78+
}
6779

6880
private fun getCacheTypeName(kType: KType): String {
6981
throwIfTypeIsNotSupported(kType)
7082
return getJvmErasureName(kType)
7183
}
7284

7385
private fun getJvmErasureNameFromList(type: KType): String =
74-
getJvmErasureName(type.arguments.first().type)
86+
getJvmErasureName(type.getTypeOfFirstArgument())
7587

7688
@Throws(CouldNotGetJvmNameOfKTypeException::class)
77-
private fun getJvmErasureName(kType: KType?): String =
78-
kType?.jvmErasure?.simpleName ?: throw CouldNotGetJvmNameOfKTypeException(kType)
89+
private fun getJvmErasureName(kType: KType): String =
90+
kType.jvmErasure.simpleName ?: throw CouldNotGetJvmNameOfKTypeException(kType)
7991

8092
@Throws(TypeNotSupportedException::class)
8193
private fun throwIfTypeIsNotSupported(type: KType) {
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
package com.expedia.graphql.schema.generator.types
22

3+
import com.expedia.graphql.schema.extensions.getArrayType
34
import com.expedia.graphql.schema.extensions.getTypeOfFirstArgument
45
import com.expedia.graphql.schema.generator.SchemaGenerator
56
import com.expedia.graphql.schema.generator.TypeBuilder
67
import graphql.schema.GraphQLList
78
import kotlin.reflect.KType
89

910
internal class ListTypeBuilder(generator: SchemaGenerator) : TypeBuilder(generator) {
11+
internal fun arrayType(type: KType, inputType: Boolean): GraphQLList =
12+
GraphQLList.list(graphQLTypeOf(type.getArrayType(), inputType))
13+
1014
internal fun listType(type: KType, inputType: Boolean): GraphQLList =
1115
GraphQLList.list(graphQLTypeOf(type.getTypeOfFirstArgument(), inputType))
1216
}

src/test/kotlin/com/expedia/graphql/schema/extensions/KClassExtensionsTest.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,10 +84,18 @@ internal class KClassExtensionsTest {
8484

8585
@Test
8686
fun `test list extension`() {
87-
assertTrue(listOf(1)::class.isGraphQLList())
88-
assertTrue(arrayListOf(1)::class.isGraphQLList())
89-
assertTrue(arrayOf(1)::class.isGraphQLList())
90-
assertFalse(MyTestClass::class.isGraphQLList())
87+
assertTrue(listOf(1)::class.isList())
88+
assertTrue(arrayListOf(1)::class.isList())
89+
assertFalse(arrayOf(1)::class.isList())
90+
assertFalse(MyTestClass::class.isList())
91+
}
92+
93+
@Test
94+
fun `test array extension`() {
95+
assertTrue(arrayOf(1)::class.isArray())
96+
assertTrue(intArrayOf(1)::class.isArray())
97+
assertFalse(listOf(1)::class.isArray())
98+
assertFalse(MyTestClass::class.isArray())
9199
}
92100

93101
@Test

src/test/kotlin/com/expedia/graphql/schema/generator/PolymorphicTests.kt

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,15 @@ internal class PolymorphicTests {
2020

2121
val graphqlType = schema.getType("BodyPart") as? GraphQLUnionType
2222
assertNotNull(graphqlType)
23-
graphqlType?.let { union ->
24-
assertTrue(union.types.any { it.name == "LeftHand" })
25-
assertTrue(union.types.any { it.name == "RightHand" })
26-
assertTrue(union.types.any { it.name == "Arm" })
27-
}
23+
assertTrue(graphqlType.types.any { it.name == "LeftHand" })
24+
assertTrue(graphqlType.types.any { it.name == "RightHand" })
25+
assertTrue(graphqlType.types.any { it.name == "Arm" })
2826

2927
assertNotNull(schema.getType("RightHand"))
3028
val leftHandType = schema.getType("LeftHand") as? GraphQLObjectType
3129

3230
assertNotNull(leftHandType)
33-
assertNotNull(leftHandType?.getFieldDefinition("associatedWith"))
31+
assertNotNull(leftHandType.getFieldDefinition("associatedWith"))
3432
}
3533

3634
@Test
@@ -66,17 +64,12 @@ internal class PolymorphicTests {
6664

6765
val carType = schema.getType("Car") as? GraphQLObjectType
6866
assertNotNull(carType)
69-
70-
carType?.let {
71-
assertNotNull(it.getFieldDefinition("model"))
72-
assertNotNull(it.getFieldDefinition("color"))
73-
}
67+
assertNotNull(carType.getFieldDefinition("model"))
68+
assertNotNull(carType.getFieldDefinition("color"))
7469

7570
val productType = schema.getType("Product") as? GraphQLUnionType
7671
assertNotNull(productType)
77-
productType?.let {
78-
assertTrue { it.types.contains(carType) }
79-
}
72+
assertTrue(productType.types.contains(carType))
8073
}
8174

8275
@Test

src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -241,9 +241,9 @@ class SchemaGeneratorTest {
241241
val bobChildren = res?.get("children") as? List<Map<String, Any>>
242242
assertNotNull(bobChildren)
243243

244-
val firstChild = bobChildren?.get(0)
245-
assertEquals("Alice", firstChild?.get("name"))
246-
assertNull(firstChild?.get("children"))
244+
val firstChild = bobChildren.get(0)
245+
assertEquals("Alice", firstChild["name"])
246+
assertNull(firstChild["children"])
247247
}
248248

249249
@Test
@@ -281,8 +281,7 @@ class SchemaGeneratorTest {
281281
}
282282

283283
class QueryWithArray {
284-
@Suppress("Detekt.ArrayPrimitive")
285-
fun sumOf(ints: Array<Int>): Int = ints.sum()
284+
fun sumOf(ints: IntArray): Int = ints.sum()
286285
fun sumOfComplexArray(objects: Array<ComplexWrappingType>): Int = objects.map { it.value }.sum()
287286
}
288287

0 commit comments

Comments
 (0)