Skip to content

Commit 4edf9fb

Browse files
committed
Add support to unwrap CompletableFuture and override with config (#11)
1 parent 83c1697 commit 4edf9fb

File tree

7 files changed

+120
-4
lines changed

7 files changed

+120
-4
lines changed

example/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
<groupId>com.expedia.www</groupId>
77
<artifactId>graphql-kotlin-example</artifactId>
8-
<version>0.0.8-SNAPSHOT</version>
8+
<version>0.0.1-SNAPSHOT</version>
99
<name>graphql-kotlin-example</name>
1010
<description>Example SpringBoot app using graphql-kotlin</description>
1111
<url>https://github.com/ExpediaDotCom/graphql-kotlin</url>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.expedia.graphql.sample.query
2+
3+
import com.expedia.graphql.annotations.GraphQLDescription
4+
import org.springframework.stereotype.Component
5+
import java.util.concurrent.CompletableFuture
6+
7+
/**
8+
* Example async queries.
9+
*/
10+
@Component
11+
class AsyncQuery : Query {
12+
13+
@GraphQLDescription("Delays for given amount and then echos the string back."
14+
+ " The default async executor will work with CompletableFuture."
15+
+ " To use other rx frameworks you'll need to install a custom one to handle the types correctly.")
16+
fun delayedEchoUsingCompletableFuture(msg: String, delaySeconds: Int): CompletableFuture<String> {
17+
val future = CompletableFuture<String>()
18+
Thread {
19+
Thread.sleep(delaySeconds * 1000L)
20+
future.complete(msg)
21+
}.start()
22+
return future
23+
}
24+
}

pom.xml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,12 @@
162162
<version>${junit.version}</version>
163163
<scope>test</scope>
164164
</dependency>
165+
<dependency>
166+
<groupId>io.reactivex.rxjava2</groupId>
167+
<artifactId>rxjava</artifactId>
168+
<version>2.1.0</version>
169+
<scope>test</scope>
170+
</dependency>
165171
</dependencies>
166172

167173
<profiles>

src/main/kotlin/com/expedia/graphql/schema/SchemaGenerator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,7 @@ internal class SchemaGenerator(
130130
val hookDataFetcher = config.hooks.didGenerateDataFetcher(fn, dataFetcher)
131131
builder.dataFetcher(hookDataFetcher)
132132
}
133-
builder.type(graphQLTypeOf(fn.returnType) as GraphQLOutputType)
133+
builder.type(graphQLTypeOf(config.monadResolver(fn.returnType)) as GraphQLOutputType)
134134
return builder.build()
135135
}
136136

src/main/kotlin/com/expedia/graphql/schema/SchemaGeneratorConfig.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package com.expedia.graphql.schema
22

33
import com.expedia.graphql.schema.hooks.NoopSchemaGeneratorHooks
44
import com.expedia.graphql.schema.hooks.SchemaGeneratorHooks
5-
import graphql.schema.GraphQLDirective
5+
import kotlin.reflect.KType
66

