Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,7 @@ class CodeGen(private val config: CodeGenConfig) {
return if (config.generateClientApi) {
definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.filter { it.name == "Query" || it.name == "Mutation" || it.name == "Subscription" }
.filter { OperationTypes.isOperationType(it.name) }
.map { ClientApiGenerator(config, document).generate(it) }
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
} else CodeGenResult()
Expand Down Expand Up @@ -190,18 +190,25 @@ class CodeGen(private val config: CodeGenConfig) {
}

private fun generateJavaDataFetchers(definitions: Collection<Definition<*>>): CodeGenResult {
return definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.filter { it.name == "Query" }
.map { DatafetcherGenerator(config, document).generate(it) }
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
return if (config.generateDataFetchersAsInterfaces) {
definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.map { DataFetcherInterfaceGenerator(config, document).generate(it) }
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
} else {
definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.filter { it.name == OperationTypes.query }
.map { DatafetcherGenerator(config, document).generate(it) }
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
}
}

private fun generateJavaDataType(definitions: Collection<Definition<*>>): CodeGenResult {
return definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.excludeSchemaTypeExtension()
.filter { it.name != "Query" && it.name != "Mutation" && it.name != "RelayPageInfo" }
.filter { it.name != OperationTypes.query && it.name != OperationTypes.mutation && it.name != "RelayPageInfo" }
.filter { config.generateInterfaces || config.generateDataTypes || it.name in requiredTypeCollector.requiredTypes }
.map {
DataTypeGenerator(config, document).generate(it, findTypeExtensions(it.name, definitions))
Expand Down Expand Up @@ -262,13 +269,22 @@ class CodeGen(private val config: CodeGenConfig) {
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }

val dataFetcherResult = if (config.generateDataFetchersAsInterfaces) {
definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.map { KotlinDataFetcherInterfaceGenerator(config).generate(it) }
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
} else {
CodeGenResult()
}

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)
return datatypesResult.merge(inputTypes).merge(interfacesResult).merge(unionResult).merge(enumsResult).merge(dataFetcherResult)
.merge(client).merge(entitiesClient).merge(entitiesRepresentationsTypes).merge(constantsClass)
}

Expand Down Expand Up @@ -299,7 +315,7 @@ class CodeGen(private val config: CodeGenConfig) {
return definitions.asSequence()
.filterIsInstance<ObjectTypeDefinition>()
.excludeSchemaTypeExtension()
.filter { it.name != "Query" && it.name != "Mutation" && it.name != "RelayPageInfo" }
.filter { !OperationTypes.isOperationType(it.name) && it.name != "RelayPageInfo" }
.filter { config.generateDataTypes || it.name in requiredTypeCollector.requiredTypes }
.map {
val extensions = findTypeExtensions(it.name, definitions)
Expand Down Expand Up @@ -336,6 +352,7 @@ data class CodeGenConfig(
/** If enabled, the names of the classes available via the DgsConstant class will be snake cased.*/
val snakeCaseConstantNames: Boolean = false,
val generateInterfaceSetters: Boolean = true,
val generateDataFetchersAsInterfaces: Boolean = false,
) {
val packageNameClient: String
get() = "$packageName.$subPackageNameClient"
Expand Down Expand Up @@ -445,21 +462,21 @@ fun List<FieldDefinition>.filterSkipped(): List<FieldDefinition> {

fun List<FieldDefinition>.filterIncludedInConfig(definitionName: String, config: CodeGenConfig): List<FieldDefinition> {
return when (definitionName) {
"Query" -> {
OperationTypes.query -> {
if (config.includeQueries.isEmpty()) {
this
} else {
this.filter { it.name in config.includeQueries }
}
}
"Mutation" -> {
OperationTypes.mutation -> {
if (config.includeMutations.isEmpty()) {
this
} else {
this.filter { it.name in config.includeMutations }
}
}
"Subscription" -> {
OperationTypes.subscription -> {
if (config.includeSubscriptions.isEmpty()) {
this
} else {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
private val typeMapping: Map<String, String> by option("--type-mapping").associate()
private val shortProjectionNames by option("--short-projection-names").flag()
private val generateInterfaceSetters by option("--generate-interface-setters").flag()
private val generateDataFetchersAsInterfaces by option("--generate-datafetchers-as-interfaces", help = "Generate the data fetchers as interfaces instead of generating basic examples").flag(default = false)

override fun run() {
val inputSchemas = if (schemas.isEmpty()) {
Expand Down Expand Up @@ -92,7 +93,8 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
shortProjectionNames = shortProjectionNames,
generateDataTypes = generateDataTypes,
generateInterfaces = generateInterfaces,
generateInterfaceSetters = generateInterfaceSetters
generateInterfaceSetters = generateInterfaceSetters,
generateDataFetchersAsInterfaces = generateDataFetchersAsInterfaces
)
} else {
CodeGenConfig(
Expand All @@ -112,7 +114,8 @@ class CodeGenCli : CliktCommand("Generate Java sources for SCHEMA file(s)") {
shortProjectionNames = shortProjectionNames,
generateDataTypes = generateDataTypes,
generateInterfaces = generateInterfaces,
generateInterfaceSetters = generateInterfaceSetters
generateInterfaceSetters = generateInterfaceSetters,
generateDataFetchersAsInterfaces = generateDataFetchersAsInterfaces
)
}
).generate()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
*
* 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

import graphql.language.Document
import graphql.language.SchemaDefinition

object OperationTypes {
fun initialize(document: Document) {
restoreDefaults()
val schemaDefinitionList = document.definitions.filterIsInstance<SchemaDefinition>()

if (schemaDefinitionList.isNotEmpty()) {
val schemaDefinition = schemaDefinitionList.last()
schemaDefinition.operationTypeDefinitions
.forEach {
when (it.name) {
"query" -> query = it.typeName.name
"mutation" -> mutation = it.typeName.name
"subscription" -> subscription = it.typeName.name
}
}
}
}

private fun restoreDefaults() {
query = "Query"
mutation = "Mutation"
subscription = "Subscription"
}

fun isOperationType(typeName: String) = typeName == query || typeName == mutation || typeName == subscription

var query = "Query"
var mutation = "Mutation"
var subscription = "Subscription"
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,11 +42,14 @@ class RequiredTypeCollector(

init {
val fieldDefinitions = mutableListOf<FieldDefinition>()

OperationTypes.initialize(document)

for (definition in document.definitions.asSequence().filterIsInstance<ObjectTypeDefinition>()) {
when (definition.name) {
"Query" -> definition.fieldDefinitions.filterTo(fieldDefinitions) { it.name in queries }
"Mutation" -> definition.fieldDefinitions.filterTo(fieldDefinitions) { it.name in mutations }
"Subscription" -> definition.fieldDefinitions.filterTo(fieldDefinitions) { it.name in subscriptions }
OperationTypes.query -> definition.fieldDefinitions.filterTo(fieldDefinitions) { it.name in queries }
OperationTypes.mutation -> definition.fieldDefinitions.filterTo(fieldDefinitions) { it.name in mutations }
OperationTypes.subscription -> definition.fieldDefinitions.filterTo(fieldDefinitions) { it.name in subscriptions }
}
}

Expand Down Expand Up @@ -127,4 +130,4 @@ class RequiredTypeCollector(
fieldDefinitions
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package com.netflix.graphql.dgs.codegen.generators.java

import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.OperationTypes
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.capitalized
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInputExtensions
Expand Down Expand Up @@ -103,13 +104,13 @@ class ConstantsGenerator(private val config: CodeGenConfig, private val document
constantsType.addField(FieldSpec.builder(TypeName.get(String::class.java), "TYPE_NAME").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer(""""${it.name}"""").build())
}

if (document.definitions.any { it is ObjectTypeDefinition && it.name == "Query" }) {
if (document.definitions.any { it is ObjectTypeDefinition && it.name == OperationTypes.query }) {
javaType.addField(FieldSpec.builder(TypeName.get(String::class.java), "QUERY_TYPE").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer(""""Query"""").build())
}
if (document.definitions.any { it is ObjectTypeDefinition && it.name == "MUTATION" }) {
if (document.definitions.any { it is ObjectTypeDefinition && it.name == OperationTypes.mutation }) {
javaType.addField(FieldSpec.builder(TypeName.get(String::class.java), "MUTATION_TYPE").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer(""""Mutation"""").build())
}
if (document.definitions.any { it is ObjectTypeDefinition && it.name == "Subscription" }) {
if (document.definitions.any { it is ObjectTypeDefinition && it.name == OperationTypes.subscription }) {
javaType.addField(FieldSpec.builder(TypeName.get(String::class.java), "SUBSCRIPTION_TYPE").addModifiers(Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL).initializer(""""Subscription"""").build())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
*
* 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.java

import com.netflix.graphql.dgs.*
import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.CodeGenResult
import com.netflix.graphql.dgs.codegen.OperationTypes
import com.netflix.graphql.dgs.codegen.generators.shared.CodeGeneratorUtils.capitalized
import com.netflix.graphql.dgs.codegen.generators.shared.isID
import com.netflix.graphql.dgs.codegen.isBaseType
import com.squareup.javapoet.*
import graphql.execution.UnknownOperationException
import graphql.language.Document
import graphql.language.FieldDefinition
import graphql.language.InputValueDefinition
import graphql.language.ObjectTypeDefinition
import org.reactivestreams.Publisher
import java.util.concurrent.CompletableFuture
import javax.lang.model.element.Modifier

class DataFetcherInterfaceGenerator(private val config: CodeGenConfig, private val document: Document) {
fun generate(objectTypeDefinition: ObjectTypeDefinition): CodeGenResult {
val isOperationType = OperationTypes.isOperationType(objectTypeDefinition.name)
val fields = if (!isOperationType) {
objectTypeDefinition.fieldDefinitions.filter { !it.type.isBaseType() && !it.type.isID() }
} else {
objectTypeDefinition.fieldDefinitions
}

if (fields.isNullOrEmpty()) {
return CodeGenResult()
}

return createDataFetcherInterface(
objectTypeDefinition,
if (!isOperationType) {
fields.map { field ->
createDataFetcherInterfaceMethodForType(field, objectTypeDefinition)
}
} else {
fields.map { field ->
createDataFetcherInterfaceMethodForOperation(field, objectTypeDefinition)
}
}
)
}

private fun createDataFetcherInterface(
objectTypeDefinition: ObjectTypeDefinition,
methods: List<MethodSpec>
): CodeGenResult {
val javaType = TypeSpec.interfaceBuilder(objectTypeDefinition.name.capitalized() + "DataFetcher")
.addModifiers(Modifier.PUBLIC)
.addMethods(methods)

val javaFile = JavaFile.builder(getPackageName(), javaType.build()).build()

return CodeGenResult(javaDataFetchers = listOf(javaFile))
}

private fun createDataFetcherInterfaceMethodForOperation(field: FieldDefinition, parent: ObjectTypeDefinition): MethodSpec {
val returnType = TypeUtils(config.packageNameTypes, config, document).findReturnType(field.type)

val methodSpec = MethodSpec.methodBuilder(field.name)
.returns(
if (parent.name == OperationTypes.subscription)
ParameterizedTypeName.get(ClassName.get(Publisher::class.java), returnType)
else
returnType
)
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(
when (parent.name) {
OperationTypes.query -> AnnotationSpec
.builder(DgsQuery::class.java)
.addMember(DgsQuery::field.name, "\$S", field.name)
.build()
OperationTypes.mutation -> AnnotationSpec
.builder(DgsMutation::class.java)
.addMember(DgsMutation::field.name, "\$S", field.name)
.build()
OperationTypes.subscription -> AnnotationSpec
.builder(DgsSubscription::class.java)
.addMember(DgsSubscription::field.name, "\$S", field.name)
.build()
else -> throw UnknownOperationException(parent.name)
}
)
.addParameter(ParameterSpec.builder(DgsDataFetchingEnvironment::class.java, "dataFetchingEnvironment").build())

generateInputParameter(field.inputValueDefinitions).forEach(methodSpec::addParameter)

return methodSpec.build()
}

private fun createDataFetcherInterfaceMethodForType(field: FieldDefinition, parent: ObjectTypeDefinition): MethodSpec {
val returnType = TypeUtils(config.packageNameTypes, config, document).findReturnType(field.type)

val methodSpec = MethodSpec.methodBuilder(field.name)
.returns(ParameterizedTypeName.get(ClassName.get(CompletableFuture::class.java), returnType))
.addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT)
.addAnnotation(
AnnotationSpec.builder(DgsData::class.java)
.addMember(DgsData::field.name, "\$S", field.name)
.addMember(DgsData::parentType.name, "\$S", parent.name)
.build()
)
.addParameter(
ParameterSpec.builder(DgsDataFetchingEnvironment::class.java, "dataFetchingEnvironment").build()
)

generateInputParameter(field.inputValueDefinitions).forEach(methodSpec::addParameter)

return methodSpec.build()
}

private fun generateInputParameter(inputValueDefinitions: List<InputValueDefinition>): List<ParameterSpec> {
return inputValueDefinitions.map {
ParameterSpec.builder(
TypeUtils(config.packageNameTypes, config, document).findReturnType(it.type),
it.name
).addAnnotation(
AnnotationSpec.builder(InputArgument::class.java)
.addMember(InputArgument::name.name, "\$S", it.name)
.build()
).build()
}
}

private fun getPackageName(): String {
return config.packageNameDatafetchers
}
}
Loading