diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt index 694a7fcc8..c56ec0b90 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/CodeGen.kt @@ -98,7 +98,13 @@ class CodeGen(private val config: CodeGenConfig) { codeGenResult.kotlinInputTypes.forEach { it.writeTo(config.outputDir) } codeGenResult.kotlinInterfaces.forEach { it.writeTo(config.outputDir) } codeGenResult.kotlinEnumTypes.forEach { it.writeTo(config.outputDir) } - codeGenResult.kotlinDataFetchers.forEach { it.writeTo(config.examplesOutputDir) } + codeGenResult.kotlinDataFetchers.forEach { + if (config.generateDataFetcherInterfaces) { + it.writeTo(config.outputDir) + } else { + it.writeTo(config.examplesOutputDir) + } + } codeGenResult.kotlinConstants.forEach { it.writeTo(config.outputDir) } codeGenResult.kotlinClientTypes.forEach { it.writeTo(config.outputDir) } codeGenResult.docFiles.forEach { it.writeTo(config.generatedDocsFolder) } @@ -359,7 +365,8 @@ class CodeGen(private val config: CodeGenConfig) { kotlinInputTypes = generateKotlin2InputTypes(config, document, requiredTypes), kotlinInterfaces = generateKotlin2Interfaces(config, document), kotlinEnumTypes = generateKotlin2EnumTypes(config, document, requiredTypes), - kotlinConstants = KotlinConstantsGenerator(config, document).generate().kotlinConstants + kotlinConstants = KotlinConstantsGenerator(config, document).generate().kotlinConstants, + kotlinDataFetchers = generateKotlin2DataFetcherInterfaces(config, document) ) } else { val datatypesResult = generateKotlinDataTypes(definitions) @@ -387,12 +394,23 @@ class CodeGen(private val config: CodeGenConfig) { val constantsClass = KotlinConstantsGenerator(config, document).generate() + val dataFetchers = if (config.generateDataFetcherInterfaces) { + definitions.asSequence() + .filterIsInstance() + .filter { it.name == "Query" || it.name == "Mutation" || it.name == "Subscription" } + .map { KotlinDataFetcherGenerator(config, document).generate(it) } + .fold(CodeGenResult()) { result, next -> result.merge(next) } + } else { + CodeGenResult() + } + datatypesResult .merge(inputTypes) .merge(interfacesResult) .merge(unionResult) .merge(enumsResult) .merge(constantsClass) + .merge(dataFetchers) } val clientTypes = if (config.generateKotlinClosureProjections) { @@ -505,6 +523,7 @@ class CodeGenConfig( var generateInterfaces: Boolean = false, var generateKotlinNullableClasses: Boolean = false, var generateKotlinClosureProjections: Boolean = false, + var generateDataFetcherInterfaces: Boolean = false, var typeMapping: Map = emptyMap(), var includeQueries: Set = emptySet(), var includeMutations: Set = emptySet(), diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinDataFetcherGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinDataFetcherGenerator.kt new file mode 100644 index 000000000..aeadba7d8 --- /dev/null +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin/KotlinDataFetcherGenerator.kt @@ -0,0 +1,92 @@ +package com.netflix.graphql.dgs.codegen.generators.kotlin + +import com.netflix.graphql.dgs.DgsComponent +import com.netflix.graphql.dgs.DgsData +import com.netflix.graphql.dgs.InputArgument +import com.netflix.graphql.dgs.codegen.CodeGenConfig +import com.netflix.graphql.dgs.codegen.CodeGenResult +import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.capitalized +import com.squareup.kotlinpoet.AnnotationSpec +import com.squareup.kotlinpoet.ClassName +import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.FunSpec +import com.squareup.kotlinpoet.KModifier +import com.squareup.kotlinpoet.ParameterSpec +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.TypeSpec +import graphql.language.Document +import graphql.language.FieldDefinition +import graphql.language.ObjectTypeDefinition +import graphql.schema.DataFetchingEnvironment + +class KotlinDataFetcherGenerator(private val config: CodeGenConfig, private val document: Document) { + + private val packageName = config.packageNameDatafetchers + private val typeUtils = KotlinTypeUtils(config.packageNameTypes, config, document) + private val dsgConstantsPackageName = config.packageName + + fun generate(topLevelObject: ObjectTypeDefinition): CodeGenResult = + topLevelObject.fieldDefinitions + .map { generateField(it, topLevelObject.name) } + .fold(CodeGenResult()) { left, right -> left.merge(right) } + + private fun generateField(field: FieldDefinition, topLevelObjectName: String): CodeGenResult { + val fieldName = field.name.capitalized() + val className = fieldName + topLevelObjectName + + val returnType = if (topLevelObjectName == "Subscription") { + val genericType = typeUtils.findReturnType(field.type) + ClassName.bestGuess("org.reactivestreams.Publisher").parameterizedBy(genericType) + } else { + typeUtils.findReturnType(field.type) + } + + val dsgConstantPrefix = when (topLevelObjectName) { + "Query" -> "DgsConstants.QUERY" + "Mutation" -> "DgsConstants.MUTATION" + "Subscription" -> "DgsConstants.SUBSCRIPTION" + else -> error("not supported top level object type: $topLevelObjectName") + } + + val annotationParentType = "$dsgConstantPrefix.TYPE_NAME" + + val annotationFieldName = "$dsgConstantPrefix.$fieldName" + + val dsgDataAnnotation = AnnotationSpec.builder(DgsData::class) + .addMember("parentType = $annotationParentType") + .addMember("field = $annotationFieldName") + .build() + + val methodSpec = FunSpec.builder("${field.name}") + .addAnnotation(dsgDataAnnotation) + .addModifiers(KModifier.ABSTRACT) + .addInputArguments(field, dsgConstantPrefix) + .addParameter("dataFetchingEnvironment", DataFetchingEnvironment::class) + .returns(returnType) + .build() + + val interfaceBuilder = TypeSpec.interfaceBuilder(className) + .addAnnotation(DgsComponent::class) + .addFunction(methodSpec) + .build() + + val fileSpec = FileSpec.builder(packageName, interfaceBuilder.name!!) + .addType(interfaceBuilder) + .addImport(dsgConstantsPackageName, "DgsConstants") + .build() + + return CodeGenResult(kotlinDataFetchers = listOf(fileSpec)) + } + + private fun FunSpec.Builder.addInputArguments(field: FieldDefinition, prefix: String): FunSpec.Builder = apply { + field.inputValueDefinitions.forEach { input -> + val inputAnnotation = AnnotationSpec.builder(InputArgument::class) + .addMember("$prefix.${field.name.uppercase()}_INPUT_ARGUMENT.${input.name.capitalized()}") + .build() + val inputType = ParameterSpec.builder(input.name, typeUtils.findReturnType(input.type)) + .addAnnotation(inputAnnotation) + .build() + addParameter(inputType) + } + } +} diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataFetcherInterfaces.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataFetcherInterfaces.kt new file mode 100644 index 000000000..169ea6ae0 --- /dev/null +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/GenerateKotlin2DataFetcherInterfaces.kt @@ -0,0 +1,38 @@ +/* + * + * Copyright 2020 Netflix, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.netflix.graphql.dgs.codegen.generators.kotlin2 + +import com.netflix.graphql.dgs.codegen.CodeGenConfig +import com.netflix.graphql.dgs.codegen.CodeGenResult +import com.netflix.graphql.dgs.codegen.generators.kotlin.KotlinDataFetcherGenerator +import com.squareup.kotlinpoet.FileSpec +import graphql.language.Document +import graphql.language.ObjectTypeDefinition + +fun generateKotlin2DataFetcherInterfaces(config: CodeGenConfig, document: Document): List = + if (config.generateDataFetcherInterfaces) { + document.definitions.asSequence() + .filterIsInstance() + .filter { it.name == "Query" || it.name == "Mutation" || it.name == "Subscription" } + .map { KotlinDataFetcherGenerator(config, document).generate(it) } + .fold(CodeGenResult()) { result, next -> result.merge(next) } + .kotlinDataFetchers + } else { + emptyList() + } diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt index 0a16f70df..dbba803cb 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/KotlinCodeGenTest.kt @@ -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.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.data.Index @@ -35,6 +36,10 @@ import java.util.stream.Stream.of class KotlinCodeGenTest { + val basePackageName = "com.netflix.graphql.dgs.codegen.tests.generated" + val typesPackageName = "$basePackageName.types" + val datafetchersPackageName = "$basePackageName.datafetchers" + @Test fun generateDataClassWithStringProperties() { val schema = """ @@ -68,6 +73,195 @@ 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, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("PeopleQuery") + assertThat(dataFetchers[0].packageName).isEqualTo(datafetchersPackageName) + val type = dataFetchers[0].members[0] as TypeSpec + + assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE) + assertThat(type.annotations).hasSize(1).first().satisfies({ + assertThat(it.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.DgsComponent") + }) + assertThat(type.funSpecs).hasSize(1) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("people") + val returnType = fn.returnType as ParameterizedTypeName + assertThat(fn.returnType) + assertThat(returnType.rawType.canonicalName).isEqualTo(List::class.qualifiedName) + assertThat(returnType.typeArguments).hasSize(1) + val arg0 = returnType.typeArguments.single() as ClassName + assertThat(arg0.canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(fn.parameters).hasSize(1) + val param0 = fn.parameters.single() + assertThat(param0.name).isEqualTo("dataFetchingEnvironment") + assertThat((param0.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + assertThat(fn.annotations).hasSize(1).first().satisfies({ annotation -> + assertThat(annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.DgsData") + assertThat(annotation.members).satisfiesExactly( + { member -> assertThat(member.toString()).isEqualTo("parentType = DgsConstants.QUERY.TYPE_NAME") }, + { member -> assertThat(member.toString()).isEqualTo("field = DgsConstants.QUERY.People") } + ) + }) + } + + @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, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("PersonQuery") + 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) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("person") + assertThat((fn.returnType as ClassName).canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(fn.parameters).hasSize(2) + val arg0 = fn.parameters[0] + assertThat(arg0.name).isEqualTo("name") + assertThat((arg0.type as ClassName).canonicalName).isEqualTo(String::class.qualifiedName) + assertThat(arg0.annotations).hasSize(1) + val arg0Annotation = arg0.annotations[0] + assertThat(arg0Annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.InputArgument") + assertThat(arg0Annotation.members.single().toString()).isEqualTo("DgsConstants.QUERY.PERSON_INPUT_ARGUMENT.Name") + val arg1 = fn.parameters[1] + assertThat(arg1.name).isEqualTo("dataFetchingEnvironment") + assertThat((arg1.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + } + + @Test + fun generateMutationInterfaceWithArgument() { + val schema = """ + type Mutation { + addPerson(person: Person): Person + } + + type Person { + firstname: String + lastname: String + } + """.trimIndent() + + val dataFetchers = CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("AddPersonMutation") + 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) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("addPerson") + assertThat((fn.returnType as ClassName).canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(fn.parameters).hasSize(2) + val arg0 = fn.parameters[0] + assertThat(arg0.name).isEqualTo("person") + assertThat((arg0.type as ClassName).canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(arg0.annotations).hasSize(1) + val arg0Annotation = arg0.annotations[0] + assertThat(arg0Annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.InputArgument") + assertThat(arg0Annotation.members.single().toString()).isEqualTo("DgsConstants.MUTATION.ADDPERSON_INPUT_ARGUMENT.Person") + val arg1 = fn.parameters[1] + assertThat(arg1.name).isEqualTo("dataFetchingEnvironment") + assertThat((arg1.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + } + + @Test + fun generateSubscriptionInterfaceWithArgument() { + val schema = """ + type Subscription { + personUpdated(id: Int): Person + } + + type Person { + firstname: String + lastname: String + } + """.trimIndent() + + val dataFetchers = CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("PersonUpdatedSubscription") + 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) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("personUpdated") + assertThat((fn.returnType as ParameterizedTypeName).rawType.canonicalName).isEqualTo("org.reactivestreams.Publisher") + assertThat((fn.returnType as ParameterizedTypeName).typeArguments[0].toString()).isEqualTo("$typesPackageName.Person?") + assertThat(fn.parameters).hasSize(2) + val arg0 = fn.parameters[0] + assertThat(arg0.name).isEqualTo("id") + assertThat((arg0.type as ClassName).canonicalName).isEqualTo("kotlin.Int") + assertThat(arg0.annotations).hasSize(1) + val arg0Annotation = arg0.annotations[0] + assertThat(arg0Annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.InputArgument") + assertThat(arg0Annotation.members.single().toString()).isEqualTo("DgsConstants.SUBSCRIPTION.PERSONUPDATED_INPUT_ARGUMENT.Id") + val arg1 = fn.parameters[1] + assertThat(arg1.name).isEqualTo("dataFetchingEnvironment") + assertThat((arg1.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + } + @Test fun generateDataClassWithNullablePrimitive() { val schema = """ diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt index c17ab3874..a1f743112 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/Kotline2CodeGenTest.kt @@ -18,9 +18,12 @@ package com.netflix.graphql.dgs.codegen +import com.squareup.kotlinpoet.ClassName import com.squareup.kotlinpoet.FileSpec +import com.squareup.kotlinpoet.ParameterizedTypeName import com.squareup.kotlinpoet.TypeSpec import com.squareup.kotlinpoet.asClassName +import graphql.schema.DataFetchingEnvironment import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test import java.io.Serializable @@ -134,4 +137,197 @@ class Kotline2CodeGenTest { assertCompilesKotlin(result.kotlinEnumTypes) } + + @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, + generateKotlinNullableClasses = true, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("PeopleQuery") + assertThat(dataFetchers[0].packageName).isEqualTo("$basePackageName.datafetchers") + val type = dataFetchers[0].members[0] as TypeSpec + + assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE) + assertThat(type.annotations).hasSize(1).first().satisfies({ + assertThat(it.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.DgsComponent") + }) + assertThat(type.funSpecs).hasSize(1) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("people") + val returnType = fn.returnType as ParameterizedTypeName + assertThat(fn.returnType) + assertThat(returnType.rawType.canonicalName).isEqualTo(List::class.qualifiedName) + assertThat(returnType.typeArguments).hasSize(1) + val arg0 = returnType.typeArguments.single() as ClassName + assertThat(arg0.canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(fn.parameters).hasSize(1) + val param0 = fn.parameters.single() + assertThat(param0.name).isEqualTo("dataFetchingEnvironment") + assertThat((param0.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + assertThat(fn.annotations).hasSize(1).first().satisfies({ annotation -> + assertThat(annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.DgsData") + assertThat(annotation.members).satisfiesExactly( + { member -> assertThat(member.toString()).isEqualTo("parentType = DgsConstants.QUERY.TYPE_NAME") }, + { member -> assertThat(member.toString()).isEqualTo("field = DgsConstants.QUERY.People") } + ) + }) + } + + @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, + generateKotlinNullableClasses = true, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("PersonQuery") + assertThat(dataFetchers[0].packageName).isEqualTo("$basePackageName.datafetchers") + val type = dataFetchers[0].members[0] as TypeSpec + + assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE) + assertThat(type.funSpecs).hasSize(1) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("person") + assertThat((fn.returnType as ClassName).canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(fn.parameters).hasSize(2) + val arg0 = fn.parameters[0] + assertThat(arg0.name).isEqualTo("name") + assertThat((arg0.type as ClassName).canonicalName).isEqualTo(String::class.qualifiedName) + assertThat(arg0.annotations).hasSize(1) + val arg0Annotation = arg0.annotations[0] + assertThat(arg0Annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.InputArgument") + assertThat(arg0Annotation.members.single().toString()).isEqualTo("DgsConstants.QUERY.PERSON_INPUT_ARGUMENT.Name") + val arg1 = fn.parameters[1] + assertThat(arg1.name).isEqualTo("dataFetchingEnvironment") + assertThat((arg1.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + } + + @Test + fun generateMutationInterfaceWithArgument() { + val schema = """ + type Mutation { + addPerson(person: Person): Person + } + + type Person { + firstname: String + lastname: String + } + """.trimIndent() + + val dataFetchers = CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + generateKotlinNullableClasses = true, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("AddPersonMutation") + assertThat(dataFetchers[0].packageName).isEqualTo("$basePackageName.datafetchers") + val type = dataFetchers[0].members[0] as TypeSpec + + assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE) + assertThat(type.funSpecs).hasSize(1) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("addPerson") + assertThat((fn.returnType as ClassName).canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(fn.parameters).hasSize(2) + val arg0 = fn.parameters[0] + assertThat(arg0.name).isEqualTo("person") + assertThat((arg0.type as ClassName).canonicalName).isEqualTo("$typesPackageName.Person") + assertThat(arg0.annotations).hasSize(1) + val arg0Annotation = arg0.annotations[0] + assertThat(arg0Annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.InputArgument") + assertThat(arg0Annotation.members.single().toString()).isEqualTo("DgsConstants.MUTATION.ADDPERSON_INPUT_ARGUMENT.Person") + val arg1 = fn.parameters[1] + assertThat(arg1.name).isEqualTo("dataFetchingEnvironment") + assertThat((arg1.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + } + + @Test + fun generateSubscriptionInterfaceWithArgument() { + val schema = """ + type Subscription { + personUpdated(id: Int): Person + } + + type Person { + firstname: String + lastname: String + } + """.trimIndent() + + val dataFetchers = CodeGen( + CodeGenConfig( + schemas = setOf(schema), + packageName = basePackageName, + language = Language.KOTLIN, + generateKotlinNullableClasses = true, + generateDataFetcherInterfaces = true + ) + ).generate().kotlinDataFetchers + + assertThat(dataFetchers.size).isEqualTo(1) + assertThat(dataFetchers[0].name).isEqualTo("PersonUpdatedSubscription") + assertThat(dataFetchers[0].packageName).isEqualTo("$basePackageName.datafetchers") + val type = dataFetchers[0].members[0] as TypeSpec + + assertThat(type.kind).isEqualTo(TypeSpec.Kind.INTERFACE) + assertThat(type.funSpecs).hasSize(1) + val fn = type.funSpecs.single() + assertThat(fn.name).isEqualTo("personUpdated") + assertThat((fn.returnType as ParameterizedTypeName).rawType.canonicalName).isEqualTo("org.reactivestreams.Publisher") + assertThat((fn.returnType as ParameterizedTypeName).typeArguments[0].toString()).isEqualTo("$typesPackageName.Person?") + assertThat(fn.parameters).hasSize(2) + val arg0 = fn.parameters[0] + assertThat(arg0.name).isEqualTo("id") + assertThat((arg0.type as ClassName).canonicalName).isEqualTo("kotlin.Int") + assertThat(arg0.annotations).hasSize(1) + val arg0Annotation = arg0.annotations[0] + assertThat(arg0Annotation.typeName.toString()).isEqualTo("com.netflix.graphql.dgs.InputArgument") + assertThat(arg0Annotation.members.single().toString()).isEqualTo("DgsConstants.SUBSCRIPTION.PERSONUPDATED_INPUT_ARGUMENT.Id") + val arg1 = fn.parameters[1] + assertThat(arg1.name).isEqualTo("dataFetchingEnvironment") + assertThat((arg1.type as ClassName).canonicalName).isEqualTo(DataFetchingEnvironment::class.qualifiedName) + } } diff --git a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt index 8ec1162b8..1f2c99bef 100644 --- a/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt +++ b/graphql-dgs-codegen-gradle/src/main/kotlin/com/netflix/graphql/dgs/codegen/gradle/GenerateJavaTask.kt @@ -83,6 +83,9 @@ open class GenerateJavaTask @Inject constructor( @Input var generateKotlinClosureProjections = false + @Input + var generateDataFetcherInterfaces = false + @Input var generateDataTypes = true @@ -194,6 +197,7 @@ open class GenerateJavaTask @Inject constructor( generateClientApiv2 = generateClientv2, generateKotlinNullableClasses = generateKotlinNullableClasses, generateKotlinClosureProjections = generateKotlinClosureProjections, + generateDataFetcherInterfaces = generateDataFetcherInterfaces, generateInterfaces = generateInterfaces, generateInterfaceSetters = generateInterfaceSetters, generateInterfaceMethodsForInterfaceFields = generateInterfaceMethodsForInterfaceFields,