diff --git a/pom.xml b/pom.xml index b2d8ed8c..2e3cb557 100644 --- a/pom.xml +++ b/pom.xml @@ -138,6 +138,12 @@ 1.0.2 test + + javax.servlet + javax.servlet-api + 3.1.0 + test + diff --git a/src/main/kotlin/com/coxautodev/graphql/tools/MethodFieldResolver.kt b/src/main/kotlin/com/coxautodev/graphql/tools/MethodFieldResolver.kt index b9027c42..e473d45b 100644 --- a/src/main/kotlin/com/coxautodev/graphql/tools/MethodFieldResolver.kt +++ b/src/main/kotlin/com/coxautodev/graphql/tools/MethodFieldResolver.kt @@ -127,16 +127,12 @@ internal class MethodFieldResolver(field: FieldDefinition, search: FieldResolver } private fun isScalarType(environment: DataFetchingEnvironment, type: Type<*>, genericParameterType: JavaType): Boolean = - when { - type is ListType -> - List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType)) + when (type) { + is ListType -> List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType)) && isScalarType(environment, type.type, this.genericType.unwrapGenericType(genericParameterType)) - type is TypeName -> - environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) } ?: false - type is NonNullType && type.type is TypeName -> - environment.graphQLSchema?.getType((type.type as TypeName).name)?.let { isScalar(unwrapNonNull(it)) } ?: false - else -> - false + is TypeName -> environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) } ?: false + is NonNullType -> isScalarType(environment, type.type, genericParameterType) + else -> false } override fun scanForMatches(): List { diff --git a/src/test/groovy/com/coxautodev/graphql/tools/EndToEndSpec.groovy b/src/test/groovy/com/coxautodev/graphql/tools/EndToEndSpec.groovy index e75cbcc7..8ec955cc 100644 --- a/src/test/groovy/com/coxautodev/graphql/tools/EndToEndSpec.groovy +++ b/src/test/groovy/com/coxautodev/graphql/tools/EndToEndSpec.groovy @@ -197,6 +197,19 @@ class EndToEndSpec extends Specification { 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) { + ''' + mutation ($fileParts: [Upload!]!) { echoFiles(fileParts: $fileParts)} + ''' + } + then: + (data["echoFiles"] as ArrayList).join(",") == "Hello,World" + } + def "generated schema should handle any java.util.Map (using HashMap) types as property maps"() { when: def data = Utils.assertNoGraphQlErrors(gql) { diff --git a/src/test/kotlin/com/coxautodev/graphql/tools/EndToEndSpecHelper.kt b/src/test/kotlin/com/coxautodev/graphql/tools/EndToEndSpecHelper.kt index 8e27e7e2..342f756d 100644 --- a/src/test/kotlin/com/coxautodev/graphql/tools/EndToEndSpecHelper.kt +++ b/src/test/kotlin/com/coxautodev/graphql/tools/EndToEndSpecHelper.kt @@ -4,21 +4,21 @@ import graphql.execution.DataFetcherResult import graphql.execution.batched.Batched import graphql.language.ObjectValue import graphql.language.StringValue -import graphql.schema.Coercing -import graphql.schema.DataFetchingEnvironment -import graphql.schema.GraphQLScalarType +import graphql.schema.* import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.ReceiveChannel import kotlinx.coroutines.coroutineScope import org.reactivestreams.Publisher +import java.io.InputStream import java.util.* import java.util.concurrent.CompletableFuture +import javax.servlet.http.Part fun createSchema() = SchemaParser.newParser() .schemaString(schemaDefinition) - .resolvers(Query(), Mutation(), Subscription(), ItemResolver(), UnusedRootResolver(), UnusedResolver()) - .scalars(customScalarUUID, customScalarMap, customScalarId) + .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) @@ -31,6 +31,7 @@ val schemaDefinition = """ ## Private comment! scalar UUID scalar customScalarMap +scalar Upload type Query { # Check if items list is empty @@ -108,6 +109,7 @@ input ComplexInputTypeTwo { type Mutation { addItem(newItem: NewItemInput!): Item! + echoFiles(fileParts: [Upload!]!): [String!]! } type Subscription { @@ -353,6 +355,10 @@ class ItemResolver : GraphQLResolver { } } +class EchoFilesResolver: GraphQLMutationResolver{ + fun echoFiles(fileParts: List): List = fileParts.map { String(it.inputStream.readBytes()) } +} + interface ItemInterface { val name: String val type: Type @@ -373,6 +379,18 @@ 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") +} val customScalarId = GraphQLScalarType.newScalar() .name("ID") @@ -427,3 +445,33 @@ val customScalarMap = GraphQLScalarType.newScalar() 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() +