Skip to content

Commit 10690cc

Browse files
authored
Merge branch 'Netflix:master' into feat/kotlin-interfaces
2 parents bddf915 + 692b97c commit 10690cc

File tree

381 files changed

+9910
-49
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

381 files changed

+9910
-49
lines changed
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
*
3+
* Copyright 2020 Netflix, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*
17+
*/
18+
19+
package com.netflix.graphql.dgs.client.codegen
20+
21+
@DslMarker
22+
annotation class QueryProjectionMarker
23+
24+
@QueryProjectionMarker
25+
abstract class GraphQLProjection(defaultFields: Set<String> = setOf("__typename")) : GraphQLInput() {
26+
27+
private val builder = StringBuilder("{ ${defaultFields.joinToString(" ")} ")
28+
29+
protected fun field(field: String) {
30+
builder.append("$field ")
31+
}
32+
33+
protected fun <T : GraphQLProjection> project(field: String, projection: T, projectionFields: T.() -> T) {
34+
builder.append("$field ")
35+
projectionFields.invoke(projection)
36+
builder.append(projection.asQuery())
37+
}
38+
39+
fun asQuery() = "$builder}"
40+
}
41+
42+
abstract class GraphQLInput {
43+
44+
companion object {
45+
46+
private val inputSerializer = InputValueSerializer()
47+
48+
protected fun inputToString(value: Any?): String {
49+
// TODO escape newlines in InputValueSerializer
50+
return inputSerializer.serialize(value).replace("\n", "\\n")
51+
}
52+
53+
val defaults: ThreadLocal<MutableSet<String>> = ThreadLocal.withInitial { mutableSetOf() }
54+
55+
@JvmStatic
56+
protected fun <T> default(arg: String): T? {
57+
defaults.get().add(arg)
58+
return null
59+
}
60+
}
61+
62+
private val _defaults = defaults.get()
63+
64+
init {
65+
defaults.set(mutableSetOf())
66+
}
67+
68+
protected fun formatArgs(vararg args: Pair<String, Any?>): String {
69+
return args
70+
.filter { (k, _) -> !_defaults.contains(k) }
71+
.joinToString(", ") { (k, v) -> "$k: ${inputToString(v)}" }
72+
}
73+
}

graphql-dgs-codegen-core/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
implementation(project(":graphql-dgs-codegen-client-core"))
3030
implementation 'com.graphql-java:graphql-java'
3131
implementation 'com.fasterxml.jackson.core:jackson-annotations'
32+
implementation 'com.fasterxml.jackson.core:jackson-databind'
3233
implementation 'org.slf4j:slf4j-api'
3334

3435
implementation 'com.squareup:javapoet:1.13.+'

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

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

2121
import com.netflix.graphql.dgs.codegen.generators.java.*
2222
import com.netflix.graphql.dgs.codegen.generators.kotlin.*
23+
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2ClientTypes
24+
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2DataTypes
25+
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2EnumTypes
26+
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2InputTypes
27+
import com.netflix.graphql.dgs.codegen.generators.kotlin2.generateKotlin2Interfaces
2328
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findEnumExtensions
2429
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInputExtensions
2530
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findInterfaceExtensions
@@ -32,6 +37,7 @@ import graphql.language.*
3237
import graphql.parser.MultiSourceReader
3338
import graphql.parser.Parser
3439
import graphql.parser.ParserOptions
40+
import graphql.schema.idl.TypeUtil
3541
import java.io.File
3642
import java.nio.file.Path
3743
import java.nio.file.Paths
@@ -73,10 +79,12 @@ class CodeGen(private val config: CodeGenConfig) {
7379
}
7480
codeGenResult.javaConstants.forEach { it.writeTo(config.outputDir) }
7581
codeGenResult.kotlinDataTypes.forEach { it.writeTo(config.outputDir) }
82+
codeGenResult.kotlinInputTypes.forEach { it.writeTo(config.outputDir) }
7683
codeGenResult.kotlinInterfaces.forEach { it.writeTo(config.outputDir) }
7784
codeGenResult.kotlinEnumTypes.forEach { it.writeTo(config.outputDir) }
7885
codeGenResult.kotlinDataFetchers.forEach { it.writeTo(config.examplesOutputDir) }
7986
codeGenResult.kotlinConstants.forEach { it.writeTo(config.outputDir) }
87+
codeGenResult.kotlinClientTypes.forEach { it.writeTo(config.outputDir) }
8088
}
8189

