Skip to content

Commit 2f225c8

Browse files
committed
Align Kotlin Entity Client Generation with what is supported for Java
This PR aligns, for Kotlin, what we already support for Java Entity Client generation. As part of this PR we are introducing the EntitiesRepresentationTypeGeneratorUtils.kt as a mechanism to share code between the Java and Kotlin Entity Client generators.
1 parent 9263ef4 commit 2f225c8

File tree

6 files changed

+223
-184
lines changed

6 files changed

+223
-184
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
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.codegen.generators
20+
21+
import com.netflix.graphql.dgs.codegen.CodeGenConfig
22+
import com.netflix.graphql.dgs.codegen.CodeGenResult
23+
import graphql.language.*
24+
25+
object EntitiesRepresentationTypeGeneratorUtils {
26+
27+
fun interface RepresentationGenerator {
28+
fun generate(
29+
definitionName: String,
30+
representationName: String,
31+
fields: List<FieldDefinition>,
32+
generatedRepresentations: MutableMap<String, Any>,
33+
keyFields: Map<String, Any>
34+
): CodeGenResult
35+
}
36+
37+
fun generate(
38+
config: CodeGenConfig,
39+
definition: ObjectTypeDefinition,
40+
generatedRepresentations: MutableMap<String, Any>,
41+
representationGenerator: RepresentationGenerator
42+
): CodeGenResult {
43+
if (config.skipEntityQueries) {
44+
return CodeGenResult()
45+
}
46+
val representationName = toRepresentationName(definition)
47+
if (representationName in generatedRepresentations) {
48+
return CodeGenResult()
49+
}
50+
51+
val directiveArg =
52+
definition
53+
.getDirectives("key")
54+
.map { it.argumentsByName["fields"]?.value as StringValue }
55+
.map { it.value }
56+
57+
val keyFields = parseKeyDirectiveValue(directiveArg)
58+
59+
return representationGenerator.generate(
60+
definition.name,
61+
representationName,
62+
definition.fieldDefinitions,
63+
generatedRepresentations,
64+
keyFields
65+
)
66+
}
67+
68+
fun findType(typeName: Type<*>, document: Document): TypeDefinition<*>? {
69+
return when (typeName) {
70+
is NonNullType -> {
71+
findType(typeName.type, document)
72+
}
73+
is ListType -> {
74+
findType(typeName.type, document)
75+
}
76+
else -> document.definitions.filterIsInstance<TypeDefinition<*>>()
77+
.find { it.name == (typeName as TypeName).name }
78+
}
79+
}
80+
81+
fun parseKeyDirectiveValue(keyDirective: List<String>): Map<String, Any> {
82+
data class Node(val key: String, val map: MutableMap<String, Any>, val parent: Node?)
83+
84+
val keys = keyDirective.map { ds ->
85+
ds.map { if (it == '{' || it == '}') " $it " else "$it" }
86+
.joinToString("", "", "")
87+
.split(" ")
88+
}.flatten()
89+
90+
// handle simple keys and nested keys by constructing the path to each key
91+
// e.g. type Movie @key(fields: "movieId") or type MovieCast @key(fields: movie { movieId } actors { name } }
92+
val mappedKeyTypes = mutableMapOf<String, Any>()
93+
var parent = Node("", mappedKeyTypes, null)
94+
var current = Node("", mappedKeyTypes, null)
95+
keys.filter { it != " " && it != "" }
96+
.forEach {
97+
when (it) {
98+
"{" -> {
99+
// push a new map for the next level
100+
val previous = parent
101+
parent = current
102+
current = Node("", current.map[current.key] as MutableMap<String, Any>, previous)
103+
}
104+
"}" -> {
105+
// pop back to parent level
106+
current = parent
107+
parent = parent.parent!!
108+
}
109+
else -> {
110+
// make an entry at the current level
111+
current.map.putIfAbsent(it, mutableMapOf<String, Any>())
112+
current = Node(it, current.map, parent)
113+
}
114+
}
115+
}
116+
return mappedKeyTypes
117+
}
118+
119+
fun toRepresentationName(definition: TypeDefinition<*>) = "${definition.name}Representation"
120+
}

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

Lines changed: 17 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -21,9 +21,16 @@ package com.netflix.graphql.dgs.codegen.generators.java
2121
import com.netflix.graphql.dgs.codegen.CodeGenConfig
2222
import com.netflix.graphql.dgs.codegen.CodeGenResult
2323
import com.netflix.graphql.dgs.codegen.fieldDefinitions
24+
import com.netflix.graphql.dgs.codegen.generators.EntitiesRepresentationTypeGeneratorUtils
25+
import com.netflix.graphql.dgs.codegen.generators.EntitiesRepresentationTypeGeneratorUtils.findType
26+
import com.netflix.graphql.dgs.codegen.generators.EntitiesRepresentationTypeGeneratorUtils.toRepresentationName
2427
import com.squareup.javapoet.ClassName
2528
import com.squareup.javapoet.CodeBlock
26-
import graphql.language.*
29+
import graphql.language.Document
30+
import graphql.language.EnumTypeDefinition
31+
import graphql.language.FieldDefinition
32+
import graphql.language.InterfaceTypeDefinition
33+
import graphql.language.ObjectTypeDefinition
2734
import org.slf4j.LoggerFactory
2835

