Skip to content

Commit a17cb09

Browse files
committed
Fail instead of passing type and object ids for custom scalars without visible properties
1 parent 771b5f1 commit a17cb09

2 files changed

Lines changed: 26 additions & 39 deletions

File tree

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

Lines changed: 4 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package graphql.kickstart.tools.resolver
22

3-
import com.fasterxml.jackson.annotation.ObjectIdGenerator
43
import tools.jackson.core.JacksonException
54
import tools.jackson.core.JsonGenerator
65
import tools.jackson.core.JsonParser
@@ -56,7 +55,7 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
5655
}
5756
} ?: throw e
5857
try {
59-
readWithNativeTypeIds(embedded)
58+
read(embedded)
6059
} catch (retryError: JacksonException) {
6160
throw retryError.apply { addSuppressed(e) }
6261
}
@@ -84,9 +83,6 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
8483

8584
private fun read(value: Any?): Any? = reader.readValue(bufferFailingOnEmptyBeans(value))
8685

87-
// append copies the empty buffer's support for native ids, so its parser reads the type ids embedded values are written with
88-
private fun readWithNativeTypeIds(value: Any): Any? = reader.readValue(bufferFailingOnEmptyBeans(value).append(TokenBuffer(null, true)))
89-
9086
private fun bufferFailingOnEmptyBeans(value: Any?): TokenBuffer = buffer(FailOnEmptyBeans(value))
9187

9288
private fun buffer(value: Any?): TokenBuffer = writer.writeValueIntoBuffer(value).forceUseOfBigDecimal(useBigDecimalForFloats)
@@ -109,28 +105,12 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
109105
convertOnItsOwn(valueToConvert, targetType)
110106

111107
// called for an embedded value read as a Map or JsonNode, e.g. a scalar without properties, which converts to {}
112-
override fun handleUnexpectedToken(context: DeserializationContext, targetType: JavaType, t: JsonToken?, p: JsonParser, failureMsg: String?): Any? {
113-
val value = p.embeddedValue() ?: return NOT_HANDLED
114-
return bindAsObjectId(context, value, targetType) ?: convertOnItsOwn(value, targetType)
115-
}
108+
override fun handleUnexpectedToken(context: DeserializationContext, targetType: JavaType, t: JsonToken?, p: JsonParser, failureMsg: String?): Any? =
109+
p.embeddedValue()?.let { convertOnItsOwn(it, targetType) } ?: NOT_HANDLED
116110

117111
private fun JsonParser.embeddedValue(): Any? = if (hasToken(JsonToken.VALUE_EMBEDDED_OBJECT)) embeddedObject else null
118112

119113
private fun convertOnItsOwn(value: Any, targetType: JavaType): Any? = mapper.readValue(buffer(value), targetType)
120-
121-
// the deserializer of a class with an object id reads an embedded value as the id of an object read before
122-
private fun bindAsObjectId(context: DeserializationContext, value: Any, idType: JavaType): Any? {
123-
val idReader = context.findRootValueDeserializer(context.constructType(value.javaClass)).getObjectIdReader(context)
124-
if (idReader == null || idReader.idType != idType) {
125-
return null
126-
}
127-
val generatorKey = idReader.generator.key(null)
128-
val generator = context.getAttribute(generatorKey) as ObjectIdGenerator<*>?
129-
?: idReader.generator.newForSerialization(null).also { context.setAttribute(generatorKey, it) }
130-
val id = generator.generateId(value)
131-
context.findObjectId(id, idReader.generator, idReader.resolver).bindItem(context, value)
132-
return id
133-
}
134114
}
135115

136116
// FAIL_ON_EMPTY_BEANS whatever the mapper says: a scalar class with no visible properties would otherwise be read back as
@@ -172,15 +152,11 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
172152

173153
private class EmbeddedValue(private val value: Any) : JacksonSerializable {
174154
override fun serialize(gen: JsonGenerator, serializers: SerializationContext) {
175-
val typeSerializer = serializers.findTypeSerializer(serializers.constructType(value.javaClass))
176-
gen.writeTypeId(typeSerializer?.typeIdResolver?.idFromValue(serializers, value))
177155
gen.writeEmbeddedObject(value)
178-
// a TokenBuffer gives the type id to every token after it until another one is written
179-
gen.writeTypeId(null)
180156
}
181157

182158
override fun serializeWithType(gen: JsonGenerator, serializers: SerializationContext, typeSer: TypeSerializer) {
183-
serialize(gen, serializers)
159+
gen.writeEmbeddedObject(value)
184160
}
185161
}
186162
}

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

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,25 +221,36 @@ class CustomScalarInputTest {
221221
}
222222

223223
@Test
224-
fun `Kotlin custom scalars without visible properties are passed when written with a type or object id`() {
224+
fun `Kotlin custom scalars without visible properties written with a type or object id are passed in ObjectNode arguments`() {
225225
val data = assertNoGraphQlErrors(graphQL(inputSchema)) {
226226
"""
227227
query {
228-
optionalTypedToken(token: "optional")
229-
optionalWrappedToken(token: "optional")
230-
optionalIdToken(token: "optional")
231-
markedTokens(input: { typed: "input", wrapped: "input", id: "input", typedList: ["first", "second"] })
232228
markedTokenNode(input: { typed: "node", wrapped: "node", id: "node" })
233229
}
234230
"""
235231
}
236232

237-
assertEquals(data, mapOf(
238-
"optionalTypedToken" to "optional",
239-
"optionalWrappedToken" to "optional",
240-
"optionalIdToken" to "optional",
241-
"markedTokens" to "input input input [first, second]",
242-
"markedTokenNode" to "node node node"))
233+
assertEquals(data, mapOf("markedTokenNode" to "node node node"))
234+
}
235+
236+
@Test
237+
fun `Kotlin custom scalars without visible properties written with a type or object id fail rather than lose their value`() {
238+
val result = graphQL(inputSchema).execute(
239+
"""
240+
query {
241+
optionalTypedToken(token: "optional")
242+
optionalWrappedToken(token: "optional")
243+
optionalIdToken(token: "optional")
244+
typed: markedTokens(input: { typed: "input" })
245+
wrapped: markedTokens(input: { wrapped: "input" })
246+
id: markedTokens(input: { id: "input" })
247+
typedList: markedTokens(input: { typedList: ["first", "second"] })
248+
}
249+
""")
250+
251+
assertEquals(
252+
result.errors.map { it.path?.single() }.toSet(),
253+
setOf("optionalTypedToken", "optionalWrappedToken", "optionalIdToken", "typed", "wrapped", "id", "typedList"))
243254
}
244255

245256
@Test

0 commit comments

Comments
 (0)