11package graphql.kickstart.tools
22
3+ import com.fasterxml.jackson.annotation.JsonIdentityInfo
34import com.fasterxml.jackson.annotation.JsonInclude
5+ import com.fasterxml.jackson.annotation.JsonTypeInfo
6+ import com.fasterxml.jackson.annotation.ObjectIdGenerators
47import graphql.ExceptionWhileDataFetching
58import graphql.GraphQL
69import 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