2936
@Suppress("UNCHECKED_CAST")
@@ -32,27 +39,15 @@ class EntitiesRepresentationTypeGenerator(
3239
document: Document
3340
) : BaseDataTypeGenerator(config.packageNameClient, config, document) {
3441

35-
fun generate(definition: ObjectTypeDefinition, generatedRepresentations: MutableMap<String, Any>): CodeGenResult {
36-
if (config.skipEntityQueries) {
37-
return CodeGenResult()
38-
}
39-
val representationName = toRepresentationName(definition)
40-
if (generatedRepresentations.containsKey(representationName)) {
41-
return CodeGenResult()
42-
}
43-
val directiveArg =
44-
definition
45-
.getDirectives("key")
46-
.map { it.argumentsByName["fields"]?.value as StringValue }
47-
.map { it.value }
48-
49-
val keyFields = parseKeyDirectiveValue(directiveArg)
50-
return generateRepresentations(
51-
definition.name,
52-
representationName,
53-
definition.fieldDefinitions,
42+
fun generate(
43+
definition: ObjectTypeDefinition,
44+
generatedRepresentations: MutableMap<String, Any>
45+
): CodeGenResult {
46+
return EntitiesRepresentationTypeGeneratorUtils.generate(
47+
config,
48+
definition,
5449
generatedRepresentations,
55-
keyFields
50+
this::generateRepresentations
5651
)
5752
}
5853

@@ -74,7 +69,6 @@ class EntitiesRepresentationTypeGenerator(
7469
.filter { keyFields.containsKey(it.name) }
7570
.map {
7671
val type = findType(it.type, document)
77-
7872
if (type != null &&
7973
(
8074
type is ObjectTypeDefinition ||
@@ -105,8 +99,7 @@ class EntitiesRepresentationTypeGenerator(
10599
}
106100
Field(it.name, ClassName.get("", fieldRepresentationType))
107101
} else {
108-
val returnType = typeUtils.findReturnType(it.type)
109-
Field(it.name, returnType)
102+
Field(it.name, typeUtils.findReturnType(it.type))
110103
}
111104
}
112105
// Generate base type representation...
@@ -121,61 +114,8 @@ class EntitiesRepresentationTypeGenerator(
121114
return parentRepresentationCodeGen.merge(fieldsCodeGenAccumulator)
122115
}
123116

124-
private fun findType(typeName: Type<*>, document: Document): TypeDefinition<*>? {
125-
return when (typeName) {
126-
is NonNullType -> {
127-
findType(typeName.type, document)
128-
}
129-
is ListType -> {
130-
findType(typeName.type, document)
131-
}
132-
else -> document.definitions.filterIsInstance<TypeDefinition<*>>()
133-
.find { it.name == (typeName as TypeName).name }
134-
}
135-
}
136-
137-
private fun parseKeyDirectiveValue(keyDirective: List<String>): Map<String, Any> {
138-
data class Node(val key: String, val map: MutableMap<String, Any>, val parent: Node?)
139-
140-
val keys = keyDirective.map { ds ->
141-
ds.map { if (it == '{' || it == '}') " $it " else "$it" }
142-
.joinToString("", "", "")
143-
.split(" ")
144-
}.flatten()
145-
146-
// handle simple keys and nested keys by constructing the path to each key
147-
// e.g. type Movie @key(fields: "movieId") or type MovieCast @key(fields: movie { movieId } actors { name } }
148-
val mappedKeyTypes = mutableMapOf<String, Any>()
149-
var parent = Node("", mappedKeyTypes, null)
150-
var current = Node("", mappedKeyTypes, null)
151-
keys.filter { it != " " && it != "" }
152-
.forEach {
153-
when (it) {
154-
"{" -> {
155-
// push a new map for the next level
156-
val previous = parent
157-
parent = current
158-
current = Node("", current.map[current.key] as MutableMap<String, Any>, previous)
159-
}
160-
"}" -> {
161-
// pop back to parent level
162-
current = parent
163-
parent = parent.parent!!
164-
}
165-
else -> {
166-
// make an entry at the current level
167-
current.map.putIfAbsent(it, mutableMapOf<String, Any>())
168-
current = Node(it, current.map, parent)
169-
}
170-
}
171-
}
172-
return mappedKeyTypes
173-
}
174-
175117
companion object {
176118
private val logger: org.slf4j.Logger =
177119
LoggerFactory.getLogger(EntitiesRepresentationTypeGenerator::class.java)
178-
179-
private fun toRepresentationName(definition: TypeDefinition<*>) = "${definition.name}Representation"
180120
}
181121
}

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -68,10 +68,6 @@ class KotlinDataTypeGenerator(config: CodeGenConfig, document: Document) :
6868
val interfaces = definition.implements
6969
return generate(definition.name, fields, interfaces, document, definition.description)
7070
}
71-
72-
override fun getPackageName(): String {
73-
return config.packageNameTypes
74-
}
7571
}
7672

7773
class KotlinInputTypeGenerator(config: CodeGenConfig, document: Document) :
@@ -119,10 +115,6 @@ class KotlinInputTypeGenerator(config: CodeGenConfig, document: Document) :
119115
is ParameterizedTypeName -> typeArguments[0].className
120116
else -> TODO()
121117
}
122-
123-
override fun getPackageName(): String {
124-
return config.packageNameTypes
125-
}
126118
}
127119

128120
internal data class Field(
@@ -245,5 +237,7 @@ abstract class AbstractKotlinDataTypeGenerator(
245237
return CodeGenResult(kotlinDataTypes = listOf(fileSpec))
246238
}
247239

248-
abstract fun getPackageName(): String
240+
open fun getPackageName(): String {
241+
return config.packageNameTypes
242+
}
249243
}

0 commit comments

Comments
 (0)