Skip to content

Commit dddd6a8

Browse files
authored
Merge pull request #891 from Netflix/fix/duplicate-projections-and-interfaces
Deduplicate projection methods and superinterfaces, fix Representation type gen
2 parents c8d65d2 + 2bef805 commit dddd6a8

File tree

7 files changed

+80
-19
lines changed

7 files changed

+80
-19
lines changed

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

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ class CodeGen(
191191
* the schema information for the parser.
192192
*/
193193
private fun loadSchemaReaders(vararg readerBuilders: MultiSourceReader.Builder) {
194+
validateNoOverlappingPaths(config.schemaFiles)
195+
194196
readerBuilders.forEach { rb ->
195197
val schemaFiles =
196198
config.schemaFiles
@@ -209,6 +211,28 @@ class CodeGen(
209211
}
210212
}
211213

214+
/**
215+
* Validates that there are no overlapping paths in the schema files to prevent duplicate loading.
216+
* Throws an [IllegalArgumentException] if any path is an ancestor of another.
217+
*/
218+
private fun validateNoOverlappingPaths(schemaFiles: Set<File>) {
219+
val canonicalPaths = schemaFiles.map { it.canonicalFile }
220+
221+
for (i in canonicalPaths.indices) {
222+
for (j in i + 1 until canonicalPaths.size) {
223+
val path1 = canonicalPaths[i]
224+
val path2 = canonicalPaths[j]
225+
226+
if (path1.startsWith(path2) || path2.startsWith(path1)) {
227+
val (parent, child) = if (path1.startsWith(path2)) path2 to path1 else path1 to path2
228+
logger.warn(
229+
"Schema path '$child' is a descendant of '$parent'. Remove one of these paths from your configuration to avoid loading duplicate schema files.",
230+
)
231+
}
232+
}
233+
}
234+
}
235+
212236
private fun generateJava(): CodeGenResult {
213237
val definitions = document.definitions
214238
// data types

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -681,9 +681,11 @@ class ClientApiGenerator(
681681
): CodeGenResult =
682682
if (type is InterfaceTypeDefinition) {
683683
val concreteTypes =
684-
document.getDefinitionsOfType(ObjectTypeDefinition::class.java).filter {
685-
it.implements.filterIsInstance<NamedNode<*>>().any { iface -> iface.name == type.name }
686-
}
684+
document
685+
.getDefinitionsOfType(ObjectTypeDefinition::class.java)
686+
.filter {
687+
it.implements.filterIsInstance<NamedNode<*>>().any { iface -> iface.name == type.name }
688+
}.distinctBy { it.name }
687689
concreteTypes
688690
.map {
689691
addFragmentProjectionMethod(javaType, root, it, processedEdges, queryDepth)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class DataTypeGenerator(
9292
.map { it as TypeName }
9393
.any { it.name == name }
9494
}.map { it.name }
95+
.distinct()
9596
.toList()
9697

9798
var implements =

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,7 @@ class EntitiesRepresentationTypeGenerator(
7777
)
7878
) {
7979
val fieldTypeRepresentationName = toRepresentationName(type)
80-
val fieldRepresentationType =
81-
typeUtils
82-
.findReturnType(it.type)
83-
.toString()
84-
.replace(type.name, fieldTypeRepresentationName)
80+
val fieldRepresentationType = typeUtils.qualifyName(fieldTypeRepresentationName)
8581

8682
if (generatedRepresentations.containsKey(fieldTypeRepresentationName)) {
8783
logger.trace("Representation for {} was already generated.", fieldTypeRepresentationName)

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -266,12 +266,14 @@ abstract class AbstractKotlinDataTypeGenerator(
266266
}
267267

268268
val unionTypes =
269-
document.getDefinitionsOfType(UnionTypeDefinition::class.java).filter { union ->
270-
union.memberTypes
271-
.asSequence()
272-
.map { it as TypeName }
273-
.any { it.name == name }
274-
}
269+
document
270+
.getDefinitionsOfType(UnionTypeDefinition::class.java)
271+
.filter { union ->
272+
union.memberTypes
273+
.asSequence()
274+
.map { it as TypeName }
275+
.any { it.name == name }
276+
}.distinctBy { it.name }
275277

276278
val interfaceTypes = interfaces + unionTypes
277279
interfaceTypes.forEach {

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

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,7 @@ class KotlinEntitiesRepresentationTypeGenerator(
8080
) {
8181
val fieldType = typeUtils.findReturnType(it.type)
8282
val fieldTypeRepresentationName = toRepresentationName(type)
83-
val fieldRepresentationType =
84-
fieldType
85-
.toString()
86-
.replace(type.name, fieldTypeRepresentationName)
87-
.removeSuffix("?")
83+
val fieldRepresentationType = typeUtils.qualifyName(fieldTypeRepresentationName)
8884

8985
if (generatedRepresentations.containsKey(fieldTypeRepresentationName)) {
9086
logger.trace("Representation for {} was already generated.", fieldTypeRepresentationName)

graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/EntitiesClientApiGenTest.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,46 @@ class EntitiesClientApiGenTest {
583583
}
584584
}
585585

586+
@Test
587+
fun `Entity representation types should use client package not type-mapped package`() {
588+
val schema =
589+
"""
590+
type Channel @key(fields: "id type") {
591+
id: ID! @external
592+
type: ChatType @external
593+
name: String
594+
}
595+
596+
enum ChatType {
597+
PUBLIC
598+
PRIVATE
599+
DM
600+
}
601+
""".trimIndent()
602+
603+
val codeGenResult =
604+
CodeGen(
605+
CodeGenConfig(
606+
schemas = setOf(schema),
607+
packageName = BASE_PACKAGE_NAME,
608+
generateClientApi = true,
609+
generateDataTypes = false,
610+
typeMapping = mapOf("ChatType" to "com.external.types.ChatType"),
611+
),
612+
).generate()
613+
614+
val representations = codeGenResult.javaDataTypes.filter { "Representation" in it.typeSpec.name }
615+
assertThat(representations).hasSize(2)
616+
617+
val channelRepresentation = representations.find { it.typeSpec.name == "ChannelRepresentation" }
618+
assertThat(channelRepresentation).isNotNull
619+
620+
val typeField = channelRepresentation!!.typeSpec.fieldSpecs.find { it.name == "type" }
621+
assertThat(typeField).isNotNull
622+
assertThat(typeField!!.type.toString())
623+
.isEqualTo("$BASE_PACKAGE_NAME.client.ChatTypeRepresentation")
624+
}
625+
586626
companion object {
587627
fun codeGen(schema: String): CodeGenResult =
588628
CodeGen(

0 commit comments

Comments
 (0)