Skip to content

Commit f6f4fa4

Browse files
authored
Merge pull request #934 from graphql-java-kickstart/bugfix/932-typed-empty-scalars
Fail on custom scalars without visible properties written with a type or object id
2 parents 2c19290 + a17cb09 commit f6f4fa4

2 files changed

Lines changed: 89 additions & 4 deletions

File tree

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

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,8 @@ internal class ArgumentConverter(private val mapper: ObjectMapper, type: Type) {
139139

140140
private class EmptyBeanCheck(gen: JsonGenerator, private val serializers: SerializationContext) : JsonGeneratorDelegate(gen, false) {
141141
override fun writeEndObject(): JsonGenerator {
142-
val context = streamWriteContext()
143-
val bean = context.currentValue()
144-
if (context.entryCount == 0 && bean != null && hasNoProperties(bean.javaClass)) {
142+
val bean = streamWriteContext().currentValue()
143+
if (bean != null && hasNoProperties(bean.javaClass)) {
145144
serializers.reportBadDefinition<Unit>(bean.javaClass, "No properties discovered to write ${bean.javaClass.name}")
146145
}
147146
return super.writeEndObject()

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

Lines changed: 87 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package graphql.kickstart.tools
22

3+
import com.fasterxml.jackson.annotation.JsonIdentityInfo
34
import com.fasterxml.jackson.annotation.JsonInclude
5+
import com.fasterxml.jackson.annotation.JsonTypeInfo
6+
import com.fasterxml.jackson.annotation.ObjectIdGenerators
47
import graphql.ExceptionWhileDataFetching
58
import graphql.GraphQL
69
import graphql.GraphQLContext
@@ -217,6 +220,39 @@ class CustomScalarInputTest {
217220
assertEquals(data, mapOf("otherTokens" to "OtherToken(value=other) {} {}"))
218221
}
219222

223+
@Test
224+
fun `Kotlin custom scalars without visible properties written with a type or object id are passed in ObjectNode arguments`() {
225+
val data = assertNoGraphQlErrors(graphQL(inputSchema)) {
226+
"""
227+
query {
228+
markedTokenNode(input: { typed: "node", wrapped: "node", id: "node" })
229+
}
230+
"""
231+
}
232+
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"))
254+
}
255+
220256
@Test
221257
fun `errors converting inputs with custom scalars name the actual problem`() {
222258
val result = graphQL(inputSchema).execute(
@@ -234,7 +270,8 @@ class CustomScalarInputTest {
234270
private fun graphQL(schema: String, options: SchemaParserOptions = SchemaParserOptions.defaultOptions()): GraphQL {
235271
val graphQLSchema = SchemaParser.newParser()
236272
.schemaString(schema)
237-
.scalars(localDateScalar, opaqueScalar, secretScalar, boxScalar, moneyScalar, tokenScalar, customScalarUUID, uploadScalar)
273+
.scalars(localDateScalar, opaqueScalar, secretScalar, boxScalar, moneyScalar, tokenScalar, typedTokenScalar, wrappedTokenScalar,
274+
idTokenScalar, customScalarUUID, uploadScalar)
238275
.resolvers(resolver)
239276
.options(options)
240277
.build()
@@ -251,6 +288,9 @@ class CustomScalarInputTest {
251288
scalar Money
252289
scalar Upload
253290
scalar Token
291+
scalar TypedToken
292+
scalar WrappedToken
293+
scalar IdToken
254294
255295
type Query {
256296
event(input: EventInput!): String!
@@ -267,6 +307,11 @@ class CustomScalarInputTest {
267307
tokenInput(input: TokenInput!): String!
268308
tokenNode(input: NodeInput!): String!
269309
otherTokens(input: OtherTokensInput!): String!
310+
optionalTypedToken(token: TypedToken): String!
311+
optionalWrappedToken(token: WrappedToken): String!
312+
optionalIdToken(token: IdToken): String!
313+
markedTokens(input: MarkedTokensInput!): String!
314+
markedTokenNode(input: NodeInput!): String!
270315
}
271316
272317
input EventInput {
@@ -291,6 +336,9 @@ class CustomScalarInputTest {
291336
secret: Secret
292337
price: Money
293338
token: Token
339+
typed: TypedToken
340+
wrapped: WrappedToken
341+
id: IdToken
294342
}
295343
296344
input TokenInput {
@@ -303,6 +351,13 @@ class CustomScalarInputTest {
303351
node: Token
304352
}
305353
354+
input MarkedTokensInput {
355+
typed: TypedToken
356+
wrapped: WrappedToken
357+
id: IdToken
358+
typedList: [TypedToken!]
359+
}
360+
306361
input PaymentInput {
307362
price: Money
308363
reference: Opaque
@@ -341,6 +396,12 @@ class CustomScalarInputTest {
341396
fun tokenInput(input: TokenInput): String = input.token.toString()
342397
fun otherTokens(input: OtherTokensInput): String = "${input.bean} ${input.map} ${input.node}"
343398
fun tokenNode(input: ObjectNode): String = input.get("token").let { if (it is POJONode) it.pojo.toString() else "$it" }
399+
fun optionalTypedToken(token: Optional<TypedToken>): String = token.map { it.toString() }.orElse("empty")
400+
fun optionalWrappedToken(token: Optional<WrappedToken>): String = token.map { it.toString() }.orElse("empty")
401+
fun optionalIdToken(token: Optional<IdToken>): String = token.map { it.toString() }.orElse("empty")
402+
fun markedTokens(input: MarkedTokensInput): String = "${input.typed} ${input.wrapped} ${input.id} ${input.typedList}"
403+
fun markedTokenNode(input: ObjectNode): String =
404+
listOf("typed", "wrapped", "id").joinToString(" ") { input.get(it).let { node -> if (node is POJONode) node.pojo.toString() else "$node" } }
344405

345406
fun map(input: Map<String, Any?>) = receive("map", input)
346407
fun mapList(inputs: List<Map<String, Any?>>) = receive("mapList", inputs)
@@ -395,6 +456,28 @@ class CustomScalarInputTest {
395456

396457
data class OtherToken(val value: String = "other")
397458

459+
@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY)
460+
class TypedToken(private val value: String = "default") {
461+
override fun toString() = value
462+
}
463+
464+
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.WRAPPER_OBJECT)
465+
class WrappedToken(private val value: String = "default") {
466+
override fun toString() = value
467+
}
468+
469+
@JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator::class)
470+
class IdToken(private val value: String = "default") {
471+
override fun toString() = value
472+
}
473+
474+
data class MarkedTokensInput(
475+
val typed: TypedToken? = null,
476+
val wrapped: WrappedToken? = null,
477+
val id: IdToken? = null,
478+
val typedList: List<TypedToken>? = null
479+
)
480+
398481
data class OtherTokensInput(val bean: OtherToken? = null, val map: Map<String, String>? = null, val node: ObjectNode? = null)
399482

400483
class BoxContentDeserializer : StdDeserializer<String>(String::class.java) {
@@ -428,6 +511,9 @@ class CustomScalarInputTest {
428511
private val boxScalar = scalar("Box") { Box(if (it.startsWith("secret:")) Secret(it.removePrefix("secret:")) else it) }
429512
private val moneyScalar = scalar("Money") { Money(it.toInt(), "CAD") }
430513
private val tokenScalar = scalar("Token") { Token(it) }
514+
private val typedTokenScalar = scalar("TypedToken") { TypedToken(it) }
515+
private val wrappedTokenScalar = scalar("WrappedToken") { WrappedToken(it) }
516+
private val idTokenScalar = scalar("IdToken") { IdToken(it) }
431517

432518
private fun <T : Any> scalar(name: String, parse: (String) -> T): GraphQLScalarType = GraphQLScalarType.newScalar()
433519
.name(name)

0 commit comments

Comments
 (0)