Skip to content

Commit

Permalink
fix: add support for extending type with interface (#715)
Browse files Browse the repository at this point in the history
* fix: add support for extending type with interface

* add test case

* remove definitions from implementedInterfaces, expand imports

---------

Co-authored-by: Damian Wilkołek <[email protected]>
  • Loading branch information
dwilkolek and Damian Wilkołek authored Jul 19, 2024
1 parent e36cbd4 commit a1939ae
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected

public object DgsClient
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected

import kotlin.String

public object DgsConstants {
public object EXAMPLE {
public const val TYPE_NAME: String = "Example"

public const val Name: String = "name"

public const val Age: String = "age"
}

public object A {
public const val TYPE_NAME: String = "A"

public const val Name: String = "name"
}

public object B {
public const val TYPE_NAME: String = "B"

public const val Age: String = "age"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected.client

import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface
import com.netflix.graphql.dgs.codegen.GraphQLProjection

public class AProjection(
inputValueSerializer: InputValueSerializerInterface? = null,
) : GraphQLProjection(inputValueSerializer) {
public val name: AProjection
get() {
field("name")
return this
}

public fun onExample(_projection: ExampleProjection.() -> ExampleProjection): AProjection {
fragment("Example", ExampleProjection(), _projection)
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected.client

import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface
import com.netflix.graphql.dgs.codegen.GraphQLProjection

public class BProjection(
inputValueSerializer: InputValueSerializerInterface? = null,
) : GraphQLProjection(inputValueSerializer) {
public val age: BProjection
get() {
field("age")
return this
}

public fun onExample(_projection: ExampleProjection.() -> ExampleProjection): BProjection {
fragment("Example", ExampleProjection(), _projection)
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected.client

import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface
import com.netflix.graphql.dgs.codegen.GraphQLProjection

public class ExampleProjection(
inputValueSerializer: InputValueSerializerInterface? = null,
) : GraphQLProjection(inputValueSerializer) {
public val name: ExampleProjection
get() {
field("name")
return this
}

public val age: ExampleProjection
get() {
field("age")
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected.types

import com.fasterxml.jackson.`annotation`.JsonSubTypes
import com.fasterxml.jackson.`annotation`.JsonTypeInfo
import kotlin.String
import kotlin.Suppress
import kotlin.jvm.JvmName

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "__typename",
)
@JsonSubTypes(value = [
JsonSubTypes.Type(value = Example::class, name = "Example")
])
public sealed interface A {
@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("getName")
public val name: String?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected.types

import com.fasterxml.jackson.`annotation`.JsonSubTypes
import com.fasterxml.jackson.`annotation`.JsonTypeInfo
import kotlin.Int
import kotlin.Suppress
import kotlin.jvm.JvmName

@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "__typename",
)
@JsonSubTypes(value = [
JsonSubTypes.Type(value = Example::class, name = "Example")
])
public sealed interface B {
@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("getAge")
public val age: Int?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.netflix.graphql.dgs.codegen.cases.extendedDataClassWithInterface.expected.types

import com.fasterxml.jackson.`annotation`.JsonIgnoreProperties
import com.fasterxml.jackson.`annotation`.JsonProperty
import com.fasterxml.jackson.`annotation`.JsonTypeInfo
import com.fasterxml.jackson.databind.`annotation`.JsonDeserialize
import com.fasterxml.jackson.databind.`annotation`.JsonPOJOBuilder
import java.lang.IllegalStateException
import kotlin.Int
import kotlin.String
import kotlin.Suppress
import kotlin.jvm.JvmName

@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonDeserialize(builder = Example.Builder::class)
public class Example(
name: () -> String? = nameDefault,
age: () -> Int? = ageDefault,
) : A,
B {
private val __name: () -> String? = name

private val __age: () -> Int? = age

@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("getName")
override val name: String?
get() = __name.invoke()

@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("getAge")
override val age: Int?
get() = __age.invoke()

public companion object {
private val nameDefault: () -> String? =
{ throw IllegalStateException("Field `name` was not requested") }

private val ageDefault: () -> Int? =
{ throw IllegalStateException("Field `age` was not requested") }
}

@JsonPOJOBuilder
@JsonIgnoreProperties("__typename")
public class Builder {
private var name: () -> String? = nameDefault

private var age: () -> Int? = ageDefault

@JsonProperty("name")
public fun withName(name: String?): Builder = this.apply {
this.name = { name }
}

@JsonProperty("age")
public fun withAge(age: Int?): Builder = this.apply {
this.age = { age }
}

public fun build(): Example = Example(
name = name,
age = age,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
interface A { name : String }

type Example implements A {
name: String
}

interface B { age :Int }

extend type Example implements B{
age :Int
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ class DataTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTyp
union.memberTypes.asSequence().map { it as TypeName }.any { it.name == name }
}.map { it.name }.toList()

var implements = definition.implements.asSequence().filterIsInstance<TypeName>().map { typeUtils.findReturnType(it).toString() }.toList()
var implements = (definition.implements + extensions.flatMap { it.implements })
.asSequence().filterIsInstance<TypeName>().map { typeUtils.findReturnType(it).toString() }.toList()

var useInterfaceType = false
var overrideGetter = false
Expand Down Expand Up @@ -110,7 +111,7 @@ class DataTypeGenerator(config: CodeGenConfig, document: Document) : BaseDataTyp

val interfaceName = "I$name"
implements = listOf(interfaceName) + implements
val superInterfaces = definition.implements
val superInterfaces = definition.implements + extensions.flatMap { it.implements }
interfaceCodeGenResult = generateInterface(interfaceName, superInterfaces, fieldDefinitions)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class KotlinDataTypeGenerator(config: CodeGenConfig, document: Document) :
it.directives
)
}
val interfaces = definition.implements
val interfaces = definition.implements + extensions.flatMap { it.implements }
return generate(definition.name, fields, interfaces, document, definition.description, definition.directives)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,6 @@ fun generateKotlin2DataTypes(
.map { typeDefinition ->

logger.info("Generating data type {}", typeDefinition.name)

// get all interfaces this type implements
val implementedInterfaces = typeLookup.implementedInterfaces(typeDefinition)
val implementedUnionTypes = typeLookup.implementedUnionTypes(typeDefinition.name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ package com.netflix.graphql.dgs.codegen.generators.kotlin2
import com.netflix.graphql.dgs.codegen.CodeGenConfig
import com.netflix.graphql.dgs.codegen.generators.kotlin.toKtTypeName
import com.netflix.graphql.dgs.codegen.generators.shared.JAVA_TYPE_DIRECTIVE_NAME
import com.netflix.graphql.dgs.codegen.generators.shared.SchemaExtensionsUtils.findTypeExtensions
import com.netflix.graphql.dgs.codegen.generators.shared.parseMappedType
import com.squareup.kotlinpoet.BOOLEAN
import com.squareup.kotlinpoet.ClassName
Expand Down Expand Up @@ -56,7 +57,7 @@ import com.squareup.kotlinpoet.TypeName as KtTypeName
*/
class Kotlin2TypeLookup(
config: CodeGenConfig,
document: Document
private val document: Document
) {

/**
Expand Down Expand Up @@ -172,8 +173,12 @@ class Kotlin2TypeLookup(
* Returns the list of interfaces that this type implements
*/
fun implementedInterfaces(typeDefinition: ImplementingTypeDefinition<*>): List<String> {
return typeDefinition.implements
return (
typeDefinition.implements +
findTypeExtensions(typeDefinition.name, document.definitions).flatMap { it.implements }
)
.filterIsInstance<NamedNode<*>>()
.filter { it.name != typeDefinition.name }
.map { it.name }
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4913,4 +4913,35 @@ It takes a title and such.
).generate()
}.hasMessage("java.math.BigDecimal cannot be created from BooleanValue{value=true}, expected String, Int or Float value")
}

@Test
fun `Codegen should generate class implementing interface provided in extended type`() {
val schema = """
interface A { name : String }
type Example implements A {
name: String
}
interface B { age :Int }
extend type Example implements B{
age :Int
}
""".trimIndent()

val result = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName
)
).generate()
assertThat(result.javaDataTypes[0].typeSpec.superinterfaces[0].toString()).isEqualTo(
"com.netflix.graphql.dgs.codegen.tests.generated.types.com.netflix.graphql.dgs.codegen.tests.generated.types.A"
)
assertThat(result.javaDataTypes[0].typeSpec.superinterfaces[1].toString()).isEqualTo(
"com.netflix.graphql.dgs.codegen.tests.generated.types.com.netflix.graphql.dgs.codegen.tests.generated.types.B"
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4096,4 +4096,34 @@ It takes a title and such.
).generate()
}.hasMessage("java.math.BigDecimal cannot be created from BooleanValue{value=true}, expected String, Int or Float value")
}

@Test
fun `Codegen should generate class implementing interface provided in extended type`() {
val schema = """
interface A { name : String }
type Example implements A {
name: String
}
interface B { age :Int }
extend type Example implements B{
age :Int
}
""".trimIndent()

val result = CodeGen(
CodeGenConfig(
schemas = setOf(schema),
packageName = basePackageName,
language = Language.KOTLIN
)
).generate()
val superinterfaces = result.kotlinDataTypes[0].typeSpecs[0].superinterfaces.keys.map { it.toString() }
assertThat(superinterfaces.size).isEqualTo(2)
assertThat(superinterfaces).contains("com.netflix.graphql.dgs.codegen.tests.generated.types.A")
assertThat(superinterfaces).contains("com.netflix.graphql.dgs.codegen.tests.generated.types.B")
}
}

0 comments on commit a1939ae

Please sign in to comment.