diff --git a/src/main/kotlin/com/expedia/graphql/exceptions/CouldNotCastArgumentException.kt b/src/main/kotlin/com/expedia/graphql/exceptions/CouldNotCastArgumentException.kt new file mode 100644 index 0000000000..b37ce434f4 --- /dev/null +++ b/src/main/kotlin/com/expedia/graphql/exceptions/CouldNotCastArgumentException.kt @@ -0,0 +1,6 @@ +package com.expedia.graphql.exceptions + +import kotlin.reflect.KParameter + +class CouldNotCastArgumentException(kParameter: KParameter) + : GraphQLKotlinException("Could not cast or map arguments in the data fetcher for $kParameter") diff --git a/src/main/kotlin/com/expedia/graphql/exceptions/InvalidInputFieldTypeException.kt b/src/main/kotlin/com/expedia/graphql/exceptions/InvalidInputFieldTypeException.kt index c96ab9e2f9..ba6db2d56a 100644 --- a/src/main/kotlin/com/expedia/graphql/exceptions/InvalidInputFieldTypeException.kt +++ b/src/main/kotlin/com/expedia/graphql/exceptions/InvalidInputFieldTypeException.kt @@ -1,5 +1,7 @@ package com.expedia.graphql.exceptions +import kotlin.reflect.KParameter + /** * GraphQL Interfaces and Unions cannot be used as arguments. * Specification reference: https://facebook.github.io/graphql/draft/#sec-Field-Arguments @@ -28,4 +30,5 @@ package com.expedia.graphql.exceptions * * data class PartOfUnion( val property: Int) : UnionMarkup */ -class InvalidInputFieldTypeException : GraphQLKotlinException("Object field argument cannot be an interface or a union") +class InvalidInputFieldTypeException(kParameter: KParameter) + : GraphQLKotlinException("Argument cannot be an interface or a union, $kParameter") diff --git a/src/main/kotlin/com/expedia/graphql/execution/FunctionDataFetcher.kt b/src/main/kotlin/com/expedia/graphql/execution/FunctionDataFetcher.kt index b9f9b9427e..49c1e42cfa 100644 --- a/src/main/kotlin/com/expedia/graphql/execution/FunctionDataFetcher.kt +++ b/src/main/kotlin/com/expedia/graphql/execution/FunctionDataFetcher.kt @@ -36,7 +36,9 @@ class FunctionDataFetcher( val instance = target ?: environment.getSource() return instance?.let { - val parameterValues = fn.valueParameters.map { param -> mapParameterToValue(param, environment) }.toTypedArray() + val parameterValues = fn.valueParameters + .map { param -> mapParameterToValue(param, environment) } + .toTypedArray() if (fn.isSuspend) { GlobalScope.async { @@ -53,7 +55,7 @@ class FunctionDataFetcher( environment.getContext() } else { val name = param.getName() - val klazz = param.type.javaTypeClass + val klazz = param.javaTypeClass() val value = objectMapper.convertValue(environment.arguments[name], klazz) val predicateResult = executionPredicate?.evaluate(value = value, parameter = param, environment = environment) diff --git a/src/main/kotlin/com/expedia/graphql/generator/extensions/suppressedExtenstions.kt b/src/main/kotlin/com/expedia/graphql/generator/extensions/suppressedExtenstions.kt index adc855f515..0e916e9c47 100644 --- a/src/main/kotlin/com/expedia/graphql/generator/extensions/suppressedExtenstions.kt +++ b/src/main/kotlin/com/expedia/graphql/generator/extensions/suppressedExtenstions.kt @@ -1,8 +1,8 @@ package com.expedia.graphql.generator.extensions -import kotlin.reflect.KType +import com.expedia.graphql.exceptions.CouldNotCastArgumentException +import kotlin.reflect.KParameter import kotlin.reflect.jvm.javaType -@Suppress("Detekt.UnsafeCast") -internal val KType.javaTypeClass: Class<*> - get() = this.javaType as Class<*> +@Throws(CouldNotCastArgumentException::class) +internal fun KParameter.javaTypeClass(): Class<*> = this.type.javaType as? Class<*> ?: throw CouldNotCastArgumentException(this) diff --git a/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt b/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt index 13382f41ad..5a1efa889a 100644 --- a/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt +++ b/src/main/kotlin/com/expedia/graphql/generator/types/FunctionTypeBuilder.kt @@ -53,7 +53,7 @@ internal class FunctionTypeBuilder(generator: SchemaGenerator) : TypeBuilder(gen private fun argument(parameter: KParameter): GraphQLArgument { if (parameter.isInterface()) { - throw InvalidInputFieldTypeException() + throw InvalidInputFieldTypeException(parameter) } val builder = GraphQLArgument.newArgument() diff --git a/src/test/kotlin/com/expedia/graphql/execution/FunctionDataFetcherTest.kt b/src/test/kotlin/com/expedia/graphql/execution/FunctionDataFetcherTest.kt index 23d4b3452c..b5a4baeda9 100644 --- a/src/test/kotlin/com/expedia/graphql/execution/FunctionDataFetcherTest.kt +++ b/src/test/kotlin/com/expedia/graphql/execution/FunctionDataFetcherTest.kt @@ -1,11 +1,13 @@ package com.expedia.graphql.execution import com.expedia.graphql.annotations.GraphQLContext +import com.expedia.graphql.exceptions.CouldNotCastArgumentException import graphql.schema.DataFetchingEnvironment import io.mockk.every import io.mockk.mockk import org.junit.jupiter.api.Test import kotlin.test.assertEquals +import kotlin.test.assertFailsWith import kotlin.test.assertNull internal class FunctionDataFetcherTest { @@ -13,6 +15,10 @@ internal class FunctionDataFetcherTest { internal class MyClass { fun print(string: String) = string + fun printArray(items: Array) = items.joinToString(separator = ":") + + fun printList(items: List) = items.joinToString(separator = ":") + fun context(@GraphQLContext string: String) = string } @@ -68,4 +74,23 @@ internal class FunctionDataFetcherTest { every { mockEnvironmet.arguments } returns mapOf("string" to "hello") assertEquals(expected = "hello", actual = dataFetcher.get(mockEnvironmet)) } + + @Test + fun `array inputs can be converted by the object mapper`() { + val dataFetcher = FunctionDataFetcher(target = MyClass(), fn = MyClass::printArray, executionPredicate = null) + val mockEnvironmet: DataFetchingEnvironment = mockk() + every { mockEnvironmet.arguments } returns mapOf("items" to arrayOf("foo", "bar")) + assertEquals(expected = "foo:bar", actual = dataFetcher.get(mockEnvironmet)) + } + + @Test + fun `list inputs throws exception`() { + val dataFetcher = FunctionDataFetcher(target = MyClass(), fn = MyClass::printList, executionPredicate = null) + val mockEnvironmet: DataFetchingEnvironment = mockk() + every { mockEnvironmet.arguments } returns mapOf("items" to listOf("foo", "bar")) + + assertFailsWith(CouldNotCastArgumentException::class) { + dataFetcher.get(mockEnvironmet) + } + } } diff --git a/src/test/kotlin/com/expedia/graphql/generator/extensions/KParameterExtensionsKtTest.kt b/src/test/kotlin/com/expedia/graphql/generator/extensions/KParameterExtensionsKtTest.kt index 8eee4dae1f..07b265d74d 100644 --- a/src/test/kotlin/com/expedia/graphql/generator/extensions/KParameterExtensionsKtTest.kt +++ b/src/test/kotlin/com/expedia/graphql/generator/extensions/KParameterExtensionsKtTest.kt @@ -9,14 +9,23 @@ import kotlin.reflect.KParameter import kotlin.reflect.full.findParameterByName import kotlin.test.assertEquals import kotlin.test.assertFailsWith +import kotlin.test.assertFalse import kotlin.test.assertNull +import kotlin.test.assertTrue internal class KParameterExtensionsKtTest { @GraphQLDescription("class description") internal data class MyClass(val foo: String) + internal interface MyInterface { + val value: String + } + internal class Container { + + internal fun interfaceInput(myInterface: MyInterface) = myInterface + internal fun noDescription(myClass: MyClass) = myClass internal fun paramDescription(@GraphQLDescription("param description") myClass: MyClass) = myClass @@ -48,4 +57,16 @@ internal class KParameterExtensionsKtTest { val param = Container::noDescription.findParameterByName("myClass") assertNull(param?.getGraphQLDescription()) } + + @Test + fun `class input is invalid`() { + val param = Container::noDescription.findParameterByName("myClass") + assertFalse(param?.isInterface().isTrue()) + } + + @Test + fun `interface input is invalid`() { + val param = Container::interfaceInput.findParameterByName("myInterface") + assertTrue(param?.isInterface().isTrue()) + } } diff --git a/src/test/kotlin/com/expedia/graphql/generator/extensions/SuppressedExtenstionsKtTest.kt b/src/test/kotlin/com/expedia/graphql/generator/extensions/SuppressedExtenstionsKtTest.kt index 4dee96d243..7d626cdb90 100644 --- a/src/test/kotlin/com/expedia/graphql/generator/extensions/SuppressedExtenstionsKtTest.kt +++ b/src/test/kotlin/com/expedia/graphql/generator/extensions/SuppressedExtenstionsKtTest.kt @@ -1,6 +1,7 @@ package com.expedia.graphql.generator.extensions import org.junit.jupiter.api.Test +import kotlin.reflect.full.findParameterByName import kotlin.test.assertEquals internal class SuppressedExtenstionsKtTest { @@ -11,6 +12,6 @@ internal class SuppressedExtenstionsKtTest { @Test fun javaTypeClass() { - assertEquals(expected = String::class.java, actual = MyClass::stringFun.returnType.javaTypeClass) + assertEquals(expected = String::class.java, actual = MyClass::stringFun.findParameterByName("string")?.javaTypeClass()) } }