Skip to content

Commit ffafbec

Browse files
authored
Merge pull request #320 from symbaloo/198-scalar-input-types
Fixes #198 - Add support for lists of Scalar field arguments that are already coerced
2 parents fda9c53 + 44fdca1 commit ffafbec

File tree

2 files changed

+145
-3
lines changed

2 files changed

+145
-3
lines changed

src/main/kotlin/com/coxautodev/graphql/tools/MethodFieldResolver.kt

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,18 @@ import com.esotericsoftware.reflectasm.MethodAccess
55
import com.fasterxml.jackson.core.type.TypeReference
66
import graphql.execution.batched.Batched
77
import graphql.language.FieldDefinition
8+
import graphql.language.ListType
89
import graphql.language.NonNullType
10+
import graphql.language.Type
11+
import graphql.language.TypeName
912
import graphql.schema.DataFetcher
1013
import graphql.schema.DataFetchingEnvironment
14+
import graphql.schema.GraphQLTypeUtil.isScalar
1115
import kotlinx.coroutines.GlobalScope
1216
import kotlinx.coroutines.future.future
1317
import java.lang.reflect.Method
14-
import java.util.*
18+
import java.util.Comparator
19+
import java.util.Optional
1520
import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
1621
import kotlin.reflect.full.valueParameters
1722
import kotlin.reflect.jvm.javaType
@@ -89,6 +94,12 @@ internal class MethodFieldResolver(field: FieldDefinition, search: FieldResolver
8994
}
9095
}
9196

97+
if (value != null
98+
&& genericParameterType.unwrap().isAssignableFrom(value.javaClass)
99+
&& isScalarType(environment, definition.type, genericParameterType)) {
100+
return@add value
101+
}
102+
92103
return@add mapper.convertValue(value, typeReference)
93104
}
94105
}
@@ -109,9 +120,24 @@ internal class MethodFieldResolver(field: FieldDefinition, search: FieldResolver
109120
}
110121
}
111122