8290
return codeGenResult
@@ -105,9 +113,50 @@ class CodeGen(private val config: CodeGenConfig) {
105113
readerBuilder.string(schema, null)
106114
}
107115

108-
return readerBuilder.build().use { reader ->
116+
val document = readerBuilder.build().use { reader ->
109117
parser.parseDocument(reader, options)
110118
}
119+
120+
return document.transform {
121+
122+
// for kotlin2, add implicit types like PageInfo to the schema so classes are generated
123+
if (config.generateKotlinNullableClasses || config.generateKotlinClosureProjections) {
124+
val objectTypeDefs = document.getDefinitionsOfType(ObjectTypeDefinition::class.java)
125+
if (!objectTypeDefs.any { def -> def.name == "PageInfo" } &&
126+
objectTypeDefs.any { def -> def.fieldDefinitions.any { field -> TypeUtil.unwrapAll(field.type).name == "PageInfo" } }
127+
) {
128+
it.definition(
129+
ObjectTypeDefinition.newObjectTypeDefinition()
130+
.name("PageInfo")
131+
.fieldDefinition(
132+
FieldDefinition.newFieldDefinition()
133+
.name("hasNextPage")
134+
.type(NonNullType(TypeName("Boolean")))
135+
.build()
136+
)
137+
.fieldDefinition(
138+
FieldDefinition.newFieldDefinition()
139+
.name("hasPreviousPage")
140+
.type(NonNullType(TypeName("Boolean")))
141+
.build()
142+
)
143+
.fieldDefinition(
144+
FieldDefinition.newFieldDefinition()
145+
.name("startCursor")
146+
.type(TypeName("String"))
147+
.build()
148+
)
149+
.fieldDefinition(
150+
FieldDefinition.newFieldDefinition()
151+
.name("endCursor")
152+
.type(TypeName("String"))
153+
.build()
154+
)
155+
.build()
156+
)
157+
}
158+
}
159+
}
111160
}
112161