77
/**
88
* Settings for generating the schema.
@@ -11,5 +11,6 @@ data class SchemaGeneratorConfig(
1111
val supportedPackages: String,
1212
val topLevelQueryName: String = "TopLevelQuery",
1313
val topLevelMutationName: String = "TopLevelMutation",
14-
val hooks: SchemaGeneratorHooks = NoopSchemaGeneratorHooks()
14+
val hooks: SchemaGeneratorHooks = NoopSchemaGeneratorHooks(),
15+
val monadResolver: (KType) -> KType = completableFutureResolver
1516
)

src/main/kotlin/com/expedia/graphql/schema/typeMappers.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import graphql.schema.GraphQLNonNull
66
import graphql.schema.GraphQLType
77
import java.math.BigDecimal
88
import java.math.BigInteger
9+
import java.util.concurrent.CompletableFuture
910
import kotlin.reflect.KClass
1011
import kotlin.reflect.KType
1112

@@ -35,3 +36,11 @@ internal fun getGraphQLClassName(klass: KClass<*>, inputClass: Boolean): String?
3536
}
3637

3738
private fun getInputClassName(className: String?) = "${className}Input"
39+
40+
internal val completableFutureResolver = { type: KType ->
41+
if (type.classifier == CompletableFuture::class) {
42+
type.arguments.firstOrNull()?.type ?: type
43+
} else {
44+
type
45+
}
46+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package com.expedia.graphql.schema
2+
3+
import com.expedia.graphql.TopLevelObjectDef
4+
import com.expedia.graphql.toSchema
5+
import graphql.schema.GraphQLNonNull
6+
import io.reactivex.Maybe
7+
import io.reactivex.Observable
8+
import io.reactivex.Single
9+
import java.util.concurrent.CompletableFuture
10+
import kotlin.reflect.KType
11+
import kotlin.test.Test
12+
import kotlin.test.assertEquals
13+
14+
class SchemaGeneratorAsyncTests {
15+
16+
private val rxJavaMonadResolver: (KType) -> KType = { type ->
17+
when (type.classifier) {
18+
Observable::class, Single::class, Maybe::class -> type.arguments.firstOrNull()?.type
19+
else -> type
20+
} ?: type
21+
}
22+
private val testSchemaConfigWithRxJavaMonads =
23+
SchemaGeneratorConfig(supportedPackages = "com.expedia", monadResolver = rxJavaMonadResolver)
24+
25+
@Test
26+
fun `SchemaGenerator strips type argument from CompletableFuture to support async servlet`() {
27+
val schema = toSchema(listOf(TopLevelObjectDef(AsyncQuery())), config = testSchemaConfig)
28+
val returnTypeName =
29+
(schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDo").type as GraphQLNonNull).wrappedType.name
30+
assertEquals("Int", returnTypeName)
31+
}
32+
33+
@Test
34+
fun `SchemaGenerator strips type argument from RxJava2 Observable`() {
35+
val schema = toSchema(listOf(TopLevelObjectDef(RxJava2Query())), config = testSchemaConfigWithRxJavaMonads)
36+
val returnTypeName =
37+
(schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDo").type as GraphQLNonNull).wrappedType.name
38+
assertEquals("Int", returnTypeName)
39+
}
40+
41+
@Test
42+
fun `SchemaGenerator strips type argument from RxJava2 Single`() {
43+
val schema = toSchema(listOf(TopLevelObjectDef(RxJava2Query())), config = testSchemaConfigWithRxJavaMonads)
44+
val returnTypeName =
45+
(schema.getObjectType("TopLevelQuery").getFieldDefinition("asynchronouslyDoSingle").type as GraphQLNonNull).wrappedType.name
46+
assertEquals("Int", returnTypeName)
47+
}
48+
49+
@Test
50+
fun `SchemaGenerator strips type argument from RxJava2 Maybe`() {
51+
val schema = toSchema(listOf(TopLevelObjectDef(RxJava2Query())), config = testSchemaConfigWithRxJavaMonads)
52+
val returnTypeName =
53+
(schema.getObjectType("TopLevelQuery").getFieldDefinition("maybe").type as GraphQLNonNull).wrappedType.name
54+
assertEquals("Int", returnTypeName)
55+
}
56+
57+
class AsyncQuery {
58+
fun asynchronouslyDo(): CompletableFuture<Int> {
59+
return CompletableFuture.completedFuture(1)
60+
}
61+
}
62+
63+
class RxJava2Query {
64+
fun asynchronouslyDo(): Observable<Int> {
65+
return Observable.just(1)
66+
}
67+
68+
fun asynchronouslyDoSingle(): Single<Int> {
69+
return Single.just(1)
70+
}
71+
72+
fun maybe(): Maybe<Int> {
73+
return Maybe.empty()
74+
}
75+
}
76+
}

0 commit comments

Comments
 (0)