123+
private fun isScalarType(environment: DataFetchingEnvironment, type: Type<*>, genericParameterType: JavaType): Boolean =
124+
when (type) {
125+
is ListType ->
126+
List::class.java.isAssignableFrom(this.genericType.getRawClass(genericParameterType))
127+
&& isScalarType(environment, type.type, this.genericType.unwrapGenericType(genericParameterType))
128+
is TypeName ->
129+
environment.graphQLSchema?.getType(type.name)?.let { isScalar(it) } ?: false
130+
else ->
131+
false
132+
}
133+
112134
override fun scanForMatches(): List<TypeClassMatcher.PotentialMatch> {
113135
val batched = isBatched(method, search)
114-
val unwrappedGenericType = genericType.unwrapGenericType(try { method.kotlinFunction?.returnType?.javaType ?: method.genericReturnType } catch (e: InternalError) { method.genericReturnType })
136+
val unwrappedGenericType = genericType.unwrapGenericType(try {
137+
method.kotlinFunction?.returnType?.javaType ?: method.genericReturnType
138+
} catch (e: InternalError) {
139+
method.genericReturnType
140+
})
115141
val returnValueMatch = TypeClassMatcher.PotentialMatch.returnValue(field.type, unwrappedGenericType, genericType, SchemaClassScanner.ReturnValueReference(method), batched)
116142

117143
return field.inputValueDefinitions.mapIndexed { i, inputDefinition ->
@@ -148,7 +174,11 @@ open class MethodFieldResolverDataFetcher(private val sourceResolver: SourceReso
148174
// Convert to reflactasm reflection
149175
private val methodAccess = MethodAccess.get(method.declaringClass)!!
150176
private val methodIndex = methodAccess.getIndex(method.name, *method.parameterTypes)
151-
private val isSuspendFunction = try {method.kotlinFunction?.isSuspend == true } catch (e: InternalError) { false }
177+
private val isSuspendFunction = try {
178+
method.kotlinFunction?.isSuspend == true
179+
} catch (e: InternalError) {
180+
false
181+
}
152182

153183
private class CompareGenericWrappers {
154184
companion object : Comparator<GenericWrapper> {
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.coxautodev.graphql.tools
2+
3+
import graphql.ExecutionInput
4+
import graphql.GraphQL
5+
import graphql.language.StringValue
6+
import graphql.schema.Coercing
7+
import graphql.schema.GraphQLScalarType
8+
import org.junit.Assert
9+
import org.junit.Test
10+
11+
class MethodFieldResolverTest {
12+
13+
@Test
14+
fun `should handle scalar types as method input argument`() {
15+
val schema = SchemaParser.newParser()
16+
.schemaString("""
17+
scalar CustomScalar
18+
type Query {
19+
test(input: CustomScalar): Int
20+
}
21+
""".trimIndent()
22+
)
23+
.scalars(customScalarType)
24+
.resolvers(object : GraphQLQueryResolver {
25+
fun test(scalar: CustomScalar) = scalar.value.length
26+
})
27+
.build()
28+
.makeExecutableSchema()
29+
30+
val gql = GraphQL.newGraphQL(schema).build()
31+
32+
val result = gql
33+
.execute(ExecutionInput.newExecutionInput()
34+
.query("""
35+
query Test(${"$"}input: CustomScalar) {
36+
test(input: ${"$"}input)
37+
}
38+
""".trimIndent())
39+
.variables(mapOf("input" to "FooBar"))
40+
.context(Object())
41+
.root(Object()))
42+
43+
Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
44+
}
45+
46+
@Test
47+
fun `should handle lists of scalar types`() {
48+
val schema = SchemaParser.newParser()
49+
.schemaString("""
50+
scalar CustomScalar
51+
type Query {
52+
test(input: [CustomScalar]): Int
53+
}
54+
""".trimIndent()
55+
)
56+
.scalars(customScalarType)
57+
.resolvers(object : GraphQLQueryResolver {
58+
fun test(scalars: List<CustomScalar>) = scalars.map { it.value.length }.sum()
59+
})
60+
.build()
61+
.makeExecutableSchema()
62+
63+
val gql = GraphQL.newGraphQL(schema).build()
64+
65+
val result = gql
66+
.execute(ExecutionInput.newExecutionInput()
67+
.query("""
68+
query Test(${"$"}input: [CustomScalar]) {
69+
test(input: ${"$"}input)
70+
}
71+
""".trimIndent())
72+
.variables(mapOf("input" to listOf("Foo", "Bar")))
73+
.context(Object())
74+
.root(Object()))
75+
76+
Assert.assertEquals(6, result.getData<Map<String, Any>>()["test"])
77+
}
78+
79+
/**
80+
* Custom Scalar Class type that doesn't work with Jackson serialization/deserialization
81+
*/
82+
class CustomScalar private constructor(private val internalValue: String) {
83+
val value get() = internalValue
84+
85+
companion object {
86+
fun of(input: Any?) = when (input) {
87+
is String -> CustomScalar(input)
88+
else -> null
89+
}
90+
}
91+
}
92+
93+
private val customScalarType: GraphQLScalarType = GraphQLScalarType.newScalar()
94+
.name("CustomScalar")
95+
.description("customScalar")
96+
.coercing(object : Coercing<CustomScalar, String> {
97+
98+
override fun parseValue(input: Any?) = CustomScalar.of(input)
99+
100+
override fun parseLiteral(input: Any?) = when (input) {
101+
is StringValue -> CustomScalar.of(input.value)
102+
else -> null
103+
}
104+
105+
override fun serialize(dataFetcherResult: Any?) = when (dataFetcherResult) {
106+
is CustomScalar -> dataFetcherResult.value
107+
else -> null
108+
}
109+
})
110+
.build()
111+
112+
}

0 commit comments

Comments
 (0)