Skip to content

Commit 1599deb

Browse files
committed
Generates data fetcher interface for query types
1 parent 2603477 commit 1599deb

File tree

4 files changed

+183
-1
lines changed

4 files changed

+183
-1
lines changed

graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,13 @@ class CodeGen(private val config: CodeGenConfig) {
9898
codeGenResult.kotlinInputTypes.forEach { it.writeTo(config.outputDir) }
9999
codeGenResult.kotlinInterfaces.forEach { it.writeTo(config.outputDir) }
100100
codeGenResult.kotlinEnumTypes.forEach { it.writeTo(config.outputDir) }
101-
codeGenResult.kotlinDataFetchers.forEach { it.writeTo(config.examplesOutputDir) }
101+
codeGenResult.kotlinDataFetchers.forEach {
102+
if (config.generateDataFetcherInterfaces) {
103+
it.writeTo(config.outputDir)
104+
} else {
105+
it.writeTo(config.examplesOutputDir)
106+
}
107+
}
102108
codeGenResult.kotlinConstants.forEach { it.writeTo(config.outputDir) }
103109
codeGenResult.kotlinClientTypes.forEach { it.writeTo(config.outputDir) }
104110
codeGenResult.docFiles.forEach { it.writeTo(config.generatedDocsFolder) }
@@ -387,12 +393,23 @@ class CodeGen(private val config: CodeGenConfig) {
387393

388394
val constantsClass = KotlinConstantsGenerator(config, document).generate()
389395

396+
val dataFetchers = if (config.generateDataFetcherInterfaces) {
397+
definitions.asSequence()
398+
.filterIsInstance<ObjectTypeDefinition>()
399+
.filter { it.name == "Query" }
400+
.map { KotlinDataFetcherGenerator(config, document).generate(it) }
401+
.fold(CodeGenResult()) { result, next -> result.merge(next) }
402+
} else {
403+
CodeGenResult()
404+
}
405+
390406
datatypesResult
391407
.merge(inputTypes)
392408
.merge(interfacesResult)
393409
.merge(unionResult)
394410
.merge(enumsResult)
395411
.merge(constantsClass)
412+
.merge(dataFetchers)
396413
}
397414

398415
val clientTypes = if (config.generateKotlinClosureProjections) {
@@ -505,6 +522,7 @@ class CodeGenConfig(
505522
var generateInterfaces: Boolean = false,
506523
var generateKotlinNullableClasses: Boolean = false,
507524
var generateKotlinClosureProjections: Boolean = false,
525+
var generateDataFetcherInterfaces: Boolean = false,
508526
var typeMapping: Map<String, String> = emptyMap(),
509527
var includeQueries: Set<String> = emptySet(),
510528
var includeMutations: Set<String> = emptySet(),
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.netflix.graphql.dgs.codegen.generators.kotlin
2+
3+
import com.netflix.graphql.dgs.DgsComponent
4+
import com.netflix.graphql.dgs.DgsData
5+
import com.netflix.graphql.dgs.codegen.CodeGenConfig
6+
import com.netflix.graphql.dgs.codegen.CodeGenResult
7+
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.capitalized
8+
import com.squareup.kotlinpoet.AnnotationSpec
9+
import com.squareup.kotlinpoet.FileSpec
10+
import com.squareup.kotlinpoet.FunSpec
11+
import com.squareup.kotlinpoet.KModifier
12+
import com.squareup.kotlinpoet.TypeName
13+
import com.squareup.kotlinpoet.TypeSpec
14+
import graphql.language.Document
15+
import graphql.language.FieldDefinition
16+
import graphql.language.ObjectTypeDefinition
17+
import graphql.schema.DataFetchingEnvironment
18+
19+
class KotlinDataFetcherGenerator(private val config: CodeGenConfig, private val document: Document) {
20+
21+
private val packageName = config.packageNameDatafetchers
22+
private val typeUtils = KotlinTypeUtils(config.packageNameTypes, config, document)
23+
private val dsgConstantsPackageName = config.packageName
24+
25+
fun generate(query: ObjectTypeDefinition): CodeGenResult =
26+
query.fieldDefinitions
27+
.map { generateField(it) }
28+
.fold(CodeGenResult()) { left, right -> left.merge(right) }
29+
30+
private fun generateField(field: FieldDefinition): CodeGenResult {
31+
val fieldName = field.name.substring(0, 1).capitalized() + field.name.substring(1)
32+
val className = fieldName + "Datafetcher"
33+
34+
val returnType = typeUtils.findReturnType(field.type)
35+
36+
val methodSpec = FunSpec.builder("get$fieldName")
37+
.addAnnotation(AnnotationSpec.builder(DgsData::class).addMember("parentType = DgsConstants.QUERY.TYPE_NAME").addMember("field = DgsConstants.QUERY.${field.name.capitalized()}").build())
38+
.addModifiers(KModifier.ABSTRACT)
39+
.also { builder ->
40+
field.inputValueDefinitions.forEach {
41+
val inputType: TypeName = typeUtils.findReturnType(it.type)
42+
builder.addParameter(it.name, inputType)
43+
}
44+
}
45+
.addParameter("dataFetchingEnvironment", DataFetchingEnvironment::class)
46+
.returns(returnType)
47+
.build()
48+
49+
val interfaceBuilder = TypeSpec.interfaceBuilder(className)
50+
.addAnnotation(DgsComponent::class)
51+
.addFunction(methodSpec)
52+
.build()
53+
54+
val fileSpec = FileSpec.builder(packageName, interfaceBuilder.name!!)
55+
.addType(interfaceBuilder)
56+
.addImport(dsgConstantsPackageName, "DgsConstants")
57+
.build()
58+
59+
return CodeGenResult(kotlinDataFetchers = listOf(fileSpec))
60+
}
61+
}

graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ package com.netflix.graphql.dgs.codegen
2020

2121
import com.squareup.kotlinpoet.*
2222
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
23+
import graphql.schema.DataFetchingEnvironment
2324
import org.assertj.core.api.Assertions.assertThat
2425
import org.assertj.core.api.Assertions.assertThatThrownBy
2526
import org.assertj.core.data.Index
@@ -35,6 +36,10 @@ import java.util.stream.Stream.of
3536

3637
class KotlinCodeGenTest {
3738

39+
val basePackageName = "com.netflix.graphql.dgs.codegen.tests.generated"
40+
val typesPackageName = "$basePackageName.types"
41+
val datafetchersPackageName = "$basePackageName.datafetchers"
42+
3843
@Test
3944
fun generateDataClassWithStringProperties() {
4045
val schema = """
@@ -68,6 +73,100 @@ class KotlinCodeGenTest {
6873
assertCompilesKotlin(dataTypes)
6974
}
7075

76+
@Test
77+
fun generateDataFetcherInterfaceWithFunction() {
78+
val schema = """
79+
type Query {
80+
people: [Person]
81+
}
82+
83+
type Person {
84+
firstname: String
85+
lastname: String
86+
}
87+
""".trimIndent()
88+
89+
val dataFetchers = CodeGen(
90+
CodeGenConfig(
91+
schemas = setOf(schema),
92+
packageName = basePackageName,
93+
language = Language.KOTLIN,
94+
generateDataFetcherInterfaces = true
95+
)
96+
).generate().kotlinDataFetchers
97+
98+
assertThat(dataFetchers.size).isEqualTo(1)
99+
assertThat(dataFetchers[0].name).isEqualTo("PeopleDatafetcher")
100+
assertThat(dataFetchers[0].packageName).isEqualTo(datafetchersPackageName)
101+
val type = dataFetchers[0].members[0] as TypeSpec
102+
103+
assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE)
104+
assertThat(type.annotations).hasSize(1).first().satisfies({
105+
assertThat(it.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.DgsComponent")
106+
})
107+
assertThat(type.funSpecs).hasSize(1)
108+
val fn = type.funSpecs.single()
109+
assertThat(fn.name).isEqualTo("getPeople")
110+
val returnType = fn.returnType as ParameterizedTypeName
111+
assertThat(fn.returnType)
112+
assertThat(returnType.rawType.canonicalName).isEqualTo(List::class.qualifiedName)
113+
assertThat(returnType.typeArguments).hasSize(1)
114+
val arg0 = returnType.typeArguments.single() as ClassName
115+
assertThat(arg0.canonicalName).isEqualTo("$typesPackageName.Person")
116+
assertThat(fn.parameters).hasSize(1)
117+
val param0 = fn.parameters.single()
118+
assertThat(param0.name).isEqualTo("dataFetchingEnvironment")
119+
assertThat((param0.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName)
120+
assertThat(fn.annotations).hasSize(1).first().satisfies({ annotation ->
121+
assertThat(annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.DgsData")
122+
assertThat(annotation.members).satisfiesExactly(
123+
{ member -> assertThat(member.toString()).isEqualTo("parentType = DgsConstants.QUERY.TYPE_NAME") },
124+
{ member -> assertThat(member.toString()).isEqualTo("field = DgsConstants.QUERY.People") }
125+
)
126+
})
127+
}
128+
129+
@Test
130+
fun generateDataFetcherInterfaceWithArgument() {
131+
val schema = """
132+
type Query {
133+
person(name: String): Person
134+
}
135+
136+
type Person {
137+
firstname: String
138+
lastname: String
139+
}
140+
""".trimIndent()
141+
142+
val dataFetchers = CodeGen(
143+
CodeGenConfig(
144+
schemas = setOf(schema),
145+
packageName = basePackageName,
146+
language = Language.KOTLIN,
147+
generateDataFetcherInterfaces = true
148+
)
149+
).generate().kotlinDataFetchers
150+
151+
assertThat(dataFetchers.size).isEqualTo(1)
152+
assertThat(dataFetchers[0].name).isEqualTo("PersonDatafetcher")
153+
assertThat(dataFetchers[0].packageName).isEqualTo(datafetchersPackageName)
154+
val type = dataFetchers[0].members[0] as TypeSpec
155+
156+
assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE)
157+
assertThat(type.funSpecs).hasSize(1)
158+
val fn = type.funSpecs.single()
159+
assertThat(fn.name).isEqualTo("getPerson")
160+
assertThat((fn.returnType as ClassName).canonicalName).isEqualTo("$typesPackageName.Person")
161+
assertThat(fn.parameters).hasSize(2)
162+
val arg0 = fn.parameters[0]
163+
assertThat(arg0.name).isEqualTo("name")
164+
assertThat((arg0.type as ClassName).canonicalName).isEqualTo(String::class.qualifiedName)
165+
val arg1 = fn.parameters[1]
166+
assertThat(arg1.name).isEqualTo("dataFetchingEnvironment")
167+
assertThat((arg1.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName)
168+
}
169+
71170
@Test
72171
fun generateDataClassWithNullablePrimitive() {
73172
val schema = """

graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ open class GenerateJavaTask @Inject constructor(
8383
@Input
8484
var generateKotlinClosureProjections = false
8585

86+
@Input
87+
var generateDataFetcherInterfaces = false
88+
8689
@Input
8790
var generateDataTypes = true
8891

@@ -194,6 +197,7 @@ open class GenerateJavaTask @Inject constructor(
194197
generateClientApiv2 = generateClientv2,
195198
generateKotlinNullableClasses = generateKotlinNullableClasses,
196199
generateKotlinClosureProjections = generateKotlinClosureProjections,
200+
generateDataFetcherInterfaces = generateDataFetcherInterfaces,
197201
generateInterfaces = generateInterfaces,
198202
generateInterfaceSetters = generateInterfaceSetters,
199203
generateInterfaceMethodsForInterfaceFields = generateInterfaceMethodsForInterfaceFields,

0 commit comments

Comments
 (0)