Skip to content

Commit c024bc3

Browse files
authored
Merge pull request #891 from graphql-java-kickstart/feature/859-scalar-args-at-build
Check scalar arguments when building the schema
2 parents 3386722 + d9d7c42 commit c024bc3

7 files changed

Lines changed: 43 additions & 27 deletions

File tree

‎src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@ internal class SchemaClassScanner(
5858
private val objectDefinitionsByName = objectDefinitions.associateBy { it.name }
5959
private val interfaceDefinitionsByName = definitionsByName.values.filterIsInstance<InterfaceTypeDefinition>().associateBy { it.name }
6060

61-
private val fieldResolverScanner = FieldResolverScanner(options)
61+
private val scalarNames = ScalarInfo.GRAPHQL_SPECIFICATION_SCALARS_DEFINITIONS.keys + scalarDefinitions.map { it.name }
62+
private val fieldResolverScanner = FieldResolverScanner(options, scalarNames)
6263
private val typeClassMatcher = TypeClassMatcher(definitionsByName)
6364
private val dictionary = mutableMapOf<TypeDefinition<*>, DictionaryEntry>()
6465
private val unvalidatedTypes = mutableSetOf<TypeDefinition<*>>()

‎src/main/kotlin/graphql/kickstart/tools/resolver/FieldResolverScanner.kt‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import kotlin.reflect.jvm.kotlinFunction
2121
/**
2222
* @author Andrew Potter
2323
*/
24-
internal class FieldResolverScanner(val options: SchemaParserOptions) {
24+
internal class FieldResolverScanner(val options: SchemaParserOptions, private val scalarNames: Set<String>) {
2525

2626
private val log = LoggerFactory.getLogger(javaClass)
2727

@@ -45,7 +45,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
4545
private fun findFieldResolver(field: FieldDefinition, search: Search, scanProperties: Boolean): FieldResolver? {
4646
val method = findResolverMethod(field, search)
4747
if (method != null) {
48-
return MethodFieldResolver(field, search, options, method.apply(trySetAccessible(field, search.type)))
48+
return MethodFieldResolver(field, search, options, method.apply(trySetAccessible(field, search.type)), scalarNames)
4949
}
5050

5151
if (scanProperties) {

‎src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt‎

Lines changed: 10 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ import graphql.language.*
1515
import graphql.schema.DataFetcher
1616
import graphql.schema.DataFetchingEnvironment
1717
import graphql.schema.GraphQLFieldDefinition
18-
import graphql.schema.GraphQLTypeUtil.isScalar
1918
import graphql.schema.LightDataFetcher
2019
import kotlinx.coroutines.CoroutineStart
2120
import kotlinx.coroutines.ensureActive
@@ -40,7 +39,8 @@ internal class MethodFieldResolver(
4039
field: FieldDefinition,
4140
search: FieldResolverScanner.Search,
4241
options: SchemaParserOptions,
43-
val method: Method
42+
val method: Method,
43+
private val scalarNames: Set<String>
4444
) : FieldResolver(field, search, options) {
4545

4646
private val log = LoggerFactory.getLogger(javaClass)
@@ -78,6 +78,8 @@ internal class MethodFieldResolver(
7878
val isNonNull = definition.type is NonNullType
7979
val isOptional = this.typeResolver.getRawClass(parameterType) == Optional::class.java
8080
val argumentConverter = ArgumentConverter(mapper, parameterType)
81+
val parameterClass = parameterType.unwrap()
82+
val isConcreteScalar = isConcreteScalarType(definition.type, parameterType)
8183

8284
args.add { environment ->
8385
val argumentPresent = environment.arguments.containsKey(definition.name)
@@ -94,7 +96,7 @@ internal class MethodFieldResolver(
9496
return@add Optional.empty<Any>()
9597
}
9698

97-
if (value == null || shouldValueBeConverted(value, definition, parameterType, environment)) {
99+
if (value == null || !isConcreteScalar || !parameterClass.isInstance(value)) {
98100
return@add argumentConverter.convert(value)
99101
}
100102

@@ -141,24 +143,18 @@ internal class MethodFieldResolver(
141143
}
142144
}
143145

144-
private fun shouldValueBeConverted(value: Any, definition: InputValueDefinition, parameterType: JavaType, environment: DataFetchingEnvironment): Boolean {
145-
return !parameterType.unwrap().isAssignableFrom(value.javaClass) || !isConcreteScalarType(environment, definition.type, parameterType)
146-
}
147-
148146
/**
149147
* A concrete scalar type is a scalar type where values always coerce to the same Java type. The ID scalar type is not concrete
150148
* because values can be coerced to multiple different Java types (eg. String, Long, UUID). All values of a non-concrete scalar
151149
* type must be converted to the target method parameter type.
152150
*/
153-
private fun isConcreteScalarType(environment: DataFetchingEnvironment, type: Type<*>, genericParameterType: JavaType): Boolean {
151+
private fun isConcreteScalarType(type: Type<*>, genericParameterType: JavaType): Boolean {
154152
return when (type) {
155-
is ListType -> List::class.java.isAssignableFrom(this.typeResolver.getRawClass(genericParameterType))
156-
&& isConcreteScalarType(environment, type.type, this.typeResolver.resolve(genericParameterType))
157-
158-
is TypeName -> environment.graphQLSchema.getType(type.name!!)?.let { isScalar(it) && type.name != "ID" }
159-
?: false
153+
is ListType -> genericParameterType.typeArgument(List::class.java)
154+
?.let { isConcreteScalarType(type.type, it) } ?: false
160155

161-
is NonNullType -> isConcreteScalarType(environment, type.type, genericParameterType)
156+
is TypeName -> type.name in scalarNames && type.name != "ID"
157+
is NonNullType -> isConcreteScalarType(type.type, genericParameterType)
162158
else -> false
163159
}
164160
}

‎src/test/kotlin/graphql/kickstart/tools/FieldResolverScannerTest.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import java.util.*
1818
class FieldResolverScannerTest {
1919

2020
private val options = defaultOptions()
21-
private val scanner = FieldResolverScanner(options)
21+
private val scanner = FieldResolverScanner(options, GRAPHQL_SCALARS.keys)
2222

2323
@Test
2424
fun `scanner finds fields on multiple root types`() {

‎src/test/kotlin/graphql/kickstart/tools/MethodFieldResolverDataFetcherTest.kt‎

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package graphql.kickstart.tools
22

33
import graphql.ExecutionResult
44
import graphql.GraphQLContext
5-
import graphql.Scalars
65
import graphql.execution.*
76
import graphql.execution.instrumentation.SimplePerformantInstrumentation
87
import graphql.kickstart.tools.resolver.FieldResolverError
@@ -14,8 +13,6 @@ import graphql.language.TypeName
1413
import graphql.schema.DataFetcher
1514
import graphql.schema.DataFetchingEnvironment
1615
import graphql.schema.DataFetchingEnvironmentImpl
17-
import graphql.schema.GraphQLObjectType
18-
import graphql.schema.GraphQLSchema
1916
import kotlinx.coroutines.CoroutineScope
2017
import kotlinx.coroutines.DelicateCoroutinesApi
2118
import kotlinx.coroutines.Dispatchers
@@ -421,7 +418,7 @@ class MethodFieldResolverDataFetcherTest {
421418
} else {
422419
NormalResolverInfo(resolver, options)
423420
}
424-
return FieldResolverScanner(options).findFieldResolver(field, resolverInfo).createDataFetcher()
421+
return FieldResolverScanner(options, GRAPHQL_SCALARS.keys).findFieldResolver(field, resolverInfo).createDataFetcher()
425422
}
426423

427424
private fun createEnvironment(
@@ -446,11 +443,7 @@ class MethodFieldResolverDataFetcherTest {
446443
}
447444
}
448445
val executionId = ExecutionId.from("executionId123")
449-
val schema = GraphQLSchema.newSchema()
450-
.query(GraphQLObjectType.newObject().name("Query").field { it.name("active").type(Scalars.GraphQLBoolean) })
451-
.build()
452446
return ExecutionContextBuilder.newExecutionContextBuilder()
453-
.graphQLSchema(schema)
454447
.graphQLContext(context)
455448
.instrumentation(SimplePerformantInstrumentation.INSTANCE)
456449
.executionId(executionId)

‎src/test/kotlin/graphql/kickstart/tools/MethodFieldResolverTest.kt‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import graphql.ExceptionWhileDataFetching
44
import graphql.ExecutionInput
55
import graphql.GraphQL
66
import graphql.GraphQLContext
7+
import graphql.GraphQLError
78
import graphql.execution.CoercedVariables
89
import graphql.language.StringValue
910
import graphql.language.Value
@@ -167,6 +168,31 @@ class MethodFieldResolverTest {
167168
assertEquals(result.getData(), mapOf("test" to 6))
168169
}
169170

171+
@Test
172+
fun `should handle nested lists of scalar types`() {
173+
val schema = SchemaParser.newParser()
174+
.schemaString(
175+
"""
176+
scalar CustomScalar
177+
type Query {
178+
count(tags: [[String]]): Int
179+
test(input: [[CustomScalar]]): Int
180+
}
181+
""")
182+
.scalars(customScalarType)
183+
.resolvers(object : GraphQLQueryResolver {
184+
fun count(tags: List<Set<String>>) = tags.sumOf { it.size }
185+
fun test(scalars: List<Set<CustomScalar>>) = scalars.sumOf { set -> set.sumOf { it.value.length } }
186+
})
187+
.build()
188+
.makeExecutableSchema()
189+
190+
val result = GraphQL.newGraphQL(schema).build().execute("""{ count(tags: [["a", "a", "b"]]) test(input: [["Foo"], ["Bar"]]) }""")
191+
192+
assertEquals(result.errors, emptyList<GraphQLError>())
193+
assertEquals(result.getData(), mapOf("count" to 2, "test" to 6))
194+
}
195+
170196
@Test
171197
fun `should handle proxies`() {
172198
val invocationHandler = object : InvocationHandler {

‎src/test/kotlin/graphql/kickstart/tools/TypeClassMatcherTest.kt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ class TypeClassMatcherTest {
4747
) { x -> x }
4848
).build()
4949

50-
private val scanner: FieldResolverScanner = FieldResolverScanner(options)
50+
private val scanner: FieldResolverScanner = FieldResolverScanner(options, GRAPHQL_SCALARS.keys)
5151
private val resolver = RootResolverInfo(listOf(QueryMethods()), options)
5252

5353
private fun createPotentialMatch(methodName: String, graphQLType: Type<*>): TypeClassMatcher.PotentialMatch {

0 commit comments

Comments
 (0)