@@ -3,6 +3,7 @@ package com.apurebase.kgraphql.schema
3
3
import com.apurebase.kgraphql.*
4
4
import com.apurebase.kgraphql.schema.dsl.SchemaBuilder
5
5
import com.apurebase.kgraphql.schema.dsl.types.TypeDSL
6
+ import com.apurebase.kgraphql.schema.execution.DefaultGenericTypeResolver
6
7
import com.apurebase.kgraphql.schema.introspection.TypeKind
7
8
import com.apurebase.kgraphql.schema.scalar.StringScalarCoercion
8
9
import com.apurebase.kgraphql.schema.structure.Field
@@ -14,6 +15,7 @@ import org.hamcrest.MatcherAssert.assertThat
14
15
import org.junit.jupiter.api.Test
15
16
import java.util.*
16
17
import kotlin.reflect.KType
18
+ import kotlin.reflect.full.isSupertypeOf
17
19
import kotlin.reflect.typeOf
18
20
19
21
/* *
@@ -252,19 +254,6 @@ class SchemaBuilderTest {
252
254
assertThat(result.extract<String >(" data/actor/name" ), equalTo(" Boguś Linda FULL_LENGTH" ))
253
255
}
254
256
255
- private data class LambdaWrapper (val lambda : () -> Int )
256
-
257
- @Test
258
- fun `function properties cannot be handled` (){
259
- expect<SchemaException >(" Generic types are not supported by GraphQL, found () -> kotlin.Int" ){
260
- KGraphQL .schema {
261
- query(" lambda" ){
262
- resolver { -> LambdaWrapper { 1 } }
263
- }
264
- }
265
- }
266
- }
267
-
268
257
@Test
269
258
fun `java arrays should be supported` () {
270
259
KGraphQL .schema {
@@ -306,15 +295,112 @@ class SchemaBuilderTest {
306
295
assertThat(schema.inputTypeByKClass(InputTwo ::class ), notNullValue())
307
296
}
308
297
298
+ private sealed class Maybe <out T > {
299
+ abstract fun get (): T
300
+ object Undefined : Maybe<Nothing>() {
301
+ override fun get () = throw IllegalArgumentException (" Requested value is not defined!" )
302
+ }
303
+ class Defined <U >(val value : U ) : Maybe<U>() {
304
+ override fun get () = value
305
+ }
306
+ }
307
+
309
308
@Test
310
- fun `generic types are not supported` (){
311
- expect<SchemaException >(" Generic types are not supported by GraphQL, found kotlin.Pair<kotlin.Int, kotlin.String>" ){
312
- defaultSchema {
313
- query(" data" ){
314
- resolver { int: Int , string: String -> int to string }
309
+ fun `client code can declare custom generic type resolver` (){
310
+ val typeResolver = object : DefaultGenericTypeResolver () {
311
+ override fun unbox (obj : Any ) = if (obj is Maybe <* >) obj.get() else super .unbox(obj)
312
+ override fun resolveMonad (type : KType ): KType {
313
+ if (typeOf<Maybe <* >>().isSupertypeOf(type)) {
314
+ return type.arguments.first().type
315
+ ? : throw SchemaException (" Could not get the type of the first argument for the type $type " )
316
+ }
317
+ return super .resolveMonad(type)
318
+ }
319
+ }
320
+
321
+ data class SomeWithGenericType (val value : Maybe <Int >, val anotherValue : String = " foo" )
322
+
323
+ val schema = defaultSchema {
324
+ configure { genericTypeResolver = typeResolver }
325
+
326
+ type<SomeWithGenericType >()
327
+ query(" definedValueProp" ) { resolver<SomeWithGenericType > { SomeWithGenericType (Maybe .Defined (33 )) } }
328
+ query(" undefinedValueProp" ) { resolver<SomeWithGenericType > { SomeWithGenericType (Maybe .Undefined ) } }
329
+
330
+ query(" definedValue" ) { resolver<Maybe <String >> { Maybe .Defined (" good!" ) } }
331
+ query(" undefinedValue" ) { resolver<Maybe <String >> { Maybe .Undefined } }
332
+ }
333
+
334
+ deserialize(schema.executeBlocking(" {__schema{queryType{fields{ type { ofType { kind name fields { type {ofType {kind name}}}}}}}}}" )).let {
335
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/kind" ), equalTo(" OBJECT" ))
336
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/name" ), equalTo(" SomeWithGenericType" ))
337
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/fields[0]/type/ofType/kind" ), equalTo(" SCALAR" ))
338
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/fields[0]/type/ofType/name" ), equalTo(" String" ))
339
+
340
+ assertThat(it.extract(" data/__schema/queryType/fields[1]/type/ofType/kind" ), equalTo(" OBJECT" ))
341
+ assertThat(it.extract(" data/__schema/queryType/fields[1]/type/ofType/name" ), equalTo(" SomeWithGenericType" ))
342
+ assertThat(it.extract(" data/__schema/queryType/fields[1]/type/ofType/fields[0]/type/ofType/kind" ), equalTo(" SCALAR" ))
343
+ assertThat(it.extract(" data/__schema/queryType/fields[1]/type/ofType/fields[0]/type/ofType/name" ), equalTo(" String" ))
344
+
345
+ assertThat(it.extract(" data/__schema/queryType/fields[2]/type/ofType/kind" ), equalTo(" SCALAR" ))
346
+ assertThat(it.extract(" data/__schema/queryType/fields[2]/type/ofType/name" ), equalTo(" String" ))
347
+
348
+ assertThat(it.extract(" data/__schema/queryType/fields[3]/type/ofType/kind" ), equalTo(" SCALAR" ))
349
+ assertThat(it.extract(" data/__schema/queryType/fields[3]/type/ofType/name" ), equalTo(" String" ))
350
+ }
351
+
352
+ deserialize(schema.executeBlocking(" {definedValueProp {value}}" )).let {
353
+ assertThat(it.extract<String >(" data/definedValueProp/value" ), equalTo(33 ))
354
+ }
355
+ deserialize(schema.executeBlocking(" {undefinedValueProp {anotherValue}}" )).let {
356
+ assertThat(it.extract<String >(" data/undefinedValueProp/anotherValue" ), equalTo(" foo" ))
357
+ }
358
+ deserialize(schema.executeBlocking(" {definedValue}" )).let {
359
+ assertThat(it.extract<String >(" data/definedValue" ), equalTo(" good!" ))
360
+ }
361
+ expect<IllegalArgumentException >(" Requested value is not defined!" ) {
362
+ deserialize(schema.executeBlocking(" {undefinedValue}" ))
363
+ }
364
+ expect<IllegalArgumentException >(" Requested value is not defined!" ) {
365
+ deserialize(schema.executeBlocking(" {undefinedValueProp {value}}" ))
366
+ }
367
+ }
368
+
369
+ data class LambdaWrapper (val lambda : () -> Int )
370
+
371
+ @Test
372
+ fun `function properties can be handled by providing generic type resolver` () {
373
+ val typeResolver = object : DefaultGenericTypeResolver () {
374
+ override fun unbox (obj : Any ) = if (obj is Function0 <* >) obj() else super .unbox(obj)
375
+ override fun resolveMonad (type : KType ): KType {
376
+ if (typeOf<Function0 <* >>().isSupertypeOf(type)) {
377
+ return type.arguments.first().type
378
+ ? : throw SchemaException (" Could not get the type of the first argument for the type $type " )
315
379
}
380
+ return super .resolveMonad(type)
381
+ }
382
+ }
383
+
384
+ val schema = defaultSchema {
385
+ configure { genericTypeResolver = typeResolver }
386
+
387
+ type<LambdaWrapper >()
388
+
389
+ query(" lambda" ){
390
+ resolver { -> LambdaWrapper { 1 } }
316
391
}
317
392
}
393
+
394
+ deserialize(schema.executeBlocking(" {__schema{queryType{fields{ type { ofType { kind name fields { type {ofType {kind name}}}}}}}}}" )).let {
395
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/kind" ), equalTo(" OBJECT" ))
396
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/name" ), equalTo(" LambdaWrapper" ))
397
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/fields[0]/type/ofType/kind" ), equalTo(" SCALAR" ))
398
+ assertThat(it.extract(" data/__schema/queryType/fields[0]/type/ofType/fields[0]/type/ofType/name" ), equalTo(" Int" ))
399
+ }
400
+
401
+ deserialize(schema.executeBlocking(" {lambda {lambda}}" )).let {
402
+ assertThat(it.extract<String >(" data/lambda/lambda" ), equalTo(1 ))
403
+ }
318
404
}
319
405
320
406
@Test
0 commit comments