Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,8 @@ class DataLoaderPropertyDSL<T, K, R>(
this.inputValues.addAll(inputValues)
}

override fun setReturnType(type: KType) {
// NOOP
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.apurebase.kgraphql.schema.model.FunctionWrapper
import com.apurebase.kgraphql.schema.model.InputValueDef
import com.apurebase.kgraphql.schema.model.PropertyDef
import java.lang.IllegalArgumentException
import kotlin.reflect.KType


class PropertyDSL<T : Any, R>(val name : String, block : PropertyDSL<T, R>.() -> Unit) : LimitedAccessItemDSL<T>(), ResolverDSL.Target {
Expand Down Expand Up @@ -62,4 +63,8 @@ class PropertyDSL<T : Any, R>(val name : String, block : PropertyDSL<T, R>.() ->
override fun addInputValues(inputValues: Collection<InputValueDef<*>>) {
this.inputValues.addAll(inputValues)
}

override fun setReturnType(type: KType) {
// NOOP
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ package com.apurebase.kgraphql.schema.dsl

import com.apurebase.kgraphql.schema.dsl.types.InputValuesDSL
import com.apurebase.kgraphql.schema.model.InputValueDef
import kotlin.reflect.*


class ResolverDSL(private val target: Target) {
class ResolverDSL(val target: Target) {
fun withArgs(block : InputValuesDSL.() -> Unit){
val inputValuesDSL = InputValuesDSL().apply(block)

Expand All @@ -13,7 +14,14 @@ class ResolverDSL(private val target: Target) {
})
}

@OptIn(ExperimentalStdlibApi::class)
inline fun <reified T: Any> returns(): ResolverDSL {
target.setReturnType(typeOf<T>())
return this
}

interface Target {
fun addInputValues(inputValues: Collection<InputValueDef<*>>)
fun setReturnType(type: KType)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.apurebase.kgraphql.schema.model.InputValueDef
import com.apurebase.kgraphql.schema.model.PropertyDef
import com.apurebase.kgraphql.schema.model.TypeDef
import java.lang.IllegalArgumentException
import kotlin.reflect.KType


class UnionPropertyDSL<T : Any>(val name : String, block: UnionPropertyDSL<T>.() -> Unit) : LimitedAccessItemDSL<T>(), ResolverDSL.Target {
Expand Down Expand Up @@ -69,4 +70,8 @@ class UnionPropertyDSL<T : Any>(val name : String, block: UnionPropertyDSL<T>.()
override fun addInputValues(inputValues: Collection<InputValueDef<*>>) {
this.inputValues.addAll(inputValues)
}

override fun setReturnType(type: KType) {
throw IllegalArgumentException("A return value cannot be set on an Union type")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.apurebase.kgraphql.schema.dsl.ResolverDSL
import com.apurebase.kgraphql.schema.model.FunctionWrapper
import com.apurebase.kgraphql.schema.model.InputValueDef
import kotlin.reflect.KFunction
import kotlin.reflect.KType


abstract class AbstractOperationDSL(
Expand All @@ -17,10 +18,18 @@ abstract class AbstractOperationDSL(

internal var functionWrapper: FunctionWrapper<*>? = null

var explicitReturnType: KType? = null

private fun resolver(function: FunctionWrapper<*>): ResolverDSL {

require(function.hasReturnType()) {
"Resolver for '$name' has no return value"
try {
require(function.hasReturnType()) {
"Resolver for '$name' has no return value"
}
} catch (e: Throwable) {
if ("KotlinReflectionInternalError" !in e.toString()) {
throw e
}
}

functionWrapper = function
Expand Down Expand Up @@ -58,5 +67,8 @@ abstract class AbstractOperationDSL(
this.inputValues.addAll(inputValues)
}

override fun setReturnType(type: KType) {
explicitReturnType = type
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class MutationDSL(
isDeprecated = isDeprecated,
deprecationReason = deprecationReason,
inputValues = inputValues,
accessRule = accessRuleBlock
accessRule = accessRuleBlock,
explicitReturnType = explicitReturnType
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ class QueryDSL(
isDeprecated = isDeprecated,
deprecationReason = deprecationReason,
inputValues = inputValues,
accessRule = accessRuleBlock
accessRule = accessRuleBlock,
explicitReturnType = explicitReturnType
)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.apurebase.kgraphql.schema.model

import com.apurebase.kgraphql.Context
import kotlin.reflect.KType

abstract class BaseOperationDef<T, R>(
name : String,
private val operationWrapper: FunctionWrapper<R>,
val inputValues : List<InputValueDef<*>>,
val accessRule : ((T?, Context) -> Exception?)?
) : Definition(name), OperationDef<R>, FunctionWrapper<R> by operationWrapper
val accessRule : ((T?, Context) -> Exception?)?,
private val explicitReturnType: KType?
) : Definition(name), OperationDef<R>, FunctionWrapper<R> by operationWrapper {

val returnType: KType get() = explicitReturnType ?: kFunction.returnType

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.apurebase.kgraphql.schema.model

import com.apurebase.kgraphql.Context
import kotlin.reflect.KType

class MutationDef<R> (
name : String,
Expand All @@ -9,5 +10,6 @@ class MutationDef<R> (
override val isDeprecated: Boolean,
override val deprecationReason: String?,
accessRule: ((Nothing?, Context) -> Exception?)? = null,
inputValues : List<InputValueDef<*>> = emptyList()
) : BaseOperationDef<Nothing, R>(name, resolver, inputValues, accessRule), DescribedDef
inputValues : List<InputValueDef<*>> = emptyList(),
explicitReturnType: KType? = null
) : BaseOperationDef<Nothing, R>(name, resolver, inputValues, accessRule, explicitReturnType), DescribedDef
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ interface PropertyDef<T> : Depreciable, DescribedDef {
override val isDeprecated: Boolean = false,
override val deprecationReason: String? = null,
accessRule : ((T?, Context) -> Exception?)? = null,
inputValues : List<InputValueDef<*>> = emptyList()
) : BaseOperationDef<T, R>(name, resolver, inputValues, accessRule), PropertyDef<T>
inputValues : List<InputValueDef<*>> = emptyList(),
explicitReturnType: KType? = null
) : BaseOperationDef<T, R>(name, resolver, inputValues, accessRule, explicitReturnType), PropertyDef<T>

/**
* [T] -> The Parent Type
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.apurebase.kgraphql.schema.model

import com.apurebase.kgraphql.Context
import kotlin.reflect.KType

class QueryDef<R> (
name : String,
Expand All @@ -9,5 +10,6 @@ class QueryDef<R> (
override val isDeprecated: Boolean = false,
override val deprecationReason: String? = null,
accessRule: ((Nothing?, Context) -> Exception?)? = null,
inputValues : List<InputValueDef<*>> = emptyList()
) : BaseOperationDef<Nothing, R>(name, resolver, inputValues, accessRule), DescribedDef
inputValues : List<InputValueDef<*>> = emptyList(),
explicitReturnType: KType? = null
) : BaseOperationDef<Nothing, R>(name, resolver, inputValues, accessRule, explicitReturnType), DescribedDef
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.apurebase.kgraphql.schema.model

import com.apurebase.kgraphql.Context
import kotlin.reflect.KType

class SubscriptionDef<R> (
name : String,
Expand All @@ -9,5 +10,6 @@ class SubscriptionDef<R> (
override val isDeprecated: Boolean,
override val deprecationReason: String?,
accessRule: ((Nothing?, Context) -> Exception?)? = null,
inputValues : List<InputValueDef<*>> = emptyList()
) : BaseOperationDef<Nothing, R>(name, resolver, inputValues, accessRule), DescribedDef
inputValues : List<InputValueDef<*>> = emptyList(),
explicitReturnType: KType? = null
) : BaseOperationDef<Nothing, R>(name, resolver, inputValues, accessRule, explicitReturnType), DescribedDef
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ class SchemaCompilation(
)

private suspend fun handleOperation(operation : BaseOperationDef<*, *>) : Field {
val returnType = handlePossiblyWrappedType(operation.kFunction.returnType, TypeCategory.QUERY)
val returnType = handlePossiblyWrappedType(operation.returnType, TypeCategory.QUERY)
val inputValues = handleInputValues(operation.name, operation, operation.inputValues)
return Field.Function(operation, returnType, inputValues)
}
Expand All @@ -159,18 +159,26 @@ class SchemaCompilation(
return Field.Union(unionProperty, unionProperty.nullable, type, inputValues)
}

private suspend fun handlePossiblyWrappedType(kType : KType, typeCategory: TypeCategory) : Type = when {
kType.isIterable() -> handleCollectionType(kType, typeCategory)
kType.jvmErasure == Context::class && typeCategory == TypeCategory.INPUT -> contextType
kType.jvmErasure == Execution.Node::class && typeCategory == TypeCategory.INPUT -> executionType
kType.jvmErasure == Context::class && typeCategory == TypeCategory.QUERY -> throw SchemaException("Context type cannot be part of schema")
kType.arguments.isNotEmpty() -> throw SchemaException("Generic types are not supported by GraphQL, found $kType")
kType.jvmErasure.isSealed -> TypeDef.Union(
name = kType.jvmErasure.simpleName!!,
members = kType.jvmErasure.sealedSubclasses.toSet(),
description = null
).let { handleUnionType(it) }
else -> handleSimpleType(kType, typeCategory)
private suspend fun handlePossiblyWrappedType(kType : KType, typeCategory: TypeCategory) : Type = try {
when {
kType.isIterable() -> handleCollectionType(kType, typeCategory)
kType.jvmErasure == Context::class && typeCategory == TypeCategory.INPUT -> contextType
kType.jvmErasure == Execution.Node::class && typeCategory == TypeCategory.INPUT -> executionType
kType.jvmErasure == Context::class && typeCategory == TypeCategory.QUERY -> throw SchemaException("Context type cannot be part of schema")
kType.arguments.isNotEmpty() -> throw SchemaException("Generic types are not supported by GraphQL, found $kType")
kType.jvmErasure.isSealed -> TypeDef.Union(
name = kType.jvmErasure.simpleName!!,
members = kType.jvmErasure.sealedSubclasses.toSet(),
description = null
).let { handleUnionType(it) }
else -> handleSimpleType(kType, typeCategory)
}
} catch (e: Throwable) {
if ("KotlinReflectionInternalError" in e.toString()) {
throw SchemaException("If you construct a query/mutation generically, you must specify the return type T explicitly with resolver{ ... }.returns<T>()")
} else {
throw e
}
}

private suspend fun handleCollectionType(kType: KType, typeCategory: TypeCategory): Type {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.apurebase.kgraphql.schema

import com.apurebase.kgraphql.*
import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
import com.apurebase.kgraphql.schema.introspection.TypeKind
import com.apurebase.kgraphql.schema.scalar.StringScalarCoercion
import com.apurebase.kgraphql.schema.structure.Field
Expand Down Expand Up @@ -603,4 +604,45 @@ class SchemaBuilderTest {
message shouldBeEqualTo "Resolver for 'main' has no return value"
}
}

inline fun <reified T: Any> SchemaBuilder.createGenericQuery(x: T) {
query("data") {
resolver { -> x }.returns<T>()
}
}

@Test
fun `specifying return type explicitly allows generic query creation`(){
val schema = defaultSchema {
createGenericQuery(InputOne("generic"))
}

assertThat(schema.typeByKClass(InputOne::class), notNullValue())
}

inline fun <reified T: Any> SchemaBuilder.createGenericQueryWithoutReturns(x: T) {
query("data") {
resolver { -> x }
}
}

@Test
fun `not specifying return value explicitly with generic query creation throws exception`(){
invoking {
defaultSchema {
createGenericQueryWithoutReturns(InputOne("generic"))
}
} shouldThrow SchemaException::class with {
message shouldBeEqualTo "If you construct a query/mutation generically, you must specify the return type T explicitly with resolver{ ... }.returns<T>()"
}
}

@Test
fun `specifying return type explicitly allows generic query creation that returns List of T`(){
val schema = defaultSchema {
createGenericQuery(listOf("generic"))
}
val result = deserialize(schema.executeBlocking("{data}"))
assertThat(result.extract("data/data"), equalTo(listOf("generic")))
}
}