Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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<Dog> does not override List<Pet>).
//
// 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 =
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,9 @@ class TypeUtils(
var canUseWildcardType = false
if (useWildcardType) {
if (typeName is ClassName) {
if (document.definitions
if (isFieldTypeAnInterface(node.type)) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use wildcards for List of interface.

canUseWildcardType = true
} else if (document.definitions
.filterIsInstance<ObjectTypeDefinition>()
.any { e -> "I${e.name}" == typeName.simpleName() } ||
(
Expand All @@ -107,6 +109,8 @@ class TypeUtils(
) {
canUseWildcardType = true
}
} else if (typeName is ParameterizedTypeName && typeName.rawType().canonicalName() == "java.util.List") {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also use wildcard for List of List (of List...).

This ensure List<List<Interface>> still works and becomes List<? extends List<? extends Interface>>.

It will also unnecessarily do it for concrete types, which should be harmless. e.g., List<List<ConcreteType>> becomes List<? extends List<ConcreteType>>.

canUseWildcardType = true
}
}

Expand Down Expand Up @@ -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 }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,12 @@ class CodeGenTest {
| Pet getMother();
|
| Pet getFather();
|
| List<? extends Pet> getParents();
|
| List<? extends List<? extends Pet>> getFriends();
|
| List<? extends Pet> getSiblings();
|}
|
""".trimMargin(),
Expand Down Expand Up @@ -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(
Expand All @@ -3414,6 +3421,8 @@ class CodeGenTest {
| void setAge(Integer age);
|
| Employee getBoss();
|
| List<? extends Employee> getTeam();
|}
|
""".trimMargin(),
Expand Down Expand Up @@ -3480,9 +3489,9 @@ class CodeGenTest {
|
| void setAge(Integer age);
|
| List<Person> getParents();
| List<? extends Person> getParents();
|
| void setParents(List<Person> parents);
| void setParents(List<? extends Person> parents);
|
| Person getFriend();
|
Expand Down Expand Up @@ -3520,9 +3529,9 @@ class CodeGenTest {
|
| void setAge(Integer age);
|
| List<Person> getParents();
| List<? extends Person> getParents();
|
| void setParents(List<Person> parents);
| void setParents(List<? extends Person> parents);
|
| Person getFriend();
|
Expand Down Expand Up @@ -3993,14 +4002,27 @@ class CodeGenTest {
|
| void setName(String name);
|
| List<Pet> getFriends();
| List<? extends Pet> getFriends();
|
| void setFriends(List<Pet> friends);
| void setFriends(List<? extends Pet> friends);

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This demonstrates how interface-setters also have wildcards. This is more flexible for the Java compiler, but may theoretically lead to runtime errors. e.g., if a concrete type only supported a specific subtype of Pet.

Note that this is consistent with the existing generateInterfaces behaviour, but I wanted to point it out.

|}
|
""".trimMargin(),
)

assertThat(dataTypes[0].toString()).contains(
"""
| public List<? extends Pet> getFriends() {
| return friends;
| }
|
| public void setFriends(List<? extends Pet> friends) {
| this.friends = friends;
| }
|
""".trimMargin(),
)

assertCompilesJava(codeGenResult)
}

Expand Down Expand Up @@ -4059,7 +4081,7 @@ class CodeGenTest {
|
| Integer getAge();
|
| List<Person> getParents();
| List<? extends Person> getParents();
|
| Person getFriend();
|}
Expand Down Expand Up @@ -4091,7 +4113,7 @@ class CodeGenTest {
|
| Integer getAge();
|
| List<Person> getParents();
| List<? extends Person> getParents();
|
| Person getFriend();
|}
Expand Down
Loading