diff --git a/src/main/kotlin/graphql/kickstart/tools/MethodFieldResolver.kt b/src/main/kotlin/graphql/kickstart/tools/MethodFieldResolver.kt index 959eff02..79cdb3f3 100644 --- a/src/main/kotlin/graphql/kickstart/tools/MethodFieldResolver.kt +++ b/src/main/kotlin/graphql/kickstart/tools/MethodFieldResolver.kt @@ -11,6 +11,8 @@ import graphql.schema.DataFetchingEnvironment import graphql.schema.GraphQLTypeUtil.isScalar import kotlinx.coroutines.future.future import java.lang.reflect.Method +import java.lang.reflect.ParameterizedType +import java.lang.reflect.WildcardType import java.util.* import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn import kotlin.reflect.full.valueParameters @@ -22,216 +24,223 @@ import kotlin.reflect.jvm.kotlinFunction */ internal class MethodFieldResolver(field: FieldDefinition, search: FieldResolverScanner.Search, options: SchemaParserOptions, val method: Method) : FieldResolver(field, search, options, search.type) { - companion object { - fun isBatched(method: Method, search: FieldResolverScanner.Search): Boolean { - if (method.getAnnotation(Batched::class.java) != null) { - if (!search.allowBatched) { - throw ResolverError("The @Batched annotation is only allowed on non-root resolver methods, but it was found on ${search.type.unwrap().name}#${method.name}!") - } - - return true - } - return false + companion object { + fun isBatched(method: Method, search: FieldResolverScanner.Search): Boolean { + if (method.getAnnotation(Batched::class.java) != null) { + if (!search.allowBatched) { + throw ResolverError("The @Batched annotation is only allowed on non-root resolver methods, but it was found on ${search.type.unwrap().name}#${method.name}!") } - } - private val additionalLastArgument = - try { - method.kotlinFunction?.valueParameters?.size ?: method.parameterCount == (field.inputValueDefinitions.size + getIndexOffset() + 1) - } catch (e: InternalError) { - method.parameterCount == (field.inputValueDefinitions.size + getIndexOffset() + 1) - } - - override fun createDataFetcher(): DataFetcher<*> { - val batched = isBatched(method, search) - val args = mutableListOf() - val mapper = options.objectMapperProvider.provide(field) - - // Add source argument if this is a resolver (but not a root resolver) - if (this.search.requiredFirstParameterType != null) { - val expectedType = if (batched) Iterable::class.java else this.search.requiredFirstParameterType - - args.add { environment -> - val source = environment.getSource() - if (!(expectedType.isAssignableFrom(source.javaClass))) { - throw ResolverError("Source type (${source.javaClass.name}) is not expected type (${expectedType.name})!") - } - - source - } + return true + } + return false + } + } + + private val additionalLastArgument = + try { + method.kotlinFunction?.valueParameters?.size ?: method.parameterCount == (field.inputValueDefinitions.size + getIndexOffset() + 1) + } catch (e: InternalError) { + method.parameterCount == (field.inputValueDefinitions.size + getIndexOffset() + 1) + } + + override fun createDataFetcher(): DataFetcher<*> { + val batched = isBatched(method, search) + val args = mutableListOf() + val mapper = options.objectMapperProvider.provide(field) + + // Add source argument if this is a resolver (but not a root resolver) + if (this.search.requiredFirstParameterType != null) { + val expectedType = if (batched) Iterable::class.java else this.search.requiredFirstParameterType + + args.add { environment -> + val source = environment.getSource() + if (!(expectedType.isAssignableFrom(source.javaClass))) { + throw ResolverError("Source type (${source.javaClass.name}) is not expected type (${expectedType.name})!") } - // Add an argument for each argument defined in the GraphQL schema - this.field.inputValueDefinitions.forEachIndexed { index, definition -> - - val genericParameterType = this.getJavaMethodParameterType(index) - ?: throw ResolverError("Missing method type at position ${this.getJavaMethodParameterIndex(index)}, this is most likely a bug with graphql-java-tools") - - val isNonNull = definition.type is NonNullType - val isOptional = this.genericType.getRawClass(genericParameterType) == Optional::class.java - - val typeReference = object : TypeReference() { - override fun getType() = genericParameterType - } - - args.add { environment -> - val value = environment.arguments[definition.name] ?: if (isNonNull) { - throw ResolverError("Missing required argument with name '${definition.name}', this is most likely a bug with graphql-java-tools") - } else { - null - } - - if (value == null && isOptional) { - if (environment.containsArgument(definition.name)) { - return@add Optional.empty() - } else { - return@add null - } - } - - if (value != null - && genericParameterType.unwrap().isAssignableFrom(value.javaClass) - && isScalarType(environment, definition.type, genericParameterType)) { - return@add value - } - - return@add mapper.convertValue(value, typeReference) - } - } + source + } + } - // Add DataFetchingEnvironment/Context argument - if (this.additionalLastArgument) { - when (this.method.parameterTypes.last()) { - null -> throw ResolverError("Expected at least one argument but got none, this is most likely a bug with graphql-java-tools") - options.contextClass -> args.add { environment -> environment.getContext() } - else -> args.add { environment -> environment } - } - } + // Add an argument for each argument defined in the GraphQL schema + this.field.inputValueDefinitions.forEachIndexed { index, definition -> - return if (batched) { - BatchedMethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options) - } else { - if (args.isEmpty() && isTrivialDataFetcher(this.method)) { - TrivialMethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options) - } else { - MethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options) - } + val genericParameterType = this.getJavaMethodParameterType(index) + ?: throw ResolverError("Missing method type at position ${this.getJavaMethodParameterIndex(index)}, this is most likely a bug with graphql-java-tools") - } - } + val isNonNull = definition.type is NonNullType + val isOptional = this.genericType.getRawClass(genericParameterType) == Optional::class.java - private fun isScalarType(environment: DataFetchingEnvironment, type: Type<*>, genericParameterType: JavaType): Boolean = - when (type) { - is ListType -> List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType)) - && isScalarType(environment, type.type, this.genericType.unwrapGenericType(genericParameterType)) - is TypeName -> environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) } ?: false - is NonNullType -> isScalarType(environment, type.type, genericParameterType) - else -> false - } - - override fun scanForMatches(): List { - val batched = isBatched(method, search) - val unwrappedGenericType = genericType.unwrapGenericType(try { - method.kotlinFunction?.returnType?.javaType ?: method.genericReturnType - } catch (e: InternalError) { - method.genericReturnType - }) - val returnValueMatch = TypeClassMatcher.PotentialMatch.returnValue(field.type, unwrappedGenericType, genericType, SchemaClassScanner.ReturnValueReference(method), batched) - - return field.inputValueDefinitions.mapIndexed { i, inputDefinition -> - TypeClassMatcher.PotentialMatch.parameterType(inputDefinition.type, getJavaMethodParameterType(i)!!, genericType, SchemaClassScanner.MethodParameterReference(method, i), batched) - } + listOf(returnValueMatch) - } + val typeReference = object : TypeReference() { + override fun getType() = genericParameterType + } - private fun getIndexOffset(): Int { - return if (resolverInfo is DataClassTypeResolverInfo && !method.declaringClass.isAssignableFrom(resolverInfo.dataClassType)) { - 1 + args.add { environment -> + val value = environment.arguments[definition.name] ?: if (isNonNull) { + throw ResolverError("Missing required argument with name '${definition.name}', this is most likely a bug with graphql-java-tools") } else { - 0 + null } - } - - private fun getJavaMethodParameterIndex(index: Int) = index + getIndexOffset() - private fun getJavaMethodParameterType(index: Int): JavaType? { - val methodIndex = getJavaMethodParameterIndex(index) - val parameters = method.parameterTypes + if (value == null && isOptional) { + if (environment.containsArgument(definition.name)) { + return@add Optional.empty() + } else { + return@add null + } + } - return if (parameters.size > methodIndex) { - method.genericParameterTypes[getJavaMethodParameterIndex(index)] - } else { - null + if (value != null + && genericParameterType.unwrap().isAssignableFrom(value.javaClass) + && isScalarType(environment, definition.type, genericParameterType)) { + return@add value } + + return@add mapper.convertValue(value, typeReference) + } } - override fun toString() = "MethodFieldResolver{method=$method}" -} + // Add DataFetchingEnvironment/Context argument + if (this.additionalLastArgument) { + when (this.method.parameterTypes.last()) { + null -> throw ResolverError("Expected at least one argument but got none, this is most likely a bug with graphql-java-tools") + options.contextClass -> args.add { environment -> environment.getContext() } + else -> args.add { environment -> environment } + } + } -open class MethodFieldResolverDataFetcher(private val sourceResolver: SourceResolver, method: Method, private val args: List, private val options: SchemaParserOptions) : DataFetcher { + return if (batched) { + BatchedMethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options) + } else { + if (args.isEmpty() && isTrivialDataFetcher(this.method)) { + TrivialMethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options) + } else { + MethodFieldResolverDataFetcher(getSourceResolver(), this.method, args, options) + } - // Convert to reflactasm reflection - private val methodAccess = MethodAccess.get(method.declaringClass)!! - private val methodIndex = methodAccess.getIndex(method.name, *method.parameterTypes) - private val isSuspendFunction = try { - method.kotlinFunction?.isSuspend == true + } + } + + private fun isScalarType(environment: DataFetchingEnvironment, type: Type<*>, genericParameterType: JavaType): Boolean = + when (type) { + is ListType -> List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType)) + && isScalarType(environment, type.type, this.genericType.unwrapGenericType(genericParameterType)) + is TypeName -> environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) && !isJavaLanguageType(genericParameterType) } ?: false + is NonNullType -> isScalarType(environment, type.type, genericParameterType) + else -> false + } + + private fun isJavaLanguageType(type: JavaType): Boolean = + when (type) { + is ParameterizedType -> isJavaLanguageType(type.actualTypeArguments[0]) + is WildcardType -> isJavaLanguageType(type.upperBounds[0]) + else -> genericType.getRawClass(type).`package`.name == "java.lang" + } + + override fun scanForMatches(): List { + val batched = isBatched(method, search) + val unwrappedGenericType = genericType.unwrapGenericType(try { + method.kotlinFunction?.returnType?.javaType ?: method.genericReturnType } catch (e: InternalError) { - false + method.genericReturnType + }) + val returnValueMatch = TypeClassMatcher.PotentialMatch.returnValue(field.type, unwrappedGenericType, genericType, SchemaClassScanner.ReturnValueReference(method), batched) + + return field.inputValueDefinitions.mapIndexed { i, inputDefinition -> + TypeClassMatcher.PotentialMatch.parameterType(inputDefinition.type, getJavaMethodParameterType(i)!!, genericType, SchemaClassScanner.MethodParameterReference(method, i), batched) + } + listOf(returnValueMatch) + } + + private fun getIndexOffset(): Int { + return if (resolverInfo is DataClassTypeResolverInfo && !method.declaringClass.isAssignableFrom(resolverInfo.dataClassType)) { + 1 + } else { + 0 } + } - private class CompareGenericWrappers { - companion object : Comparator { - override fun compare(w1: GenericWrapper, w2: GenericWrapper): Int = when { - w1.type.isAssignableFrom(w2.type) -> 1 - else -> -1 - } - } - } + private fun getJavaMethodParameterIndex(index: Int) = index + getIndexOffset() - override fun get(environment: DataFetchingEnvironment): Any? { - val source = sourceResolver(environment) - val args = this.args.map { it(environment) }.toTypedArray() + private fun getJavaMethodParameterType(index: Int): JavaType? { + val methodIndex = getJavaMethodParameterIndex(index) + val parameters = method.parameterTypes - return if (isSuspendFunction) { - environment.coroutineScope().future(options.coroutineContextProvider.provide()) { - methodAccess.invokeSuspend(source, methodIndex, args)?.transformWithGenericWrapper(environment) - } - } else { - methodAccess.invoke(source, methodIndex, *args)?.transformWithGenericWrapper(environment) - } + return if (parameters.size > methodIndex) { + method.genericParameterTypes[getJavaMethodParameterIndex(index)] + } else { + null } + } - private fun Any.transformWithGenericWrapper(environment: DataFetchingEnvironment): Any? { - return options.genericWrappers - .asSequence() - .filter { it.type.isInstance(this) } - .sortedWith(CompareGenericWrappers) - .firstOrNull() - ?.transformer?.invoke(this, environment) ?: this - } + override fun toString() = "MethodFieldResolver{method=$method}" +} - /** - * Function that return the object used to fetch the data - * It can be a DataFetcher or an entity - */ - @Suppress("unused") - open fun getWrappedFetchingObject(environment: DataFetchingEnvironment): Any { - return sourceResolver(environment) +open class MethodFieldResolverDataFetcher(private val sourceResolver: SourceResolver, method: Method, private val args: List, private val options: SchemaParserOptions) : DataFetcher { + + // Convert to reflactasm reflection + private val methodAccess = MethodAccess.get(method.declaringClass)!! + private val methodIndex = methodAccess.getIndex(method.name, *method.parameterTypes) + private val isSuspendFunction = try { + method.kotlinFunction?.isSuspend == true + } catch (e: InternalError) { + false + } + + private class CompareGenericWrappers { + companion object : Comparator { + override fun compare(w1: GenericWrapper, w2: GenericWrapper): Int = when { + w1.type.isAssignableFrom(w2.type) -> 1 + else -> -1 + } + } + } + + override fun get(environment: DataFetchingEnvironment): Any? { + val source = sourceResolver(environment) + val args = this.args.map { it(environment) }.toTypedArray() + + return if (isSuspendFunction) { + environment.coroutineScope().future(options.coroutineContextProvider.provide()) { + methodAccess.invokeSuspend(source, methodIndex, args)?.transformWithGenericWrapper(environment) + } + } else { + methodAccess.invoke(source, methodIndex, *args)?.transformWithGenericWrapper(environment) } + } + + private fun Any.transformWithGenericWrapper(environment: DataFetchingEnvironment): Any? { + return options.genericWrappers + .asSequence() + .filter { it.type.isInstance(this) } + .sortedWith(CompareGenericWrappers) + .firstOrNull() + ?.transformer?.invoke(this, environment) ?: this + } + + /** + * Function that return the object used to fetch the data + * It can be a DataFetcher or an entity + */ + @Suppress("unused") + open fun getWrappedFetchingObject(environment: DataFetchingEnvironment): Any { + return sourceResolver(environment) + } } -open class TrivialMethodFieldResolverDataFetcher(private val sourceResolver: SourceResolver, method: Method, private val args: List, private val options: SchemaParserOptions) : MethodFieldResolverDataFetcher(sourceResolver, method, args, options), TrivialDataFetcher { +open class TrivialMethodFieldResolverDataFetcher(sourceResolver: SourceResolver, method: Method, args: List, options: SchemaParserOptions) : MethodFieldResolverDataFetcher(sourceResolver, method, args, options), TrivialDataFetcher { } private suspend inline fun MethodAccess.invokeSuspend(target: Any, methodIndex: Int, args: Array): Any? { - return suspendCoroutineUninterceptedOrReturn { continuation -> - invoke(target, methodIndex, *args + continuation) - } + return suspendCoroutineUninterceptedOrReturn { continuation -> + invoke(target, methodIndex, *args + continuation) + } } class BatchedMethodFieldResolverDataFetcher(sourceResolver: SourceResolver, method: Method, args: List, options: SchemaParserOptions) : MethodFieldResolverDataFetcher(sourceResolver, method, args, options) { - @Batched - override fun get(environment: DataFetchingEnvironment) = super.get(environment) + @Batched + override fun get(environment: DataFetchingEnvironment) = super.get(environment) } internal typealias ArgumentPlaceholder = (DataFetchingEnvironment) -> Any? diff --git a/src/test/groovy/graphql/kickstart/tools/BuiltInLongIdSpec.groovy b/src/test/groovy/graphql/kickstart/tools/BuiltInLongIdSpec.groovy new file mode 100644 index 00000000..1382467a --- /dev/null +++ b/src/test/groovy/graphql/kickstart/tools/BuiltInLongIdSpec.groovy @@ -0,0 +1,81 @@ +package graphql.kickstart.tools + +import graphql.GraphQL +import graphql.execution.AsyncExecutionStrategy +import graphql.schema.GraphQLSchema +import spock.lang.Shared +import spock.lang.Specification + +class BuiltInLongIdSpec extends Specification { + + @Shared + GraphQL gql + + def setupSpec() { + GraphQLSchema schema = SchemaParser.newParser().schemaString('''\ + type Query { + itemByLongId(id: ID!): Item! + itemsByLongIds(ids: [ID!]!): [Item!]! + } + + type Item { + id: ID! + } + '''.stripIndent()) + .resolvers(new QueryWithLongItemResolver()) + .build() + .makeExecutableSchema() + gql = GraphQL.newGraphQL(schema) + .queryExecutionStrategy(new AsyncExecutionStrategy()) + .build() + } + + def "supports Long as ID as input"() { + when: + def data = Utils.assertNoGraphQlErrors(gql) { + ''' + { + itemByLongId(id: 1) { + id + } + } + ''' + } + + then: + data.itemByLongId != null + data.itemByLongId.id == "1" + } + + def "supports list of Long as ID as input"() { + when: + def data = Utils.assertNoGraphQlErrors(gql) { + ''' + { + itemsByLongIds(ids: [1,2,3]) { + id + } + } + ''' + } + + then: + data.itemsByLongIds != null + data.itemsByLongIds.size == 3 + data.itemsByLongIds[0].id == "1" + } + + class QueryWithLongItemResolver implements GraphQLQueryResolver { + Item itemByLongId(Long id) { + new Item(id: id) + } + + List itemsByLongIds(List ids) { + ids.collect { new Item(id: it) } + } + } + + class Item { + Long id + } +} diff --git a/src/test/groovy/graphql/kickstart/tools/EndToEndSpec.groovy b/src/test/groovy/graphql/kickstart/tools/EndToEndSpec.groovy index e33d1784..f77555a5 100644 --- a/src/test/groovy/graphql/kickstart/tools/EndToEndSpec.groovy +++ b/src/test/groovy/graphql/kickstart/tools/EndToEndSpec.groovy @@ -29,23 +29,23 @@ class EndToEndSpec extends Specification { GraphQLSchema schema = EndToEndSpecHelperKt.createSchema() batchedGql = GraphQL.newGraphQL(schema) - .queryExecutionStrategy(new BatchedExecutionStrategy()) - .build() + .queryExecutionStrategy(new BatchedExecutionStrategy()) + .build() gql = GraphQL.newGraphQL(schema) - .queryExecutionStrategy(new AsyncExecutionStrategy()) - .build() + .queryExecutionStrategy(new AsyncExecutionStrategy()) + .build() } def "schema comments are used as descriptions"() { expect: - gql.graphQLSchema.allTypesAsList.find { it.name == 'Type' }?.valueDefinitionMap?.TYPE_1?.description == "Item type 1" + gql.graphQLSchema.allTypesAsList.find { it.name == 'Type' }?.valueDefinitionMap?.TYPE_1?.description == "Item type 1" } def "generated schema should respond to simple queries"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { items(itemsInput: {name: "item1"}) { id @@ -53,16 +53,16 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - noExceptionThrown() + noExceptionThrown() } def "generated schema should respond to simple mutations"() { when: - def data = Utils.assertNoGraphQlErrors(gql, [name: "new1", type: Type.TYPE_2.toString()]) { - ''' + def data = Utils.assertNoGraphQlErrors(gql, [name: "new1", type: Type.TYPE_2.toString()]) { + ''' mutation addNewItem($name: String!, $type: Type!) { addItem(newItem: {name: $name, type: $type}) { id @@ -71,56 +71,56 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.addItem + data.addItem } def "generated schema should execute the subscription query"() { when: - def newItem = new Item(1, "item", Type.TYPE_1, UUID.randomUUID(), []) - def returnedItem = null - def data = Utils.assertNoGraphQlErrors(gql, [:], new OnItemCreatedContext(newItem)) { - ''' + def newItem = new Item(1, "item", Type.TYPE_1, UUID.randomUUID(), []) + def returnedItem = null + def data = Utils.assertNoGraphQlErrors(gql, [:], new OnItemCreatedContext(newItem)) { + ''' subscription { onItemCreated { id } } ''' - } - CountDownLatch latch = new CountDownLatch(1) - (data as Publisher).subscribe(new Subscriber() { - @Override - void onSubscribe(org.reactivestreams.Subscription s) { + } + CountDownLatch latch = new CountDownLatch(1) + (data as Publisher).subscribe(new Subscriber() { + @Override + void onSubscribe(org.reactivestreams.Subscription s) { - } + } - @Override - void onNext(ExecutionResult executionResult) { - returnedItem = executionResult.data - latch.countDown() - } + @Override + void onNext(ExecutionResult executionResult) { + returnedItem = executionResult.data + latch.countDown() + } - @Override - void onError(Throwable t) { - } + @Override + void onError(Throwable t) { + } - @Override - void onComplete() { - } - }) - latch.await(3, TimeUnit.SECONDS) + @Override + void onComplete() { + } + }) + latch.await(3, TimeUnit.SECONDS) then: - returnedItem.get("onItemCreated").id == 1 + returnedItem.get("onItemCreated").id == 1 } def "generated schema should handle interface types"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { itemsByInterface { name @@ -128,16 +128,16 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.itemsByInterface + data.itemsByInterface } def "generated schema should handle union types"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { allItems { ... on Item { @@ -151,16 +151,16 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.allItems + data.allItems } def "generated schema should handle nested union types"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { nestedUnionItems { ... on Item { @@ -175,33 +175,33 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.nestedUnionItems == [[itemId: 0], [itemId: 1], [otherItemId: 0], [otherItemId: 1], [thirdItemId: 100]] + data.nestedUnionItems == [[itemId: 0], [itemId: 1], [otherItemId: 0], [otherItemId: 1], [thirdItemId: 100]] } def "generated schema should handle scalar types"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { itemByUUID(uuid: "38f685f1-b460-4a54-a17f-7fd69e8cf3f8") { uuid } } ''' - } + } then: - data.itemByUUID + data.itemByUUID } def "generated schema should handle non nullable scalar types"() { when: def fileParts = [new MockPart("test.doc", "Hello"), new MockPart("test.doc", "World")] def args = ["fileParts": fileParts] - def data = Utils.assertNoGraphQlErrors( gql, args) { + def data = Utils.assertNoGraphQlErrors(gql, args) { ''' mutation ($fileParts: [Upload!]!) { echoFiles(fileParts: $fileParts)} ''' @@ -224,7 +224,7 @@ class EndToEndSpec extends Specification { } then: - data.propertyHashMapItems == [ [name: "bob", age:55] ] + data.propertyHashMapItems == [[name: "bob", age: 55]] } def "generated schema should handle any java.util.Map (using SortedMap) types as property maps"() { @@ -241,7 +241,7 @@ class EndToEndSpec extends Specification { } then: - data.propertySortedMapItems == [ [name: "Arthur", age:76], [name: "Jane", age:28] ] + data.propertySortedMapItems == [[name: "Arthur", age: 76], [name: "Jane", age: 28]] } // In this test a dictionary entry for the schema type ComplexMapItem is defined @@ -251,7 +251,7 @@ class EndToEndSpec extends Specification { // resolver/POJO graph. def "generated schema should handle java.util.Map types as property maps when containing complex data"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { + def data = Utils.assertNoGraphQlErrors(gql) { ''' { propertyMapWithComplexItems { @@ -265,7 +265,7 @@ class EndToEndSpec extends Specification { } then: - data.propertyMapWithComplexItems == [ [nameId:[id:150], age:72] ] + data.propertyMapWithComplexItems == [[nameId: [id: 150], age: 72]] } // This behavior is consistent with PropertyDataFetcher @@ -283,7 +283,7 @@ class EndToEndSpec extends Specification { } then: - data.propertyMapMissingNamePropItems == [ [name: null, age:55] ] + data.propertyMapMissingNamePropItems == [[name: null, age: 55]] } // In this test a dictonary entry for the schema type NestedComplexMapItem is defined @@ -308,14 +308,14 @@ class EndToEndSpec extends Specification { } then: - data.propertyMapWithNestedComplexItems == [ [ nested:[ item: [id:63] ], age:72] ] + data.propertyMapWithNestedComplexItems == [[nested: [item: [id: 63]], age: 72]] } def "generated schema should handle optional arguments"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { missing: itemsWithOptionalInput { id @@ -326,17 +326,17 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.missing?.size > 1 - data.present?.size == 1 + data.missing?.size > 1 + data.present?.size == 1 } def "generated schema should handle optional arguments using java.util.Optional"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { missing: itemsWithOptionalInputExplicit { id @@ -347,17 +347,17 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.missing?.size > 1 - data.present?.size == 1 + data.missing?.size > 1 + data.present?.size == 1 } def "generated schema should handle optional return types using java.util.Optional"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { missing: optionalItem(itemsInput: {name: "item?"}) { id @@ -368,31 +368,31 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.missing == null - data.present + data.missing == null + data.present } def "generated schema should pass default arguments"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { defaultArgument } ''' - } + } then: - data.defaultArgument == true + data.defaultArgument == true } def "introspection shouldn't fail for arguments of type list with a default value (defaultEnumListArgument)"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { __type(name: "Query") { name @@ -406,16 +406,16 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.__type + data.__type } def "generated schema should return null without errors for null value with nested fields"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { complexNullableType { first @@ -424,31 +424,31 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.containsKey('complexNullableType') - data.complexNullableType == null + data.containsKey('complexNullableType') + data.complexNullableType == null } def "generated schema handles nested lists in input type fields"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { complexInputType(complexInput: [[{first: "foo", second: [[{first: "bar"}]]}]]) } ''' - } + } then: - data.complexInputType + data.complexInputType } def "generated schema should use type extensions"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { extendedType { first @@ -456,62 +456,62 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.extendedType - data.extendedType.first - data.extendedType.second + data.extendedType + data.extendedType.first + data.extendedType.second } def "generated schema uses properties if no methods are found"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { propertyField } ''' - } + } then: - data.propertyField + data.propertyField } def "generated schema allows enums in input types"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { enumInputType(type: TYPE_2) } ''' - } + } then: - data.enumInputType == "TYPE_2" + data.enumInputType == "TYPE_2" } def "generated schema works with custom scalars as input values"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { customScalarMapInputType(customScalarMap: { test: "me" }) } ''' - } + } then: - data.customScalarMapInputType == [ + data.customScalarMapInputType == [ test: "me" - ] + ] } def "generated schema supports generic properties"() { when: def data = Utils.assertNoGraphQlErrors(gql) { - ''' + ''' { itemWithGenericProperties { keys @@ -528,24 +528,24 @@ class EndToEndSpec extends Specification { def "generated schema supports batched datafetchers"() { when: - def data = Utils.assertNoGraphQlErrors(batchedGql) { - ''' + def data = Utils.assertNoGraphQlErrors(batchedGql) { + ''' { allBaseItems { name: batchedName } } ''' - } + } then: - data.allBaseItems.collect { it.name } == ['item1', 'item2'] + data.allBaseItems.collect { it.name } == ['item1', 'item2'] } def "generated schema supports batched datafetchers with params"() { when: - def data = Utils.assertNoGraphQlErrors(batchedGql) { - ''' + def data = Utils.assertNoGraphQlErrors(batchedGql) { + ''' { allBaseItems { tags: batchedWithParamsTags(names: ["item2-tag1"]) { @@ -554,49 +554,49 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.allBaseItems.collect { it.tags.collect { it.name } } == [[], ['item2-tag1']] + data.allBaseItems.collect { it.tags.collect { it.name } } == [[], ['item2-tag1']] } def "generated schema supports overriding built-in scalars"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { itemByBuiltInId(id: "38f685f1-b460-4a54-a17f-7fd69e8cf3f8") { name } } ''' - } + } then: - noExceptionThrown() - data.itemByBuiltInId != null + noExceptionThrown() + data.itemByBuiltInId != null } def "generated schema supports DataFetcherResult"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { dataFetcherResult { name } } ''' - } + } then: - data.dataFetcherResult.name == "item1" + data.dataFetcherResult.name == "item1" } def "generated schema supports Kotlin suspend functions"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { coroutineItems { id @@ -604,63 +604,64 @@ class EndToEndSpec extends Specification { } } ''' - } + } then: - data.coroutineItems == [[id:0, name:"item1"], [id:1, name:"item2"]] + data.coroutineItems == [[id: 0, name: "item1"], [id: 1, name: "item2"]] } def "generated schema supports Kotlin coroutine channels for the subscription query"() { when: - def newItem = new Item(1, "item", Type.TYPE_1, UUID.randomUUID(), []) - def data = Utils.assertNoGraphQlErrors(gql, [:], new OnItemCreatedContext(newItem)) { - ''' + def newItem = new Item(1, "item", Type.TYPE_1, UUID.randomUUID(), []) + def data = Utils.assertNoGraphQlErrors(gql, [:], new OnItemCreatedContext(newItem)) { + ''' subscription { onItemCreatedCoroutineChannel { id } } ''' - } - def subscriber = new TestEnvironment().newManualSubscriber(data as Publisher) + } + def subscriber = new TestEnvironment().newManualSubscriber(data as Publisher) then: - subscriber.requestNextElement().data.get("onItemCreatedCoroutineChannel").id == 1 - subscriber.expectCompletion() + subscriber.requestNextElement().data.get("onItemCreatedCoroutineChannel").id == 1 + subscriber.expectCompletion() } def "generated schema supports Kotlin coroutine channels with suspend function for the subscription query"() { when: - def newItem = new Item(1, "item", Type.TYPE_1, UUID.randomUUID(), []) - def data = Utils.assertNoGraphQlErrors(gql, [:], new OnItemCreatedContext(newItem)) { - ''' + def newItem = new Item(1, "item", Type.TYPE_1, UUID.randomUUID(), []) + def data = Utils.assertNoGraphQlErrors(gql, [:], new OnItemCreatedContext(newItem)) { + ''' subscription { onItemCreatedCoroutineChannelAndSuspendFunction { id } } ''' - } - def subscriber = new TestEnvironment().newManualSubscriber(data as Publisher) + } + def subscriber = new TestEnvironment().newManualSubscriber(data as Publisher) then: - subscriber.requestNextElement().data.get("onItemCreatedCoroutineChannelAndSuspendFunction").id == 1 - subscriber.expectCompletion() + subscriber.requestNextElement().data.get("onItemCreatedCoroutineChannelAndSuspendFunction").id == 1 + subscriber.expectCompletion() } def "generated schema supports arrays"() { when: - def data = Utils.assertNoGraphQlErrors(gql) { - ''' + def data = Utils.assertNoGraphQlErrors(gql) { + ''' { arrayItems { name } } ''' - } + } then: - data.arrayItems.collect { it.name } == ['item1', 'item2'] + data.arrayItems.collect { it.name } == ['item1', 'item2'] } + } diff --git a/src/test/kotlin/graphql/kickstart/tools/EndToEndSpecHelper.kt b/src/test/kotlin/graphql/kickstart/tools/EndToEndSpecHelper.kt index 38480e0c..22e2f509 100644 --- a/src/test/kotlin/graphql/kickstart/tools/EndToEndSpecHelper.kt +++ b/src/test/kotlin/graphql/kickstart/tools/EndToEndSpecHelper.kt @@ -16,17 +16,17 @@ import java.util.concurrent.CompletableFuture import javax.servlet.http.Part fun createSchema() = SchemaParser.newParser() - .schemaString(schemaDefinition) - .resolvers(Query(), Mutation(), Subscription(), ItemResolver(), UnusedRootResolver(), UnusedResolver(), EchoFilesResolver()) - .scalars(customScalarUUID, customScalarMap, customScalarId, uploadScalar) - .dictionary("OtherItem", OtherItemWithWrongName::class) - .dictionary("ThirdItem", ThirdItem::class) - .dictionary("ComplexMapItem", ComplexMapItem::class) - .dictionary("NestedComplexMapItem", NestedComplexMapItem::class) - .build() - .makeExecutableSchema() - -val schemaDefinition = """ + .schemaString(schemaDefinition) + .resolvers(Query(), Mutation(), Subscription(), ItemResolver(), UnusedRootResolver(), UnusedResolver(), EchoFilesResolver()) + .scalars(customScalarUUID, customScalarMap, customScalarId, uploadScalar) + .dictionary("OtherItem", OtherItemWithWrongName::class) + .dictionary("ThirdItem", ThirdItem::class) + .dictionary("ComplexMapItem", ComplexMapItem::class) + .dictionary("NestedComplexMapItem", NestedComplexMapItem::class) + .build() + .makeExecutableSchema() + +private const val schemaDefinition = """ ## Private comment! scalar UUID @@ -212,157 +212,157 @@ type ItemWithGenericProperties { val items = mutableListOf( - Item(0, "item1", Type.TYPE_1, UUID.fromString("38f685f1-b460-4a54-a17f-7fd69e8cf3f8"), listOf(Tag(1, "item1-tag1"), Tag(2, "item1-tag2"))), - Item(1, "item2", Type.TYPE_2, UUID.fromString("38f685f1-b460-4a54-b17f-7fd69e8cf3f8"), listOf(Tag(3, "item2-tag1"), Tag(4, "item2-tag2"))) + Item(0, "item1", Type.TYPE_1, UUID.fromString("38f685f1-b460-4a54-a17f-7fd69e8cf3f8"), listOf(Tag(1, "item1-tag1"), Tag(2, "item1-tag2"))), + Item(1, "item2", Type.TYPE_2, UUID.fromString("38f685f1-b460-4a54-b17f-7fd69e8cf3f8"), listOf(Tag(3, "item2-tag1"), Tag(4, "item2-tag2"))) ) val otherItems = mutableListOf( - OtherItemWithWrongName(0, "otherItem1", Type.TYPE_1, UUID.fromString("38f685f1-b460-4a54-c17f-7fd69e8cf3f8")), - OtherItemWithWrongName(1, "otherItem2", Type.TYPE_2, UUID.fromString("38f685f1-b460-4a54-d17f-7fd69e8cf3f8")) + OtherItemWithWrongName(0, "otherItem1", Type.TYPE_1, UUID.fromString("38f685f1-b460-4a54-c17f-7fd69e8cf3f8")), + OtherItemWithWrongName(1, "otherItem2", Type.TYPE_2, UUID.fromString("38f685f1-b460-4a54-d17f-7fd69e8cf3f8")) ) val thirdItems = mutableListOf( - ThirdItem(100) + ThirdItem(100) ) val propetyHashMapItems = mutableListOf( - hashMapOf("name" to "bob", "age" to 55) + hashMapOf("name" to "bob", "age" to 55) ) val propertyMapMissingNamePropItems = mutableListOf( - hashMapOf("age" to 55) + hashMapOf("age" to 55) ) val propetySortedMapItems = mutableListOf( - sortedMapOf("name" to "Arthur", "age" to 76), - sortedMapOf("name" to "Jane", "age" to 28) + sortedMapOf("name" to "Arthur", "age" to 76), + sortedMapOf("name" to "Jane", "age" to 28) ) val propertyMapWithComplexItems = mutableListOf( - hashMapOf("nameId" to ComplexMapItem(150), "age" to 72) + hashMapOf("nameId" to ComplexMapItem(150), "age" to 72) ) val propertyMapWithNestedComplexItems = mutableListOf( - hashMapOf("nested" to NestedComplexMapItem(UndiscoveredItem(63)), "age" to 72) + hashMapOf("nested" to NestedComplexMapItem(UndiscoveredItem(63)), "age" to 72) ) class Query : GraphQLQueryResolver, ListListResolver() { - fun isEmpty() = items.isEmpty() - fun allBaseItems() = items - fun items(input: ItemSearchInput): List = items.filter { it.name == input.name } - fun optionalItem(input: ItemSearchInput) = items(input).firstOrNull()?.let { Optional.of(it) } ?: Optional.empty() - fun allItems(): List = items + otherItems - fun otherUnionItems(): List = items + thirdItems - fun nestedUnionItems(): List = items + otherItems + thirdItems - fun itemsByInterface(): List = items + otherItems - fun itemByUUID(uuid: UUID): Item? = items.find { it.uuid == uuid } - fun itemByBuiltInId(id: UUID): Item? { - return items.find { it.uuid == id } - } + fun isEmpty() = items.isEmpty() + fun allBaseItems() = items + fun items(input: ItemSearchInput): List = items.filter { it.name == input.name } + fun optionalItem(input: ItemSearchInput) = items(input).firstOrNull()?.let { Optional.of(it) } ?: Optional.empty() + fun allItems(): List = items + otherItems + fun otherUnionItems(): List = items + thirdItems + fun nestedUnionItems(): List = items + otherItems + thirdItems + fun itemsByInterface(): List = items + otherItems + fun itemByUUID(uuid: UUID): Item? = items.find { it.uuid == uuid } + fun itemByBuiltInId(id: UUID): Item? { + return items.find { it.uuid == id } + } - fun itemsWithOptionalInput(input: ItemSearchInput?) = if (input == null) items else items(input) - fun itemsWithOptionalInputExplicit(input: Optional?) = if (input?.isPresent == true) items(input.get()) else items - fun enumInputType(type: Type) = type - fun customScalarMapInputType(customScalarMap: Map) = customScalarMap - fun itemWithGenericProperties() = ItemWithGenericProperties(listOf("A", "B")) + fun itemsWithOptionalInput(input: ItemSearchInput?) = if (input == null) items else items(input) + fun itemsWithOptionalInputExplicit(input: Optional?) = if (input?.isPresent == true) items(input.get()) else items + fun enumInputType(type: Type) = type + fun customScalarMapInputType(customScalarMap: Map) = customScalarMap + fun itemWithGenericProperties() = ItemWithGenericProperties(listOf("A", "B")) - fun defaultArgument(arg: Boolean) = arg - fun defaultEnumListArgument(types: List) = types + fun defaultArgument(arg: Boolean) = arg + fun defaultEnumListArgument(types: List) = types - fun futureItems() = CompletableFuture.completedFuture(items) - fun complexNullableType(): ComplexNullable? = null + fun futureItems() = CompletableFuture.completedFuture(items) + fun complexNullableType(): ComplexNullable? = null - fun complexInputType(input: List?>?) = input?.firstOrNull()?.firstOrNull()?.let { it.first == "foo" && it.second?.firstOrNull()?.firstOrNull()?.first == "bar" } - ?: false + fun complexInputType(input: List?>?) = input?.firstOrNull()?.firstOrNull()?.let { it.first == "foo" && it.second?.firstOrNull()?.firstOrNull()?.first == "bar" } + ?: false - fun extendedType() = ExtendedType() + fun extendedType() = ExtendedType() - fun getItemsWithGetResolver() = items + fun getItemsWithGetResolver() = items - fun getFieldClass() = items - fun getFieldHashCode() = items + fun getFieldClass() = items + fun getFieldHashCode() = items - fun propertyHashMapItems() = propetyHashMapItems - fun propertyMapMissingNamePropItems() = propertyMapMissingNamePropItems - fun propertySortedMapItems() = propetySortedMapItems - fun propertyMapWithComplexItems() = propertyMapWithComplexItems - fun propertyMapWithNestedComplexItems() = propertyMapWithNestedComplexItems + fun propertyHashMapItems() = propetyHashMapItems + fun propertyMapMissingNamePropItems() = propertyMapMissingNamePropItems + fun propertySortedMapItems() = propetySortedMapItems + fun propertyMapWithComplexItems() = propertyMapWithComplexItems + fun propertyMapWithNestedComplexItems() = propertyMapWithNestedComplexItems - private val propertyField = "test" + private val propertyField = "test" - fun dataFetcherResult(): DataFetcherResult { - return DataFetcherResult.newResult().data(items.first()).build() - } + fun dataFetcherResult(): DataFetcherResult { + return DataFetcherResult.newResult().data(items.first()).build() + } - suspend fun coroutineItems(): List = CompletableDeferred(items).await() + suspend fun coroutineItems(): List = CompletableDeferred(items).await() - fun arrayItems() = items.toTypedArray() + fun arrayItems() = items.toTypedArray() } class UnusedRootResolver : GraphQLQueryResolver class UnusedResolver : GraphQLResolver class ExtendedType { - fun first() = "test" - fun second() = "test" + fun first() = "test" + fun second() = "test" } abstract class ListListResolver { - fun listList(): List> = listOf(listOf()) + fun listList(): List> = listOf(listOf()) } class Mutation : GraphQLMutationResolver { - fun addItem(input: NewItemInput): Item { - return Item(items.size, input.name, input.type, UUID.randomUUID(), listOf()) // Don't actually add the item to the list, since we want the test to be deterministic - } + fun addItem(input: NewItemInput): Item { + return Item(items.size, input.name, input.type, UUID.randomUUID(), listOf()) // Don't actually add the item to the list, since we want the test to be deterministic + } } class OnItemCreatedContext(val newItem: Item) class Subscription : GraphQLSubscriptionResolver { - fun onItemCreated(env: DataFetchingEnvironment) = - Publisher { subscriber -> - subscriber.onNext(env.getContext().newItem) + fun onItemCreated(env: DataFetchingEnvironment) = + Publisher { subscriber -> + subscriber.onNext(env.getContext().newItem) // subscriber.onComplete() - } - - fun onItemCreatedCoroutineChannel(env: DataFetchingEnvironment): ReceiveChannel { - val channel = Channel(1) - channel.offer(env.getContext().newItem) - return channel - } - - suspend fun onItemCreatedCoroutineChannelAndSuspendFunction(env: DataFetchingEnvironment): ReceiveChannel { - return coroutineScope { - val channel = Channel(1) - channel.offer(env.getContext().newItem) - channel - } + } + + fun onItemCreatedCoroutineChannel(env: DataFetchingEnvironment): ReceiveChannel { + val channel = Channel(1) + channel.offer(env.getContext().newItem) + return channel + } + + suspend fun onItemCreatedCoroutineChannelAndSuspendFunction(env: DataFetchingEnvironment): ReceiveChannel { + return coroutineScope { + val channel = Channel(1) + channel.offer(env.getContext().newItem) + channel } + } } class ItemResolver : GraphQLResolver { - fun tags(item: Item, names: List?): List = item.tags.filter { names?.contains(it.name) ?: true } + fun tags(item: Item, names: List?): List = item.tags.filter { names?.contains(it.name) ?: true } - @Batched - fun batchedName(items: List) = items.map { it.name } + @Batched + fun batchedName(items: List) = items.map { it.name } - @Batched - fun batchedWithParamsTags(items: List, names: List?): List> = items.map { - it.tags.filter { - names?.contains(it.name) ?: true - } + @Batched + fun batchedWithParamsTags(items: List, names: List?): List> = items.map { + it.tags.filter { + names?.contains(it.name) ?: true } + } } -class EchoFilesResolver: GraphQLMutationResolver { - fun echoFiles(fileParts: List): List = fileParts.map { String(it.inputStream.readBytes()) } +class EchoFilesResolver : GraphQLMutationResolver { + fun echoFiles(fileParts: List): List = fileParts.map { String(it.inputStream.readBytes()) } } interface ItemInterface { - val name: String - val type: Type - val uuid: UUID + val name: String + val type: Type + val uuid: UUID } enum class Type { TYPE_1, TYPE_2 } @@ -379,99 +379,99 @@ data class ComplexNullable(val first: String, val second: String, val third: Str data class ComplexInputType(val first: String, val second: List?>?) data class ComplexInputTypeTwo(val first: String) data class ItemWithGenericProperties(val keys: List) -class MockPart(private val name: String, private val content: String):Part{ - override fun getSubmittedFileName(): String = name - override fun getName(): String = name - override fun write(fileName: String?) = throw IllegalArgumentException("Not supported") - override fun getHeader(name: String): String? = null - override fun getSize(): Long = content.toByteArray().size.toLong() - override fun getContentType(): String? =null - override fun getHeaders(name: String?): Collection = listOf() - override fun getHeaderNames(): Collection = listOf() - override fun getInputStream(): InputStream = content.byteInputStream() - override fun delete() = throw IllegalArgumentException("Not supported") +class MockPart(private val name: String, private val content: String) : Part { + override fun getSubmittedFileName(): String = name + override fun getName(): String = name + override fun write(fileName: String?) = throw IllegalArgumentException("Not supported") + override fun getHeader(name: String): String? = null + override fun getSize(): Long = content.toByteArray().size.toLong() + override fun getContentType(): String? = null + override fun getHeaders(name: String?): Collection = listOf() + override fun getHeaderNames(): Collection = listOf() + override fun getInputStream(): InputStream = content.byteInputStream() + override fun delete() = throw IllegalArgumentException("Not supported") } val customScalarId = GraphQLScalarType.newScalar() - .name("ID") - .description("Overrides built-in ID") - .coercing(object : Coercing { - override fun serialize(input: Any): String? = when (input) { - is String -> input - is UUID -> input.toString() - else -> null - } - - override fun parseValue(input: Any): UUID? = parseLiteral(input) - - override fun parseLiteral(input: Any): UUID? = when (input) { - is StringValue -> UUID.fromString(input.value) - else -> null - } - }) - .build() + .name("ID") + .description("Overrides built-in ID") + .coercing(object : Coercing { + override fun serialize(input: Any): String? = when (input) { + is String -> input + is UUID -> input.toString() + else -> null + } + + override fun parseValue(input: Any): UUID? = parseLiteral(input) + + override fun parseLiteral(input: Any): UUID? = when (input) { + is StringValue -> UUID.fromString(input.value) + else -> null + } + }) + .build() val customScalarUUID = GraphQLScalarType.newScalar() - .name("UUID") - .description("UUID") - .coercing(object : Coercing { + .name("UUID") + .description("UUID") + .coercing(object : Coercing { - override fun serialize(input: Any): String? = when (input) { - is String -> input - is UUID -> input.toString() - else -> null - } + override fun serialize(input: Any): String? = when (input) { + is String -> input + is UUID -> input.toString() + else -> null + } - override fun parseValue(input: Any): UUID? = parseLiteral(input) + override fun parseValue(input: Any): UUID? = parseLiteral(input) - override fun parseLiteral(input: Any): UUID? = when (input) { - is StringValue -> UUID.fromString(input.value) - else -> null - } - }) - .build() + override fun parseLiteral(input: Any): UUID? = when (input) { + is StringValue -> UUID.fromString(input.value) + else -> null + } + }) + .build() val customScalarMap = GraphQLScalarType.newScalar() - .name("customScalarMap") - .description("customScalarMap") - .coercing(object : Coercing, Map> { + .name("customScalarMap") + .description("customScalarMap") + .coercing(object : Coercing, Map> { - @Suppress("UNCHECKED_CAST") - override fun parseValue(input: Any?): Map = input as Map + @Suppress("UNCHECKED_CAST") + override fun parseValue(input: Any?): Map = input as Map - @Suppress("UNCHECKED_CAST") - override fun serialize(dataFetcherResult: Any?): Map = dataFetcherResult as Map + @Suppress("UNCHECKED_CAST") + override fun serialize(dataFetcherResult: Any?): Map = dataFetcherResult as Map - override fun parseLiteral(input: Any?): Map = (input as ObjectValue).objectFields.associateBy { it.name }.mapValues { (it.value.value as StringValue).value } - }) - .build() + override fun parseLiteral(input: Any?): Map = (input as ObjectValue).objectFields.associateBy { it.name }.mapValues { (it.value.value as StringValue).value } + }) + .build() //Definition from https://github.com/graphql-java-kickstart/graphql-java-servlet/blob/master/src/main/java/graphql/servlet/core/ApolloScalars.java val uploadScalar: GraphQLScalarType = GraphQLScalarType.newScalar() - .name("Upload") - .description("A file part in a multipart request") - .coercing(object : Coercing { - override fun serialize(dataFetcherResult: Any): Void? { - throw CoercingSerializeException("Upload is an input-only type") - } - - override fun parseValue(input: Any?): Part? { - return when (input) { - is Part -> { - input - } - null -> { - null - } - else -> { - throw CoercingParseValueException("Expected type ${Part::class.java.name} but was ${input.javaClass.name}") - } - } - } - - override fun parseLiteral(input: Any): Part? { - throw CoercingParseLiteralException( - "Must use variables to specify Upload values") - } - }).build() + .name("Upload") + .description("A file part in a multipart request") + .coercing(object : Coercing { + override fun serialize(dataFetcherResult: Any): Void? { + throw CoercingSerializeException("Upload is an input-only type") + } + + override fun parseValue(input: Any?): Part? { + return when (input) { + is Part -> { + input + } + null -> { + null + } + else -> { + throw CoercingParseValueException("Expected type ${Part::class.java.name} but was ${input.javaClass.name}") + } + } + } + + override fun parseLiteral(input: Any): Part? { + throw CoercingParseLiteralException( + "Must use variables to specify Upload values") + } + }).build()