Skip to content

Commit

Permalink
Merge branch 'Netflix:master' into feat/kotlin-interfaces
Browse files Browse the repository at this point in the history
  • Loading branch information
crypter68 authored Jun 23, 2022
2 parents bddf915 + 692b97c commit 10690cc
Show file tree
Hide file tree
Showing 381 changed files with 9,910 additions and 49 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
*
* 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.client.codegen

@DslMarker
annotation class QueryProjectionMarker

@QueryProjectionMarker
abstract class GraphQLProjection(defaultFields: Set<String> = setOf("__typename")) : GraphQLInput() {

private val builder = StringBuilder("{ ${defaultFields.joinToString(" ")} ")

protected fun field(field: String) {
builder.append("$field ")
}

protected fun <T : GraphQLProjection> project(field: String, projection: T, projectionFields: T.() -> T) {
builder.append("$field ")
projectionFields.invoke(projection)
builder.append(projection.asQuery())
}

fun asQuery() = "$builder}"
}

abstract class GraphQLInput {

companion object {

private val inputSerializer = InputValueSerializer()

protected fun inputToString(value: Any?): String {
// TODO escape newlines in InputValueSerializer
return inputSerializer.serialize(value).replace("\n", "\\n")
}

val defaults: ThreadLocal<MutableSet<String>> = ThreadLocal.withInitial { mutableSetOf() }

@JvmStatic
protected fun <T> default(arg: String): T? {
defaults.get().add(arg)
return null
}
}

private val _defaults = defaults.get()

init {
defaults.set(mutableSetOf())
}

protected fun formatArgs(vararg args: Pair<String, Any?>): String {
return args
.filter { (k, _) -> !_defaults.contains(k) }
.joinToString(", ") { (k, v) -> "$k: ${inputToString(v)}" }
}
}
1 change: 1 addition & 0 deletions graphql-dgs-codegen-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ dependencies {
implementation(project(":graphql-dgs-codegen-client-core"))
implementation 'com.graphql-java:graphql-java'
implementation 'com.fasterxml.jackson.core:jackson-annotations'
implementation 'com.fasterxml.jackson.core:jackson-databind'
implementation 'org.slf4j:slf4j-api'

implementation 'com.squareup:javapoet:1.13.+'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ package com.netflix.graphql.dgs.codegen

import com.netflix.graphql.dgs.codegen.generators.java.*
import com.netflix.graphql.dgs.codegen.generators.kotlin.*
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2ClientTypes
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2DataTypes
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2EnumTypes
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2InputTypes
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2Interfaces
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findEnumExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInputExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInterfaceExtensions
Expand All @@ -32,6 +37,7 @@ import graphql.language.*
import graphql.parser.MultiSourceReader
import graphql.parser.Parser
import graphql.parser.ParserOptions
import graphql.schema.idl.TypeUtil
import java.io.File
import java.nio.file.Path
import java.nio.file.Paths
Expand Down Expand Up @@ -73,10 +79,12 @@ class CodeGen(private val config: CodeGenConfig) {
}
codeGenResult.javaConstants.forEach { it.writeTo(config.outputDir) }
codeGenResult.kotlinDataTypes.forEach { it.writeTo(config.outputDir) }
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.kotlinConstants.forEach { it.writeTo(config.outputDir) }
codeGenResult.kotlinClientTypes.forEach { it.writeTo(config.outputDir) }
}

return codeGenResult
Expand Down Expand Up @@ -105,9 +113,50 @@ class CodeGen(private val config: CodeGenConfig) {
readerBuilder.string(schema, null)
}

return readerBuilder.build().use { reader ->
val document = readerBuilder.build().use { reader ->
parser.parseDocument(reader, options)
}

return document.transform {

// for kotlin2, add implicit types like PageInfo to the schema so classes are generated
if (config.generateKotlinNullableClasses || config.generateKotlinClosureProjections) {
val objectTypeDefs = document.getDefinitionsOfType(ObjectTypeDefinition::class.java)
if (!objectTypeDefs.any { def -> def.name == "PageInfo" } &&
objectTypeDefs.any { def -> def.fieldDefinitions.any { field -> TypeUtil.unwrapAll(field.type).name == "PageInfo" } }
) {
it.definition(
ObjectTypeDefinition.newObjectTypeDefinition()
.name("PageInfo")
.fieldDefinition(
FieldDefinition.newFieldDefinition()
.name("hasNextPage")
.type(NonNullType(TypeName("Boolean")))
.build()
)
.fieldDefinition(
FieldDefinition.newFieldDefinition()
.name("hasPreviousPage")
.type(NonNullType(TypeName("Boolean")))
.build()
)
.fieldDefinition(
FieldDefinition.newFieldDefinition()
.name("startCursor")
.type(TypeName("String"))
.build()
)
.fieldDefinition(
FieldDefinition.newFieldDefinition()
.name("endCursor")
.type(TypeName("String"))
.build()
)
.build()
)
}
}
}
}

