Skip to content

Commit 908b6f8

Browse files
authored
fix: fix exception for Iterables without type variable (#545)
Mostly reverts d8ce008 to rely on `Collection::class` instead of `Iterable::class` again, as the latter broke Kotlin `IntRange` without adding clear benefits (it would IMO be debatable if a range should even be represented as list). Resolves #176
1 parent fb49820 commit 908b6f8

File tree

5 files changed

+74
-29
lines changed

5 files changed

+74
-29
lines changed

kgraphql/src/jvm/kotlin/com/apurebase/kgraphql/BenchmarkSchema.kt

Lines changed: 3 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,21 @@ import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
55

66
data class ModelOne(val name: String, val quantity: Int = 1, val active: Boolean = true)
77

8-
data class ModelTwo(val one: ModelOne, val range: FakeIntRange)
8+
data class ModelTwo(val one: ModelOne, val range: IntRange)
99

1010
data class ModelThree(val id: String, val twos: List<ModelTwo>)
1111

12-
// wrapping needed, because https://github.com/stuebingerb/KGraphQL/commit/d8ce0085130b9f0f30c0c2f31ed52f44d6456981
13-
// destroyed compatibility with ranges in the schema.
14-
// please see: https://github.com/stuebingerb/KGraphQL/issues/176
15-
class FakeIntRange(range: IntRange) {
16-
val start = range.first
17-
val endInclusive = range.last
18-
}
19-
2012
object BenchmarkSchema {
2113
val ones = listOf(ModelOne("DUDE"), ModelOne("GUY"), ModelOne("PAL"), ModelOne("FELLA"))
2214

2315
val oneResolver: suspend () -> List<ModelOne> = { ones }
2416

2517
val twoResolver: suspend (name: String) -> ModelTwo? = { name ->
26-
ones.find { it.name == name }?.let { ModelTwo(it, FakeIntRange(it.quantity..12)) }
18+
ones.find { it.name == name }?.let { ModelTwo(it, it.quantity..12) }
2719
}
2820

2921
val threeResolver: suspend () -> ModelThree =
30-
{ ModelThree("", ones.map { ModelTwo(it, FakeIntRange(it.quantity..10)) }) }
22+
{ ModelThree("", ones.map { ModelTwo(it, it.quantity..10) }) }
3123

3224
object HasOneResolver {
3325
fun oneResolver(): List<ModelOne> {

kgraphql/src/main/kotlin/com/apurebase/kgraphql/Extensions.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ internal fun String.dropQuotes(): String = if (isLiteral()) {
2222

2323
internal fun String.isLiteral(): Boolean = startsWith('\"') && endsWith('\"')
2424

25-
internal fun KClass<*>.isIterable() = isSubclassOf(Iterable::class) || this in typeByPrimitiveArrayClass.keys
25+
internal fun KClass<*>.isCollectionType() = isSubclassOf(Collection::class) || this in typeByPrimitiveArrayClass.keys
2626

27-
internal fun KType.isIterable() = jvmErasure.isIterable() || toString().startsWith("kotlin.Array")
27+
internal fun KType.isCollectionType() = jvmErasure.isCollectionType() || toString().startsWith("kotlin.Array")
2828

2929
internal val typeByPrimitiveArrayClass = mapOf(
3030
IntArray::class to Int::class.createType(),
@@ -36,9 +36,11 @@ internal val typeByPrimitiveArrayClass = mapOf(
3636
BooleanArray::class to Boolean::class.createType()
3737
)
3838

39-
internal fun KType.getIterableElementType(): KType {
40-
require(isIterable()) { "KType $this is not collection type" }
41-
return typeByPrimitiveArrayClass[jvmErasure] ?: arguments.firstOrNull()?.type ?: throw NoSuchElementException("KType $this has no type arguments")
39+
internal fun KType.getCollectionElementType(): KType {
40+
require(isCollectionType()) { "KType $this is not collection type" }
41+
return typeByPrimitiveArrayClass[jvmErasure]
42+
?: arguments.firstOrNull()?.type
43+
?: throw NoSuchElementException("KType $this has no type arguments")
4244
}
4345

4446
internal suspend fun <T, R> Iterable<T>.mapIndexedParallel(

kgraphql/src/main/kotlin/com/apurebase/kgraphql/schema/structure/SchemaCompilation.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ package com.apurebase.kgraphql.schema.structure
55
import com.apurebase.kgraphql.Context
66
import com.apurebase.kgraphql.configuration.SchemaConfiguration
77
import com.apurebase.kgraphql.defaultKQLTypeName
8-
import com.apurebase.kgraphql.getIterableElementType
9-
import com.apurebase.kgraphql.isIterable
8+
import com.apurebase.kgraphql.getCollectionElementType
9+
import com.apurebase.kgraphql.isCollectionType
1010
import com.apurebase.kgraphql.request.isIntrospectionType
1111
import com.apurebase.kgraphql.schema.DefaultSchema
1212
import com.apurebase.kgraphql.schema.SchemaException
@@ -232,7 +232,7 @@ open class SchemaCompilation(
232232
}
233233

234234
private suspend fun handlePossiblyWrappedType(kType: KType, typeCategory: TypeCategory): Type = when {
235-
kType.isIterable() -> handleCollectionType(kType, typeCategory)
235+
kType.isCollectionType() -> handleCollectionType(kType, typeCategory)
236236
kType.jvmErasure == Context::class && typeCategory == TypeCategory.INPUT -> contextType
237237
kType.jvmErasure == Execution.Node::class && typeCategory == TypeCategory.INPUT -> executionType
238238
kType.jvmErasure == Context::class && typeCategory == TypeCategory.QUERY -> throw SchemaException("Context type cannot be part of schema")
@@ -267,7 +267,7 @@ open class SchemaCompilation(
267267
}
268268

269269
private suspend fun handleCollectionType(kType: KType, typeCategory: TypeCategory): Type {
270-
val type = kType.getIterableElementType()
270+
val type = kType.getCollectionElementType()
271271
val nullableListType = Type.AList(handlePossiblyWrappedType(type, typeCategory), kType.jvmErasure)
272272
return applyNullability(kType.isMarkedNullable, nullableListType)
273273
}

kgraphql/src/test/kotlin/com/apurebase/kgraphql/specification/typesystem/ListsSpecificationTest.kt

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class ListsSpecificationTest {
1818
fun `list arguments are valid`() {
1919
val schema = KGraphQL.schema {
2020
query("list") {
21-
resolver { list: Iterable<String> -> list }
21+
resolver { list: List<String> -> list }
2222
}
2323
}
2424

@@ -37,7 +37,7 @@ class ListsSpecificationTest {
3737
fun `lists with nullable entries are valid`() {
3838
val schema = KGraphQL.schema {
3939
query("list") {
40-
resolver { list: Iterable<String?> -> list }
40+
resolver { list: List<String?> -> list }
4141
}
4242
}
4343

@@ -54,7 +54,7 @@ class ListsSpecificationTest {
5454
fun `lists with non-nullable entries should not accept list with null element`() {
5555
val schema = KGraphQL.schema {
5656
query("list") {
57-
resolver { list: Iterable<String> -> list }
57+
resolver { list: List<String> -> list }
5858
}
5959
}
6060

@@ -71,7 +71,7 @@ class ListsSpecificationTest {
7171
fun `by default coerce single element input as collection`() {
7272
val schema = KGraphQL.schema {
7373
query("list") {
74-
resolver { list: Iterable<String> -> list }
74+
resolver { list: List<String> -> list }
7575
}
7676
}
7777

@@ -88,7 +88,7 @@ class ListsSpecificationTest {
8888
fun `null value is not coerced as single element collection`() {
8989
val schema = KGraphQL.schema {
9090
query("list") {
91-
resolver { list: Iterable<String>? -> list }
91+
resolver { list: List<String>? -> list }
9292
}
9393
}
9494

@@ -105,7 +105,7 @@ class ListsSpecificationTest {
105105
fun `list argument can be declared non-nullable`() {
106106
val schema = KGraphQL.schema {
107107
query("list") {
108-
resolver { list: Iterable<String> -> list }
108+
resolver { list: List<String> -> list }
109109
}
110110
}
111111

@@ -119,9 +119,9 @@ class ListsSpecificationTest {
119119
}
120120

121121
@Test
122-
fun `Iterable implementations are treated as list`() {
122+
fun `Collection implementations are treated as list`() {
123123

124-
fun getResult(): Iterable<String> = listOf("POTATO", "BATATO", "ROTATO")
124+
fun getResult(): Collection<String> = listOf("POTATO", "BATATO", "ROTATO")
125125

126126
val schema = KGraphQL.schema {
127127
query("list") {
@@ -130,7 +130,22 @@ class ListsSpecificationTest {
130130
}
131131

132132
val response = deserialize(schema.executeBlocking("{ list }"))
133-
response.extract<Iterable<String>>("data/list") shouldBe getResult()
133+
response.extract<Collection<String>>("data/list") shouldBe getResult()
134+
}
135+
136+
@Test
137+
fun `Set implementations are treated as list`() {
138+
139+
fun getResult(): Set<String> = setOf("POTATO", "BATATO", "ROTATO")
140+
141+
val schema = KGraphQL.schema {
142+
query("list") {
143+
resolver { -> getResult() }
144+
}
145+
}
146+
147+
val response = deserialize(schema.executeBlocking("{ list }"))
148+
response.extract<Set<String>>("data/list") shouldBe getResult()
134149
}
135150

136151
@Test

kgraphql/src/test/kotlin/com/apurebase/kgraphql/specification/typesystem/ObjectsSpecificationTest.kt

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -486,4 +486,40 @@ class ObjectsSpecificationTest {
486486
indexOf("short") shouldBeGreaterThan indexOf("long")
487487
}
488488
}
489+
490+
// https://github.com/stuebingerb/KGraphQL/issues/176
491+
@Test
492+
fun `IntRange can be used`() {
493+
data class Model(val intRange: IntRange)
494+
495+
val schema = schema {
496+
query("model") {
497+
resolver { -> Model(1..9) }
498+
}
499+
}
500+
501+
schema.printSchema() shouldBe """
502+
type IntRange {
503+
endExclusive: Int!
504+
endInclusive: Int!
505+
first: Int!
506+
last: Int!
507+
start: Int!
508+
step: Int!
509+
}
510+
511+
type Model {
512+
intRange: IntRange!
513+
}
514+
515+
type Query {
516+
model: Model!
517+
}
518+
519+
""".trimIndent()
520+
521+
schema.executeBlocking("{ model { intRange { start endInclusive endExclusive first last step } } }") shouldBe """
522+
{"data":{"model":{"intRange":{"start":1,"endInclusive":9,"endExclusive":10,"first":1,"last":9,"step":1}}}}
523+
""".trimIndent()
524+
}
489525
}

0 commit comments

Comments
 (0)