From 641b9b6be3f0e0f783bb042c8f1b0084b8d6f84d Mon Sep 17 00:00:00 2001 From: Ryan Flegel Date: Thu, 27 Aug 2026 14:23:55 -0600 Subject: [PATCH] Generate list-of-interface getters on Java interfaces --- .../generators/java/InterfaceGenerator.kt | 54 +++++++------------ .../dgs/codegen/generators/java/TypeUtils.kt | 8 ++- .../graphql/dgs/codegen/CodeGenTest.kt | 38 ++++++++++--- 3 files changed, 55 insertions(+), 45 deletions(-) diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt index 954c54236..3a59eb130 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/InterfaceGenerator.kt @@ -78,28 +78,7 @@ class InterfaceGenerator( val mergedFieldDefinitions = definition.fieldDefinitions + extensions.flatMap { it.fieldDefinitions } mergedFieldDefinitions.filterSkipped().forEach { - // Generate getters/setters for fields that are not interfaces, and only getters for fields that are interfaces - // unless generateInterfaceMethodsForInterfaceFields && generateInterfaceSetters. - // Skip generating interface methods with list types where the inner type is an interface as Java does not - // support overriding them with more specific types (i.e. List does not override List). - // - // interface Pet { - // parent: Pet - // } - // type Dog implements Pet { - // parent: Dog - // } - // type Bird implements Pet { - // parent: Bird - // } - // For the schema above, we currently generate Dog::setParent(Dog dog), but the interface - // would have Pet::setParent(Pet pet) leading to missing overrides in the generated - // implementation classes. This is not an issue if the overridden field has the same base type, - // however. - // Ref: https://github.com/graphql/graphql-js/issues/776 - if (!isListOfInterface(it.type) || config.generateInterfaceMethodsForInterfaceFields) { - addInterfaceMethod(it, javaType) - } + addInterfaceMethod(it, javaType) } val implementations = @@ -128,19 +107,24 @@ class InterfaceGenerator( .getDefinitionsOfType(InterfaceTypeDefinition::class.java) .any { node -> node.name == typeUtils.findInnerType(fieldDefinition.type).name } - // Returns true if the field is a list type (possibly nested or non-null) with an innermost type that is an interface - private fun isListOfInterface(fieldType: Type<*>): Boolean = - when (fieldType) { - is ListType -> { - val innerType = typeUtils.findInnerType(fieldType) - document - .getDefinitionsOfType(InterfaceTypeDefinition::class.java) - .any { node -> node.name == innerType.name } - } - is NonNullType -> isListOfInterface(fieldType.type) - else -> false - } - + // Generate getters/setters for fields. Do not generate setters for fields that are also interfaces, unless forced + // with generateInterfaceMethodsForInterfaceFields. + // + // interface Pet { + // parent: Pet + // } + // type Dog implements Pet { + // parent: Dog + // } + // type Bird implements Pet { + // parent: Bird + // } + // + // For the schema above, we currently generate Dog::setParent(Dog dog), but the interface + // would have Pet::setParent(Pet pet) leading to missing overrides in the generated + // implementation classes. This is not an issue if the overridden field has the same base type, + // however. + // Ref: https://github.com/graphql/graphql-js/issues/776 private fun addInterfaceMethod( fieldDefinition: FieldDefinition, javaType: TypeSpec.Builder, diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/TypeUtils.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/TypeUtils.kt index 3cc63ec9a..3e8325247 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/TypeUtils.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/java/TypeUtils.kt @@ -94,7 +94,9 @@ class TypeUtils( var canUseWildcardType = false if (useWildcardType) { if (typeName is ClassName) { - if (document.definitions + if (isFieldTypeAnInterface(node.type)) { + canUseWildcardType = true + } else if (document.definitions .filterIsInstance() .any { e -> "I${e.name}" == typeName.simpleName() } || ( @@ -107,6 +109,8 @@ class TypeUtils( ) { canUseWildcardType = true } + } else if (typeName is ParameterizedTypeName && typeName.rawType().canonicalName() == "java.util.List") { + canUseWildcardType = true } } @@ -275,7 +279,7 @@ class TypeUtils( return NodeTraverser().postOrder(visitor, fieldType) as TypeName } - private fun isFieldTypeAnInterface(fieldDefinitionType: TypeName): Boolean = + private fun isFieldTypeAnInterface(fieldDefinitionType: Type<*>): Boolean = document .getDefinitionsOfType(InterfaceTypeDefinition::class.java) .any { node -> node.name == findInnerType(fieldDefinitionType).name } diff --git a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt index 81662fe37..fe88ec23a 100644 --- a/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt +++ b/graphql-dgs-codegen-core/src/test/kotlin/com/netflix/graphql/dgs/codegen/CodeGenTest.kt @@ -993,6 +993,12 @@ class CodeGenTest { | Pet getMother(); | | Pet getFather(); + | + | List getParents(); + | + | List> getFriends(); + | + | List getSiblings(); |} | """.trimMargin(), @@ -3393,6 +3399,7 @@ class CodeGenTest { |import com.netflix.graphql.dgs.codegen.tests.generated.Generated; |import java.lang.Integer; |import java.lang.String; + |import java.util.List; | |@Generated |@JsonTypeInfo( @@ -3414,6 +3421,8 @@ class CodeGenTest { | void setAge(Integer age); | | Employee getBoss(); + | + | List getTeam(); |} | """.trimMargin(), @@ -3480,9 +3489,9 @@ class CodeGenTest { | | void setAge(Integer age); | - | List getParents(); + | List getParents(); | - | void setParents(List parents); + | void setParents(List parents); | | Person getFriend(); | @@ -3520,9 +3529,9 @@ class CodeGenTest { | | void setAge(Integer age); | - | List getParents(); + | List getParents(); | - | void setParents(List parents); + | void setParents(List parents); | | Person getFriend(); | @@ -3993,14 +4002,27 @@ class CodeGenTest { | | void setName(String name); | - | List getFriends(); + | List getFriends(); | - | void setFriends(List friends); + | void setFriends(List friends); |} | """.trimMargin(), ) + assertThat(dataTypes[0].toString()).contains( + """ + | public List getFriends() { + | return friends; + | } + | + | public void setFriends(List friends) { + | this.friends = friends; + | } + | + """.trimMargin(), + ) + assertCompilesJava(codeGenResult) } @@ -4059,7 +4081,7 @@ class CodeGenTest { | | Integer getAge(); | - | List getParents(); + | List getParents(); | | Person getFriend(); |} @@ -4091,7 +4113,7 @@ class CodeGenTest { | | Integer getAge(); | - | List getParents(); + | List getParents(); | | Person getFriend(); |}