Skip to content

Commit 7111f0e

Browse files
gscheibelrickfast
authored andcommitted
Data fetcher factory (#41)
* Update Junit dep to Junit5 * Add support for nested queries via custom datafetchers * Add example of custom datafetchers via spring beans * Mark lateinit field as nullable
1 parent f053b45 commit 7111f0e

File tree

13 files changed

+215
-29
lines changed

13 files changed

+215
-29
lines changed

example/pom.xml

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,15 @@
6060

6161
<properties>
6262
<java.version>1.8</java.version>
63-
<kotlin.version>1.2.70</kotlin.version>
63+
<kotlin.version>1.2.71</kotlin.version>
6464
<junit.version>4.12</junit.version>
6565
<graphiql.version>5.0.2</graphiql.version>
66-
<graphql-kotlin.version>DEVELOPMENT</graphql-kotlin.version>
66+
<graphql-kotlin.version>0.0.18-SNAPSHOT</graphql-kotlin.version>
6767
<graphql-servlet.version>6.1.2</graphql-servlet.version>
6868
</properties>
6969

7070
<build>
71-
<sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
71+
<sourceDirectory>src/main/kotlin</sourceDirectory>
7272
<plugins>
7373
<plugin>
7474
<artifactId>kotlin-maven-plugin</artifactId>
@@ -140,5 +140,16 @@
140140
</exclusion>
141141
</exclusions>
142142
</dependency>
143+
<dependency>
144+
<groupId>org.jetbrains.kotlin</groupId>
145+
<artifactId>kotlin-stdlib-jdk8</artifactId>
146+
<version>${kotlin.version}</version>
147+
</dependency>
148+
<dependency>
149+
<groupId>org.jetbrains.kotlin</groupId>
150+
<artifactId>kotlin-test</artifactId>
151+
<version>${kotlin.version}</version>
152+
<scope>test</scope>
153+
</dependency>
143154
</dependencies>
144155
</project>

example/src/main/kotlin/com.expedia.graphql.sample/Application.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.expedia.graphql.sample
22

33
import com.expedia.graphql.TopLevelObjectDef
44
import com.expedia.graphql.sample.context.MyGraphQLContextBuilder
5+
import com.expedia.graphql.sample.dataFetchers.SpringDataFetcherFactory
56
import com.expedia.graphql.sample.extension.CustomSchemaGeneratorHooks
67
import com.expedia.graphql.sample.mutation.Mutation
78
import com.expedia.graphql.sample.query.Query
@@ -30,7 +31,11 @@ class Application {
3031
private val logger = LoggerFactory.getLogger(Application::class.java)
3132

3233
@Bean
33-
fun schemaConfig(): SchemaGeneratorConfig = SchemaGeneratorConfig(supportedPackages = "com.expedia", hooks = CustomSchemaGeneratorHooks())
34+
fun schemaConfig(dataFetcherFactory: SpringDataFetcherFactory): SchemaGeneratorConfig = SchemaGeneratorConfig(
35+
supportedPackages = "com.expedia",
36+
hooks = CustomSchemaGeneratorHooks(),
37+
dataFetcherFactory = dataFetcherFactory
38+
)
3439

3540
@Bean
3641
fun schema(
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.expedia.graphql.sample.dataFetchers
2+
3+
import com.expedia.graphql.schema.extensions.deepName
4+
import graphql.schema.DataFetcher
5+
import graphql.schema.DataFetcherFactory
6+
import graphql.schema.DataFetcherFactoryEnvironment
7+
import org.springframework.beans.factory.BeanFactory
8+
import org.springframework.beans.factory.BeanFactoryAware
9+
import org.springframework.stereotype.Component
10+
11+
@Component
12+
class SpringDataFetcherFactory: DataFetcherFactory<Any>, BeanFactoryAware {
13+
private lateinit var beanFactory: BeanFactory
14+
15+
override fun setBeanFactory(beanFactory: BeanFactory?) {
16+
this.beanFactory = beanFactory!!
17+
}
18+
19+
override fun get(environment: DataFetcherFactoryEnvironment?): DataFetcher<Any> {
20+
21+
//Strip out possible `Input` and `!` suffixes added to by the SchemaGenerator
22+
val targetedTypeName = environment?.fieldDefinition?.type?.deepName?.removeSuffix("!")?.removeSuffix("Input")
23+
return beanFactory.getBean("${targetedTypeName}DataFetcher") as DataFetcher<Any>
24+
}
25+
26+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.expedia.graphql.sample.query
2+
3+
import com.fasterxml.jackson.annotation.JsonIgnore
4+
import graphql.schema.DataFetcher
5+
import graphql.schema.DataFetchingEnvironment
6+
import org.springframework.beans.factory.BeanFactory
7+
import org.springframework.beans.factory.BeanFactoryAware
8+
import org.springframework.beans.factory.annotation.Autowired
9+
import org.springframework.beans.factory.getBean
10+
import org.springframework.context.annotation.Scope
11+
import org.springframework.stereotype.Component
12+
13+
@Component
14+
class NestedQueries : Query {
15+
fun findAnimal(context: String): NestedAnimal = NestedAnimal(1, "cat")
16+
}
17+
18+
data class NestedAnimal(
19+
val id: Int,
20+
val type: String
21+
) {
22+
@JsonIgnore
23+
lateinit var details: NestedAnimalDetails
24+
}
25+
26+
@Component
27+
@Scope("prototype")
28+
data class NestedAnimalDetails @Autowired(required = false) constructor(private val animalId: Int) {
29+
fun veryDetailledFunction(): String = "Details($animalId)"
30+
}
31+
32+
@Component("NestedAnimalDetailsDataFetcher")
33+
@Scope("prototype")
34+
class AnimalDetailsDataFetcher : DataFetcher<NestedAnimalDetails>, BeanFactoryAware {
35+
36+
private lateinit var beanFactory: BeanFactory
37+
38+
override fun setBeanFactory(beanFactory: BeanFactory) {
39+
this.beanFactory = beanFactory
40+
}
41+
42+
override fun get(environment: DataFetchingEnvironment?): NestedAnimalDetails {
43+
val id = environment?.getSource<NestedAnimal>()?.id
44+
if (id == null) {
45+
throw Exception("Cannot retrieve animal details, the id is null")
46+
} else {
47+
return beanFactory.getBean(id)
48+
}
49+
}
50+
}

pom.xml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<kotlin-ktlint.version>0.29.0</kotlin-ktlint.version>
7676
<kotlin-detekt.version>1.0.0.RC8</kotlin-detekt.version>
7777
<junit.version>4.12</junit.version>
78+
<mockk.version>1.8.9.kotlin13</mockk.version>
7879
</properties>
7980

8081
<pluginRepositories>
@@ -209,14 +210,14 @@
209210
</dependency>
210211
<dependency>
211212
<groupId>org.jetbrains.kotlin</groupId>
212-
<artifactId>kotlin-test-junit</artifactId>
213+
<artifactId>kotlin-test-junit5</artifactId>
213214
<version>${kotlin.version}</version>
214215
<scope>test</scope>
215216
</dependency>
216217
<dependency>
217-
<groupId>junit</groupId>
218-
<artifactId>junit</artifactId>
219-
<version>${junit.version}</version>
218+
<groupId>io.mockk</groupId>
219+
<artifactId>mockk</artifactId>
220+
<version>${mockk.version}</version>
220221
<scope>test</scope>
221222
</dependency>
222223
<dependency>

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.expedia.graphql.schema
33
import com.expedia.graphql.schema.generator.completableFutureResolver
44
import com.expedia.graphql.schema.hooks.NoopSchemaGeneratorHooks
55
import com.expedia.graphql.schema.hooks.SchemaGeneratorHooks
6+
import graphql.schema.DataFetcherFactory
67
import kotlin.reflect.KType
78

89
/**
@@ -13,5 +14,6 @@ data class SchemaGeneratorConfig(
1314
val topLevelQueryName: String = "TopLevelQuery",
1415
val topLevelMutationName: String = "TopLevelMutation",
1516
val hooks: SchemaGeneratorHooks = NoopSchemaGeneratorHooks(),
16-
val monadResolver: (KType) -> KType = completableFutureResolver
17+
val monadResolver: (KType) -> KType = completableFutureResolver,
18+
val dataFetcherFactory: DataFetcherFactory<*>? = null
1719
)

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

Lines changed: 25 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import graphql.schema.GraphQLInputObjectType
2222
import graphql.schema.GraphQLInputType
2323
import graphql.schema.GraphQLInterfaceType
2424
import graphql.schema.GraphQLList
25+
import graphql.schema.GraphQLNonNull
2526
import graphql.schema.GraphQLObjectType
2627
import graphql.schema.GraphQLOutputType
2728
import graphql.schema.GraphQLSchema
@@ -146,12 +147,30 @@ internal class SchemaGenerator(
146147
return builder.build()
147148
}
148149

149-
private fun property(prop: KProperty<*>): GraphQLFieldDefinition = GraphQLFieldDefinition.newFieldDefinition()
150-
.description(prop.graphQLDescription())
151-
.name(prop.name)
152-
.type(graphQLTypeOf(prop.returnType) as GraphQLOutputType)
153-
.deprecate(prop.getDeprecationReason())
154-
.build()
150+
private fun property(prop: KProperty<*>): GraphQLFieldDefinition {
151+
val propertyType = graphQLTypeOf(prop.returnType) as GraphQLOutputType
152+
153+
val fieldBuilder = GraphQLFieldDefinition.newFieldDefinition()
154+
.description(prop.graphQLDescription())
155+
.name(prop.name)
156+
.type(propertyType)
157+
.deprecate(prop.getDeprecationReason())
158+
159+
return if (config.dataFetcherFactory != null && prop.isLateinit) {
160+
updatePropertyFieldBuilder(propertyType, fieldBuilder)
161+
} else {
162+
fieldBuilder
163+
}.build()
164+
}
165+
166+
private fun updatePropertyFieldBuilder(propertyType: GraphQLOutputType, fieldBuilder: GraphQLFieldDefinition.Builder): GraphQLFieldDefinition.Builder {
167+
val updatedFieldBuilder = if (propertyType is GraphQLNonNull) {
168+
fieldBuilder.type(propertyType.wrappedType as GraphQLOutputType)
169+
} else {
170+
fieldBuilder
171+
}
172+
return updatedFieldBuilder.dataFetcherFactory(config.dataFetcherFactory)
173+
}
155174

156175
private fun argument(parameter: KParameter): GraphQLArgument {
157176
throwIfInterfaceIsNotAuthorized(parameter)
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.expedia.graphql.schema.dataFetchers
2+
3+
import com.expedia.graphql.TopLevelObjectDef
4+
import com.expedia.graphql.schema.SchemaGeneratorConfig
5+
import com.expedia.graphql.schema.extensions.deepName
6+
import com.expedia.graphql.toSchema
7+
import graphql.GraphQL
8+
import graphql.schema.DataFetcher
9+
import graphql.schema.DataFetcherFactory
10+
import graphql.schema.DataFetcherFactoryEnvironment
11+
import graphql.schema.DataFetchingEnvironment
12+
import org.junit.jupiter.api.Test
13+
import kotlin.test.assertEquals
14+
15+
class CustomDataFetcherTests {
16+
@Test
17+
fun `Custom DataFetcher can be used on functions`() {
18+
val config = SchemaGeneratorConfig(supportedPackages = "com.expedia", dataFetcherFactory = PetDataFetcherFactory())
19+
val schema = toSchema(listOf(TopLevelObjectDef(AnimalQuery())), config = config)
20+
21+
val animalType = schema.getObjectType("Animal")
22+
assertEquals("AnimalDetails", animalType.getFieldDefinition("details").type.deepName)
23+
24+
val graphQL = GraphQL.newGraphQL(schema).build()
25+
val execute = graphQL.execute("{ findAnimal { id type details { specialId } } }")
26+
27+
val data = execute.getData<Map<String, Any>>()["findAnimal"] as? Map<*, *>
28+
assertEquals(1, data?.get("id"))
29+
assertEquals("cat", data?.get("type"))
30+
31+
val details = data?.get("details") as? Map<*, *>
32+
assertEquals(11, details?.get("specialId"))
33+
}
34+
}
35+
36+
class AnimalQuery {
37+
fun findAnimal(): Animal = Animal(1, "cat")
38+
}
39+
40+
data class Animal(
41+
val id: Int,
42+
val type: String
43+
) {
44+
lateinit var details: AnimalDetails
45+
}
46+
47+
data class AnimalDetails(val specialId: Int)
48+
49+
class PetDataFetcherFactory : DataFetcherFactory<Any> {
50+
override fun get(environment: DataFetcherFactoryEnvironment?): DataFetcher<Any> = AnimalDetailsDataFetcher()
51+
}
52+
53+
class AnimalDetailsDataFetcher : DataFetcher<Any> {
54+
55+
override fun get(environment: DataFetchingEnvironment?): AnimalDetails {
56+
val animal = environment?.getSource<Animal>()
57+
val specialId = animal?.id?.plus(10) ?: 0
58+
return animal.let { AnimalDetails(specialId) }
59+
}
60+
}

src/test/kotlin/com/expedia/graphql/schema/generator/DirectiveTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import graphql.introspection.Introspection
88
import graphql.schema.GraphQLInputObjectType
99
import graphql.schema.GraphQLNonNull
1010
import graphql.schema.GraphQLObjectType
11-
import org.junit.Test
11+
import org.junit.jupiter.api.Test
1212
import kotlin.test.assertEquals
1313
import kotlin.test.assertNotNull
1414
import kotlin.test.assertTrue

src/test/kotlin/com/expedia/graphql/schema/generator/KClassExtensionsTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.expedia.graphql.schema.generator
22

33
import com.expedia.graphql.schema.hooks.NoopSchemaGeneratorHooks
4-
import org.junit.Test
4+
import org.junit.jupiter.api.Test
55
import kotlin.reflect.KFunction
66
import kotlin.reflect.KProperty
77
import kotlin.test.assertEquals

0 commit comments

Comments
 (0)