Skip to content

Commit 8343dc9

Browse files
oryan-blockclaude
andcommitted
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) <noreply@anthropic.com>
1 parent f1f6a90 commit 8343dc9

2 files changed

Lines changed: 51 additions & 3 deletions

File tree

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

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -266,13 +266,16 @@ internal class LightMethodFieldResolverDataFetcher(
266266
private fun Any.transformWithGenericWrapper(
267267
genericWrappers: List<GenericWrapper>,
268268
environmentSupplier: Supplier<DataFetchingEnvironment>
269-
): Any {
270-
return genericWrappers
269+
): Any? {
270+
val genericWrapper = genericWrappers
271271
.asSequence()
272272
.filter { it.type.isInstance(this) }
273273
.sortedWith(CompareGenericWrappers)
274274
.firstOrNull()
275-
?.transformer?.invoke(this, environmentSupplier.get()) ?: this
275+
?: return this
276+
277+
// a transformer may map a value to null, e.g. the empty case of an Option-like wrapper
278+
return genericWrapper.transformer(this, environmentSupplier.get())
276279
}
277280

278281
private class CompareGenericWrappers {

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

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,51 @@ class MethodFieldResolverTest {
239239
assertEquals(exceptionWhileDataFetching.exception.message, "Whoops")
240240
}
241241

242+
@Test
243+
fun `should return null when generic wrapper transformer returns null`() {
244+
val schema = SchemaParser.newParser()
245+
.schemaString(
246+
"""
247+
type Query {
248+
name: String
249+
names: [String!]
250+
nameWithArgument(input: String): String
251+
}
252+
""")
253+
.resolvers(object : GraphQLQueryResolver {
254+
fun name(): Maybe<String> = Maybe.None
255+
fun names(): Maybe<List<String>> = Maybe.None
256+
fun nameWithArgument(input: String?): Maybe<String> = Maybe.None
257+
})
258+
.options(SchemaParserOptions.newOptions()
259+
.genericWrappers(SchemaParserOptions.GenericWrapper.withTransformer(Maybe::class, 0, { maybe -> (maybe as? Maybe.Some)?.value }))
260+
.build())
261+
.build()
262+
.makeExecutableSchema()
263+
264+
val gql = GraphQL.newGraphQL(schema).build()
265+
val result = gql.execute(ExecutionInput.newExecutionInput().query(
266+
"""
267+
query {
268+
name
269+
names
270+
nameWithArgument(input: "test")
271+
}
272+
"""))
273+
274+
assertEquals(result.errors, listOf())
275+
assertEquals(result.getData(), mapOf(
276+
"name" to null,
277+
"names" to null,
278+
"nameWithArgument" to null
279+
))
280+
}
281+
282+
sealed class Maybe<out T> {
283+
data class Some<T>(val value: T) : Maybe<T>()
284+
object None : Maybe<Nothing>()
285+
}
286+
242287
/**
243288
* Custom Scalar Class type that doesn't work with Jackson serialization/deserialization
244289
*/

0 commit comments

Comments
 (0)