Skip to content

Commit cb279a9

Browse files
committed
Support Kotlin value class parameters to data fetchers
Support Kotlin value class parameters as top-level arguments to data fetcher methods. Fixes #355
1 parent fe06680 commit cb279a9

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

graphql-dgs/src/main/kotlin/com/netflix/graphql/dgs/internal/method/AbstractInputArgumentResolver.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ import org.springframework.core.convert.TypeDescriptor
2626
import org.springframework.core.convert.support.DefaultConversionService
2727
import java.util.concurrent.ConcurrentHashMap
2828
import java.util.concurrent.ConcurrentMap
29+
import kotlin.reflect.KParameter
30+
import kotlin.reflect.jvm.jvmErasure
31+
import kotlin.reflect.jvm.kotlinFunction
2932

3033
abstract class AbstractInputArgumentResolver(inputObjectMapper: InputObjectMapper) : ArgumentResolver {
3134

@@ -44,6 +47,19 @@ abstract class AbstractInputArgumentResolver(inputObjectMapper: InputObjectMappe
4447
val argumentName = getArgumentName(parameter)
4548
val value = dfe.getArgument<Any?>(argumentName)
4649

50+
val kfunc = parameter.method?.kotlinFunction
51+
if (kfunc != null) {
52+
val parameterIdx = if (kfunc.parameters.first().kind == KParameter.Kind.INSTANCE) {
53+
parameter.parameterIndex + 1
54+
} else {
55+
parameter.parameterIndex
56+
}
57+
val param = kfunc.parameters[parameterIdx]
58+
if (param.type.arguments.isEmpty() && param.type.jvmErasure.isInstance(value)) {
59+
return value
60+
}
61+
}
62+
4763
val typeDescriptor = TypeDescriptor(parameter)
4864
val convertedValue = convertValue(value, typeDescriptor)
4965

graphql-dgs/src/test/kotlin/com/netflix/graphql/dgs/internal/InputArgumentTest.kt

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.netflix.graphql.dgs.DgsComponent
2020
import com.netflix.graphql.dgs.DgsData
2121
import com.netflix.graphql.dgs.DgsQuery
2222
import com.netflix.graphql.dgs.DgsRuntimeWiring
23+
import com.netflix.graphql.dgs.DgsScalar
2324
import com.netflix.graphql.dgs.InputArgument
2425
import com.netflix.graphql.dgs.LocalDateTimeScalar
2526
import com.netflix.graphql.dgs.exceptions.DataFetcherInputArgumentSchemaMismatchException
@@ -49,8 +50,12 @@ import com.netflix.graphql.dgs.scalars.UploadScalar
4950
import graphql.ExceptionWhileDataFetching
5051
import graphql.ExecutionInput
5152
import graphql.GraphQL
53+
import graphql.GraphQLContext
54+
import graphql.execution.CoercedVariables
55+
import graphql.language.StringValue
5256
import graphql.scalars.ExtendedScalars
5357
import graphql.scalars.country.code.CountryCode
58+
import graphql.schema.Coercing
5459
import graphql.schema.DataFetchingEnvironment
5560
import graphql.schema.idl.RuntimeWiring
5661
import org.assertj.core.api.Assertions.LIST
@@ -67,6 +72,7 @@ import org.springframework.mock.web.MockMultipartFile
6772
import org.springframework.web.multipart.MultipartFile
6873
import java.time.LocalDateTime
6974
import java.time.format.DateTimeFormatter
75+
import java.util.Locale
7076
import java.util.Optional
7177
import kotlin.reflect.KClass
7278

@@ -2297,6 +2303,56 @@ internal class InputArgumentTest {
22972303
}
22982304
}
22992305

2306+
@JvmInline value class ValueClass(val value: String) {
2307+
override fun toString(): String {
2308+
return value
2309+
}
2310+
}
2311+
2312+
@Test
2313+
fun `@InputArgument mapping works for kotlin value class parameters`() {
2314+
@DgsComponent
2315+
class Component {
2316+
@DgsQuery(field = "foo")
2317+
fun fooFetcher(@InputArgument input: ValueClass): String {
2318+
return input.toString()
2319+
}
2320+
}
2321+
2322+
@DgsScalar(name = "Value")
2323+
class ValueScalar : Coercing<ValueClass, String> {
2324+
override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): ValueClass {
2325+
return ValueClass(input.toString())
2326+
}
2327+
2328+
override fun parseLiteral(
2329+
input: graphql.language.Value<*>,
2330+
variables: CoercedVariables,
2331+
graphQLContext: GraphQLContext,
2332+
locale: Locale
2333+
): ValueClass {
2334+
return ValueClass((input as StringValue).value)
2335+
}
2336+
}
2337+
2338+
contextRunner.withBeans(Component::class, ValueScalar::class).run { applicationContext ->
2339+
val schemaProvider = schemaProvider(applicationContext = applicationContext)
2340+
val schema = schemaProvider.schema(
2341+
"""
2342+
scalar Value
2343+
type Query {
2344+
foo(input: Value!): String!
2345+
}
2346+
""".trimIndent()
2347+
).graphQLSchema
2348+
2349+
val graphql = GraphQL.newGraphQL(schema).build()
2350+
val result = graphql.execute("{ foo(input: \"input-value\") }")
2351+
assertThat(result.errors).isEmpty()
2352+
assertThat(result.getData<Map<String, String>>()).containsEntry("foo", "input-value")
2353+
}
2354+
}
2355+
23002356
private fun ApplicationContextRunner.withBeans(vararg beanClasses: KClass<*>): ApplicationContextRunner {
23012357
var context = this
23022358
for (klazz in beanClasses) {

0 commit comments

Comments
 (0)