From 8343dc9510103bb999f5d5683dc105b97a929467 Mon Sep 17 00:00:00 2001 From: Oryan Date: Wed, 7 Oct 2026 08:45:26 -0400 Subject: [PATCH 1/2] Honor null generic wrapper results A generic wrapper transformer that returned null was ignored, because transformWithGenericWrapper fell back to the original object with an elvis operator. Option-like wrappers could therefore never map their empty case to null: a scalar field serialized the wrapper's toString and an object or list field failed at runtime. The transformer result is now returned as is, including null, and the original object is only kept when no wrapper matches. Fixes #371 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../tools/resolver/MethodFieldResolver.kt | 9 ++-- .../tools/MethodFieldResolverTest.kt | 45 +++++++++++++++++++ 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt b/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt index b280c463..da17eb8b 100644 --- a/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt +++ b/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt @@ -266,13 +266,16 @@ internal class LightMethodFieldResolverDataFetcher( private fun Any.transformWithGenericWrapper( genericWrappers: List, environmentSupplier: Supplier -): Any { - return genericWrappers +): Any? { + val genericWrapper = genericWrappers .asSequence() .filter { it.type.isInstance(this) } .sortedWith(CompareGenericWrappers) .firstOrNull() - ?.transformer?.invoke(this, environmentSupplier.get()) ?: this + ?: return this + + // a transformer may map a value to null, e.g. the empty case of an Option-like wrapper + return genericWrapper.transformer(this, environmentSupplier.get()) } private class CompareGenericWrappers { diff --git a/src/test/kotlin/graphql/kickstart/tools/MethodFieldResolverTest.kt b/src/test/kotlin/graphql/kickstart/tools/MethodFieldResolverTest.kt index b64c5ac2..a59503df 100644 --- a/src/test/kotlin/graphql/kickstart/tools/MethodFieldResolverTest.kt +++ b/src/test/kotlin/graphql/kickstart/tools/MethodFieldResolverTest.kt @@ -239,6 +239,51 @@ class MethodFieldResolverTest { assertEquals(exceptionWhileDataFetching.exception.message, "Whoops") } + @Test + fun `should return null when generic wrapper transformer returns null`() { + val schema = SchemaParser.newParser() + .schemaString( + """ + type Query { + name: String + names: [String!] + nameWithArgument(input: String): String + } + """) + .resolvers(object : GraphQLQueryResolver { + fun name(): Maybe = Maybe.None + fun names(): Maybe> = Maybe.None + fun nameWithArgument(input: String?): Maybe = Maybe.None + }) + .options(SchemaParserOptions.newOptions() + .genericWrappers(SchemaParserOptions.GenericWrapper.withTransformer(Maybe::class, 0, { maybe -> (maybe as? Maybe.Some)?.value })) + .build()) + .build() + .makeExecutableSchema() + + val gql = GraphQL.newGraphQL(schema).build() + val result = gql.execute(ExecutionInput.newExecutionInput().query( + """ + query { + name + names + nameWithArgument(input: "test") + } + """)) + + assertEquals(result.errors, listOf()) + assertEquals(result.getData(), mapOf( + "name" to null, + "names" to null, + "nameWithArgument" to null + )) + } + + sealed class Maybe { + data class Some(val value: T) : Maybe() + object None : Maybe() + } + /** * Custom Scalar Class type that doesn't work with Jackson serialization/deserialization */ From 33355dec0e6e4977bd3f05e80f9ef2208f99cf3e Mon Sep 17 00:00:00 2001 From: Oryan Date: Wed, 7 Oct 2026 09:42:28 -0400 Subject: [PATCH 2/2] Clarify why the generic wrapper result isn't defaulted Co-Authored-By: Claude Opus 5.5 (1M context) --- .../graphql/kickstart/tools/resolver/MethodFieldResolver.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt b/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt index da17eb8b..469a24a4 100644 --- a/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt +++ b/src/main/kotlin/graphql/kickstart/tools/resolver/MethodFieldResolver.kt @@ -274,7 +274,7 @@ private fun Any.transformWithGenericWrapper( .firstOrNull() ?: return this - // a transformer may map a value to null, e.g. the empty case of an Option-like wrapper + // returned as is, even if null, so a transformer can map a value to null return genericWrapper.transformer(this, environmentSupplier.get()) }