113162
private fun generateJava(): CodeGenResult {
@@ -239,37 +288,72 @@ class CodeGen(private val config: CodeGenConfig) {
239288
private fun generateKotlin(): CodeGenResult {
240289
val definitions = document.definitions
241290

242-
val datatypesResult = generateKotlinDataTypes(definitions)
243-
val inputTypes = generateKotlinInputTypes(definitions)
244-
val interfacesResult = generateKotlinInterfaceTypes(definitions)
291+
val requiredTypeCollector = RequiredTypeCollector(
292+
document = document,
293+
queries = config.includeQueries,
294+
mutations = config.includeMutations,
295+
subscriptions = config.includeSubscriptions,
296+
)
297+
val requiredTypes = requiredTypeCollector.requiredTypes
245298

246-
val unionResult = definitions.asSequence()
247-
.filterIsInstance<UnionTypeDefinition>()
248-
.excludeSchemaTypeExtension()
249-
.map {
250-
val extensions = findUnionExtensions(it.name, definitions)
251-
KotlinUnionTypeGenerator(config).generate(it, extensions)
252-
}
253-
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
299+
val dataTypes = if (config.generateKotlinNullableClasses) {
254300

255-
val enumsResult = definitions.asSequence()
256-
.filterIsInstance<EnumTypeDefinition>()
257-
.excludeSchemaTypeExtension()
258-
.filter { config.generateDataTypes || it.name in requiredTypeCollector.requiredTypes }
259-
.map {
260-
val extensions = findEnumExtensions(it.name, definitions)
261-
KotlinEnumTypeGenerator(config).generate(it, extensions)
262-
}
263-
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
301+
CodeGenResult(
302+
kotlinDataTypes = generateKotlin2DataTypes(config, document, requiredTypes),
303+
kotlinInputTypes = generateKotlin2InputTypes(config, document, requiredTypes),
304+
kotlinInterfaces = generateKotlin2Interfaces(config, document),
305+
kotlinEnumTypes = generateKotlin2EnumTypes(config, document, requiredTypes),
306+
kotlinConstants = KotlinConstantsGenerator(config, document).generate().kotlinConstants,
307+
)
308+
} else {
264309

265-
val constantsClass = KotlinConstantsGenerator(config, document).generate()
310+
val datatypesResult = generateKotlinDataTypes(definitions)
311+
val inputTypes = generateKotlinInputTypes(definitions)
312+
val interfacesResult = generateKotlinInterfaceTypes(definitions)
266313

267-
val client = generateJavaClientApi(definitions)
268-
val entitiesClient = generateJavaClientEntitiesApi(definitions)
269-
val entitiesRepresentationsTypes = generateKotlinClientEntitiesRepresentations(definitions)
314+
val unionResult = definitions.asSequence()
315+
.filterIsInstance<UnionTypeDefinition>()
316+
.excludeSchemaTypeExtension()
317+
.map {
318+
val extensions = findUnionExtensions(it.name, definitions)
319+
KotlinUnionTypeGenerator(config).generate(it, extensions)
320+
}
321+
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
322+
323+
val enumsResult = definitions.asSequence()
324+
.filterIsInstance<EnumTypeDefinition>()
325+
.excludeSchemaTypeExtension()
326+
.filter { config.generateDataTypes || it.name in requiredTypeCollector.requiredTypes }
327+
.map {
328+
val extensions = findEnumExtensions(it.name, definitions)
329+
KotlinEnumTypeGenerator(config).generate(it, extensions)
330+
}
331+
.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
332+
333+
val constantsClass = KotlinConstantsGenerator(config, document).generate()
334+
335+
datatypesResult
336+
.merge(inputTypes)
337+
.merge(interfacesResult)
338+
.merge(unionResult)
339+
.merge(enumsResult)
340+
.merge(constantsClass)
341+
}
270342

271-
return datatypesResult.merge(inputTypes).merge(interfacesResult).merge(unionResult).merge(enumsResult)
272-
.merge(client).merge(entitiesClient).merge(entitiesRepresentationsTypes).merge(constantsClass)
343+
val clientTypes = if (config.generateKotlinClosureProjections) {
344+
CodeGenResult(
345+
kotlinClientTypes = generateKotlin2ClientTypes(config, document),
346+
)
347+
} else {
348+
349+
val client = generateJavaClientApi(definitions)
350+
val entitiesClient = generateJavaClientEntitiesApi(definitions)
351+
val entitiesRepresentationsTypes = generateKotlinClientEntitiesRepresentations(definitions)
352+
353+
client.merge(entitiesClient).merge(entitiesRepresentationsTypes)
354+
}
355+
356+
return dataTypes.merge(clientTypes)
273357
}
274358

275359
private fun generateKotlinClientEntitiesRepresentations(definitions: Collection<Definition<*>>): CodeGenResult {
@@ -354,6 +438,8 @@ data class CodeGenConfig(
354438
val generateBoxedTypes: Boolean = false,
355439
val generateClientApi: Boolean = false,
356440
val generateInterfaces: Boolean = false,
441+
val generateKotlinNullableClasses: Boolean = false,
442+
val generateKotlinClosureProjections: Boolean = false,
357443
val typeMapping: Map<String, String> = emptyMap(),
358444
val includeQueries: Set<String> = emptySet(),
359445
val includeMutations: Set<String> = emptySet(),
@@ -398,7 +484,7 @@ data class CodeGenConfig(
398484

399485
enum class Language {
400486
JAVA,
401-
KOTLIN
487+
KOTLIN,
402488
}
403489

404490
data class CodeGenResult(
@@ -410,10 +496,12 @@ data class CodeGenResult(
410496
val clientProjections: List<JavaFile> = listOf(),
411497
val javaConstants: List<JavaFile> = listOf(),
412498
val kotlinDataTypes: List<FileSpec> = listOf(),
499+
val kotlinInputTypes: List<FileSpec> = listOf(),
413500
val kotlinInterfaces: List<FileSpec> = listOf(),
414501
val kotlinEnumTypes: List<FileSpec> = listOf(),
415502
val kotlinDataFetchers: List<FileSpec> = listOf(),
416-
val kotlinConstants: List<FileSpec> = emptyList()
503+
val kotlinConstants: List<FileSpec> = listOf(),
504+
val kotlinClientTypes: List<FileSpec> = listOf(),
417505
) {
418506
fun merge(current: CodeGenResult): CodeGenResult {
419507
val javaDataTypes = this.javaDataTypes.plus(current.javaDataTypes)
@@ -424,10 +512,12 @@ data class CodeGenResult(
424512
val clientProjections = this.clientProjections.plus(current.clientProjections)
425513
val javaConstants = this.javaConstants.plus(current.javaConstants)
426514
val kotlinDataTypes = this.kotlinDataTypes.plus(current.kotlinDataTypes)
515+
val kotlinInputTypes = this.kotlinInputTypes.plus(current.kotlinInputTypes)
427516
val kotlinInterfaces = this.kotlinInterfaces.plus(current.kotlinInterfaces)
428517
val kotlinEnumTypes = this.kotlinEnumTypes.plus(current.kotlinEnumTypes)
429518
val kotlinDataFetchers = this.kotlinDataFetchers.plus(current.kotlinDataFetchers)
430519
val kotlinConstants = this.kotlinConstants.plus(current.kotlinConstants)
520+
val kotlinClientTypes = this.kotlinClientTypes.plus(current.kotlinClientTypes)
431521

432522
return CodeGenResult(
433523
javaDataTypes = javaDataTypes,
@@ -438,32 +528,32 @@ data class CodeGenResult(
438528
clientProjections = clientProjections,
439529
javaConstants = javaConstants,
440530
kotlinDataTypes = kotlinDataTypes,
531+
kotlinInputTypes = kotlinInputTypes,
441532
kotlinInterfaces = kotlinInterfaces,
442533
kotlinEnumTypes = kotlinEnumTypes,
443534
kotlinDataFetchers = kotlinDataFetchers,
444-
kotlinConstants = kotlinConstants
535+
kotlinConstants = kotlinConstants,
536+
kotlinClientTypes = kotlinClientTypes,
445537
)
446538
}
447539

448540
fun javaSources(): List<JavaFile> {
449541
return javaDataTypes
450-
.asSequence()
451542
.plus(javaInterfaces)
452543
.plus(javaEnumTypes)
453544
.plus(javaDataFetchers)
454545
.plus(javaQueryTypes)
455546
.plus(clientProjections)
456547
.plus(javaConstants)
457-
.toList()
458548
}
459549

460550
fun kotlinSources(): List<FileSpec> {
461551
return kotlinDataTypes
462-
.asSequence()
552+
.plus(kotlinInputTypes)
463553
.plus(kotlinInterfaces)
464554
.plus(kotlinEnumTypes)
465555
.plus(kotlinConstants)
466-
.toList()
556+
.plus(kotlinClientTypes)
467557
}
468558
}
469559

@@ -498,21 +588,10 @@ fun List<FieldDefinition>.filterIncludedInConfig(definitionName: String, config:
498588
}
499589
}
500590

501-
fun ObjectTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)
502-
503-
fun InputObjectTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)
504-
505-
fun InterfaceTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)
506-
507-
fun UnionTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)
508-
509-
fun EnumTypeDefinition.shouldSkip(config: CodeGenConfig): Boolean = shouldSkip(this, config)
510-
511-
private fun <T : DirectivesContainer<*>> shouldSkip(
512-
typeDefinition: DirectivesContainer<T>,
591+
fun <T : DirectivesContainer<*>> DirectivesContainer<T>.shouldSkip(
513592
config: CodeGenConfig
514593
): Boolean {
515-
return typeDefinition.directives.any { it.name == "skipcodegen" } || config.typeMapping.containsKey((typeDefinition as NamedNode<*>).name)
594+
return directives.any { it.name == "skipcodegen" } || config.typeMapping.containsKey((this as NamedNode<*>).name)
516595
}
517596

518597
fun TypeDefinition<*>.fieldDefinitions(): List<FieldDefinition> {

graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/DataTypeGenerator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -309,7 +309,7 @@ abstract class BaseDataTypeGenerator(internal val packageName: String, config: C
309309

310310
private fun addFieldWithGetterAndSetter(returnType: com.squareup.javapoet.TypeName?, fieldDefinition: Field, javaType: TypeSpec.Builder) {
311311
val fieldBuilder = if (fieldDefinition.initialValue != null) {
312-
FieldSpec.builder(fieldDefinition.type, fieldDefinition.name).addModifiers(Modifier.PRIVATE).initializer(fieldDefinition.initialValue)
312+
FieldSpec.builder(fieldDefinition.type, ReservedKeywordSanitizer.sanitize(fieldDefinition.name)).addModifiers(Modifier.PRIVATE).initializer(fieldDefinition.initialValue)
313313
} else {
314314
FieldSpec.builder(returnType, ReservedKeywordSanitizer.sanitize(fieldDefinition.name)).addModifiers(Modifier.PRIVATE)
315315
}

graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class InterfaceGenerator(private val config: CodeGenConfig, private val document
125125
if (config.generateInterfaceSetters) {
126126
val setterBuilder = MethodSpec.methodBuilder("set${fieldName.capitalized()}")
127127
.addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
128-
.addParameter(returnType, fieldName)
128+
.addParameter(returnType, ReservedKeywordSanitizer.sanitize(fieldName))
129129

130130
if (fieldDefinition.description != null) {
131131
setterBuilder.addJavadoc(fieldDefinition.description.content.lines().joinToString("\n"))

0 commit comments

Comments
 (0)