Skip to content

Commit

Permalink
Generates data fetcher interface for query types
Browse files Browse the repository at this point in the history
  • Loading branch information
zaenk committed Dec 7, 2021
1 parent c651a77 commit 9408681
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,20 @@ class CodeGen(private val config: CodeGenConfig) {
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }

val dataFetchersResult = definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.filter { it.name == "Query" }
.map { KotlinDataFetcherGenerator(config).generate(it) }
.fold(KotlinCodeGenResult()) { t: KotlinCodeGenResult, u: KotlinCodeGenResult -> t.merge(u) }

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

val client = generateJavaClientApi(definitions)
val entitiesClient = generateJavaClientEntitiesApi(definitions)
val entitiesRepresentationsTypes = generateKotlinClientEntitiesRepresentations(definitions)

return datatypesResult.merge(inputTypes).merge(interfacesResult).merge(unionResult).merge(enumsResult)
.merge(client).merge(entitiesClient).merge(entitiesRepresentationsTypes).merge(constantsClass)
.merge(client).merge(entitiesClient).merge(entitiesRepresentationsTypes).merge(dataFetchersResult).merge(constantsClass)
}

private fun generateKotlinClientEntitiesRepresentations(definitions: Collection<Definition<*>>): CodeGenResult {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.netflix.graphql.dgs.codegen.generators.kotlin

import com.netflix.graphql.dgs.DgsData
import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.KotlinCodeGenResult
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.TypeName
import graphql.language.FieldDefinition
import graphql.language.ObjectTypeDefinition
import graphql.schema.DataFetchingEnvironment

class KotlinDataFetcherGenerator(private val config: CodeGenConfig) {

private val packageName = config.packageNameDatafetchers
private val typeUtils = KotlinTypeUtils(config.packageNameTypes, config)

fun generate(query: ObjectTypeDefinition): KotlinCodeGenResult =
query.fieldDefinitions
.map { generateField(it) }
.fold(KotlinCodeGenResult()) { left, right -> left.merge(right) }

private fun generateField(field: FieldDefinition): KotlinCodeGenResult {
val fieldName = field.name.substring(0, 1).toUpperCase() + field.name.substring(1)
val className = fieldName + "Datafetcher"

val returnType = typeUtils.findReturnType(field.type)

val methodSpec = FunSpec.builder("get$fieldName")
.addAnnotation(AnnotationSpec.builder(DgsData::class).addMember("parentType", "\$S", "Query").addMember("field", "\$S", field.name).build())
.addModifiers(KModifier.ABSTRACT)
.also { builder ->
field.inputValueDefinitions.forEach {
val inputType: TypeName = typeUtils.findReturnType(it.type)
builder.addParameter(it.name, inputType)
}
}
.addParameter("dataFetchingEnvironment", DataFetchingEnvironment::class)
.returns(returnType)
.build()

val interfaceBuilder = TypeSpec.interfaceBuilder(className)
.addFunction(methodSpec)
.build()

val fileSpec = FileSpec.get(packageName, interfaceBuilder)

return KotlinCodeGenResult(dataFetchers = listOf(fileSpec))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.netflix.graphql.dgs.codegen

import com.squareup.kotlinpoet.*
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import graphql.schema.DataFetchingEnvironment
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Nested
import org.junit.jupiter.api.Test
Expand All @@ -32,6 +33,10 @@ import java.util.stream.Stream

class KotlinCodeGenTest {

val basePackageName = "com.netflix.graphql.dgs.codegen.tests.generated"
val typesPackageName = "$basePackageName.types"
val datafetchersPackageName = "$basePackageName.datafetchers"

@Test
fun generateDataClassWithStringProperties() {

Expand Down Expand Up @@ -66,6 +71,101 @@ class KotlinCodeGenTest {
assertCompilesKotlin(dataTypes)
}

@Test
fun generateDataFetcherInterfaceWithFunction() {

val schema = """
type Query {
people: [Person]
}
type Person {
firstname: String
lastname: String
}
""".trimIndent()

val dataFetchers = (CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
language = Language.KOTLIN
)
).generate() as KotlinCodeGenResult).dataFetchers

assertThat(dataFetchers.size).isEqualTo(1)
assertThat(dataFetchers[0].name).isEqualTo("PeopleDatafetcher")
assertThat(dataFetchers[0].packageName).isEqualTo(datafetchersPackageName)
val type = dataFetchers[0].members[0] as TypeSpec

assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE)
assertThat(type.funSpecs).hasSize(1)
.anySatisfy { fn ->
assertThat(fn.name).isEqualTo("getPeople")
assertThat(fn.returnType)
.satisfies { returnType ->
returnType as ParameterizedTypeName
assertThat(returnType.rawType.canonicalName).isEqualTo(List::class.qualifiedName)
assertThat(returnType.typeArguments).hasSize(1)
.allMatch { (it as ClassName).canonicalName == "$typesPackageName.Person" }
}
assertThat(fn.parameters).hasSize(1)
.allSatisfy {
assertThat(it.name).isEqualTo("dataFetchingEnvironment")
assertThat((it.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName)
}
}
}


@Test
fun generateDataFetcherInterfaceWithArgument() {

val schema = """
type Query {
person(name: String): Person
}
type Person {
firstname: String
lastname: String
}
""".trimIndent()

val dataFetchers = (CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
language = Language.KOTLIN
)
).generate() as KotlinCodeGenResult).dataFetchers

assertThat(dataFetchers.size).isEqualTo(1)
assertThat(dataFetchers[0].name).isEqualTo("PersonDatafetcher")
assertThat(dataFetchers[0].packageName).isEqualTo(datafetchersPackageName)
val type = dataFetchers[0].members[0] as TypeSpec

assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE)
assertThat(type.funSpecs).hasSize(1)
.anySatisfy { fn ->
assertThat(fn.name).isEqualTo("getPerson")
assertThat(fn.returnType)
.satisfies { returnType ->
returnType as ClassName
assertThat(returnType.canonicalName).isEqualTo("$typesPackageName.Person")
}
assertThat(fn.parameters).hasSize(2);
assertThat(fn.parameters[0]).satisfies {
assertThat(it.name).isEqualTo("name")
assertThat((it.type as ClassName).canonicalName).isEqualTo(String::class.qualifiedName)
}
assertThat(fn.parameters[1]).satisfies {
assertThat(it.name).isEqualTo("dataFetchingEnvironment")
assertThat((it.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName)
}
}
}

@Test
fun generateDataClassWithNullablePrimitive() {

Expand Down

0 comments on commit 9408681

Please sign in to comment.