private fun generateJava(): CodeGenResult {
Expand Down Expand Up @@ -239,37 +288,72 @@ class CodeGen(private val config: CodeGenConfig) {
private fun generateKotlin(): CodeGenResult {
val definitions = document.definitions

val datatypesResult = generateKotlinDataTypes(definitions)
val inputTypes = generateKotlinInputTypes(definitions)
val interfacesResult = generateKotlinInterfaceTypes(definitions)
val requiredTypeCollector = RequiredTypeCollector(
document = document,
queries = config.includeQueries,
mutations = config.includeMutations,
subscriptions = config.includeSubscriptions,
)
val requiredTypes = requiredTypeCollector.requiredTypes

val unionResult = definitions.asSequence()
.filterIsInstance<UnionTypeDefinition>()
.excludeSchemaTypeExtension()
.map {
val extensions = findUnionExtensions(it.name, definitions)
KotlinUnionTypeGenerator(config).generate(it, extensions)
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
val dataTypes = if (config.generateKotlinNullableClasses) {

val enumsResult = definitions.asSequence()
.filterIsInstance<EnumTypeDefinition>()
.excludeSchemaTypeExtension()
.filter { config.generateDataTypes || it.name in requiredTypeCollector.requiredTypes }
.map {
val extensions = findEnumExtensions(it.name, definitions)
KotlinEnumTypeGenerator(config).generate(it, extensions)
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
CodeGenResult(
kotlinDataTypes = generateKotlin2DataTypes(config, document, requiredTypes),
kotlinInputTypes = generateKotlin2InputTypes(config, document, requiredTypes),
kotlinInterfaces = generateKotlin2Interfaces(config, document),
kotlinEnumTypes = generateKotlin2EnumTypes(config, document, requiredTypes),
kotlinConstants = KotlinConstantsGenerator(config, document).generate().kotlinConstants,
)
} else {

val constantsClass = KotlinConstantsGenerator(config, document).generate()
val datatypesResult = generateKotlinDataTypes(definitions)
val inputTypes = generateKotlinInputTypes(definitions)
val interfacesResult = generateKotlinInterfaceTypes(definitions)

val client = generateJavaClientApi(definitions)
val entitiesClient = generateJavaClientEntitiesApi(definitions)
val entitiesRepresentationsTypes = generateKotlinClientEntitiesRepresentations(definitions)
val unionResult = definitions.asSequence()
.filterIsInstance<UnionTypeDefinition>()
.excludeSchemaTypeExtension()
.map {
val extensions = findUnionExtensions(it.name, definitions)
KotlinUnionTypeGenerator(config).generate(it, extensions)
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }

val enumsResult = definitions.asSequence()
.filterIsInstance<EnumTypeDefinition>()
.excludeSchemaTypeExtension()
.filter { config.generateDataTypes || it.name in requiredTypeCollector.requiredTypes }
.map {
val extensions = findEnumExtensions(it.name, definitions)
KotlinEnumTypeGenerator(config).generate(it, extensions)
}
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }

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

datatypesResult
.merge(inputTypes)
.merge(interfacesResult)
.merge(unionResult)
.merge(enumsResult)
.merge(constantsClass)
}

return datatypesResult.merge(inputTypes).merge(interfacesResult).merge(unionResult).merge(enumsResult)
.merge(client).merge(entitiesClient).merge(entitiesRepresentationsTypes).merge(constantsClass)
val clientTypes = if (config.generateKotlinClosureProjections) {
CodeGenResult(
kotlinClientTypes = generateKotlin2ClientTypes(config, document),
)
} else {

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

client.merge(entitiesClient).merge(entitiesRepresentationsTypes)
}

return dataTypes.merge(clientTypes)
}

private fun generateKotlinClientEntitiesRepresentations(definitions: Collection<Definition<*>>): CodeGenResult {
Expand Down Expand Up @@ -354,6 +438,8 @@ data class CodeGenConfig(
val generateBoxedTypes: Boolean = false,
val generateClientApi: Boolean = false,
val generateInterfaces: Boolean = false,
val generateKotlinNullableClasses: Boolean = false,
val generateKotlinClosureProjections: Boolean = false,
val typeMapping: Map<String, String> = emptyMap(),
val includeQueries: Set<String> = emptySet(),
val includeMutations: Set<String> = emptySet(),
Expand Down Expand Up @@ -398,7 +484,7 @@ data class CodeGenConfig(

enum class Language {
JAVA,
KOTLIN
KOTLIN,
}

data class CodeGenResult(
Expand All @@ -410,10 +496,12 @@ data class CodeGenResult(
val clientProjections: List<JavaFile> = listOf(),
val javaConstants: List<JavaFile> = listOf(),
val kotlinDataTypes: List<FileSpec> = listOf(),
val kotlinInputTypes: List<FileSpec> = listOf(),
val kotlinInterfaces: List<FileSpec> = listOf(),
val kotlinEnumTypes: List<FileSpec> = listOf(),
val kotlinDataFetchers: List<FileSpec> = listOf(),
val kotlinConstants: List<FileSpec> = emptyList()
val kotlinConstants: List<FileSpec> = listOf(),
val kotlinClientTypes: List<FileSpec> = listOf(),
) {
fun merge(current: CodeGenResult): CodeGenResult {
val javaDataTypes = this.javaDataTypes.plus(current.javaDataTypes)
Expand All @@ -424,10 +512,12 @@ data class CodeGenResult(
val clientProjections = this.clientProjections.plus(current.clientProjections)
val javaConstants = this.javaConstants.plus(current.javaConstants)
val kotlinDataTypes = this.kotlinDataTypes.plus(current.kotlinDataTypes)
val kotlinInputTypes = this.kotlinInputTypes.plus(current.kotlinInputTypes)
val kotlinInterfaces = this.kotlinInterfaces.plus(current.kotlinInterfaces)
val kotlinEnumTypes = this.kotlinEnumTypes.plus(current.kotlinEnumTypes)
val kotlinDataFetchers = this.kotlinDataFetchers.plus(current.kotlinDataFetchers)
val kotlinConstants = this.kotlinConstants.plus(current.kotlinConstants)
val kotlinClientTypes = this.kotlinClientTypes.plus(current.kotlinClientTypes)

return CodeGenResult(
javaDataTypes = javaDataTypes,
Expand All @@ -438,32 +528,32 @@ data class CodeGenResult(
clientProjections = clientProjections,
javaConstants = javaConstants,
kotlinDataTypes = kotlinDataTypes,
kotlinInputTypes = kotlinInputTypes,
kotlinInterfaces = kotlinInterfaces,
kotlinEnumTypes = kotlinEnumTypes,
kotlinDataFetchers = kotlinDataFetchers,
kotlinConstants = kotlinConstants
kotlinConstants = kotlinConstants,
kotlinClientTypes = kotlinClientTypes,
)
}

fun javaSources(): List<JavaFile> {
return javaDataTypes
.asSequence()
.plus(javaInterfaces)
.plus(javaEnumTypes)
.plus(javaDataFetchers)
.plus(javaQueryTypes)
.plus(clientProjections)
.plus(javaConstants)
.toList()
}

fun kotlinSources(): List<FileSpec> {
return kotlinDataTypes
.asSequence()
.plus(kotlinInputTypes)
.plus(kotlinInterfaces)
.plus(kotlinEnumTypes)
.plus(kotlinConstants)
.toList()
.plus(kotlinClientTypes)
}
}

Expand Down Expand Up @@ -498,21 +588,10 @@ fun List<FieldDefinition>.filterIncludedInConfig(definitionName: String, config:
}
}

fun ObjectTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)

fun InputObjectTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)

fun InterfaceTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)

fun UnionTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)

fun EnumTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)

private fun <T : DirectivesContainer<*>> shouldSkip(
typeDefinition: DirectivesContainer<T>,
fun <T : DirectivesContainer<*>> DirectivesContainer<T>.shouldSkip(
config: CodeGenConfig
): Boolean {
return typeDefinition.directives.any { it.name == "skipcodegen" } || config.typeMapping.containsKey((typeDefinition as NamedNode<*>).name)
return directives.any { it.name == "skipcodegen" } || config.typeMapping.containsKey((this as NamedNode<*>).name)
}

fun TypeDefinition<*>.fieldDefinitions(): List<FieldDefinition> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,7 +309,7 @@ abstract class BaseDataTypeGenerator(internal val packageName: String, config: C

private fun addFieldWithGetterAndSetter(returnType: com.squareup.javapoet.TypeName?, fieldDefinition: Field, javaType: TypeSpec.Builder) {
val fieldBuilder = if (fieldDefinition.initialValue != null) {
FieldSpec.builder(fieldDefinition.type, fieldDefinition.name).addModifiers(Modifier.PRIVATE).initializer(fieldDefinition.initialValue)
FieldSpec.builder(fieldDefinition.type, ReservedKeywordSanitizer.sanitize(fieldDefinition.name)).addModifiers(Modifier.PRIVATE).initializer(fieldDefinition.initialValue)
} else {
FieldSpec.builder(returnType, ReservedKeywordSanitizer.sanitize(fieldDefinition.name)).addModifiers(Modifier.PRIVATE)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class InterfaceGenerator(private val config: CodeGenConfig, private val document
if (config.generateInterfaceSetters) {
val setterBuilder = MethodSpec.methodBuilder("set${fieldName.capitalized()}")
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
.addParameter(returnType, fieldName)
.addParameter(returnType, ReservedKeywordSanitizer.sanitize(fieldName))

if (fieldDefinition.description != null) {
setterBuilder.addJavadoc(fieldDefinition.description.content.lines().joinToString("\n"))
Expand Down
Loading

0 comments on commit 10690cc

Please sign in to comment.