From 78f6c92be3d66fdd9a39508d5632eaa2cd1754f4 Mon Sep 17 00:00:00 2001 From: Samuel Gagarin <66745577+Lavmee@users.noreply.github.com> Date: Fri, 4 Oct 2024 21:18:40 +0300 Subject: [PATCH] feat: [k2] add support for data types inherited from extended interface (#741) * feat: add support for data types inherited from extended interface * refactor: change expected integration test by graphql specification & rollback extensionTypes definition (because of graphql specification) graphql specification (interface extensions): https://spec.graphql.org/October2021/#sel-GAHbpBBBBKBUytV * style: lint check --- .../expected/DgsClient.kt | 14 +++ .../expected/DgsConstants.kt | 35 ++++++ .../expected/client/EmployeeProjection.kt | 32 ++++++ .../expected/client/PersonProjection.kt | 26 +++++ .../expected/client/QueryProjection.kt | 15 +++ .../expected/types/Employee.kt | 101 ++++++++++++++++++ .../expected/types/Person.kt | 30 ++++++ .../expected/types/Query.kt | 42 ++++++++ .../schema.graphql | 22 ++++ .../generators/kotlin2/Kotlin2TypeLookup.kt | 3 +- 10 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsClient.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsConstants.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/EmployeeProjection.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/PersonProjection.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/QueryProjection.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Employee.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Person.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Query.kt create mode 100644 graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/schema.graphql diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsClient.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsClient.kt new file mode 100644 index 00000000..acac7cc3 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsClient.kt @@ -0,0 +1,14 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.expected + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.expected.client.QueryProjection +import graphql.language.OperationDefinition +import kotlin.String + +public object DgsClient { + public fun buildQuery(inputValueSerializer: InputValueSerializerInterface? = null, + _projection: QueryProjection.() -> QueryProjection): String = + GraphQLProjection.asQuery(OperationDefinition.Operation.QUERY, + QueryProjection(inputValueSerializer), _projection) +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsConstants.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsConstants.kt new file mode 100644 index 00000000..3a27abae --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/DgsConstants.kt @@ -0,0 +1,35 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.expected + +import kotlin.String + +public object DgsConstants { + public const val QUERY_TYPE: String = "Query" + + public object QUERY { + public const val TYPE_NAME: String = "Query" + + public const val People: String = "people" + } + + public object EMPLOYEE { + public const val TYPE_NAME: String = "Employee" + + public const val Firstname: String = "firstname" + + public const val Lastname: String = "lastname" + + public const val Company: String = "company" + + public const val Age: String = "age" + } + + public object PERSON { + public const val TYPE_NAME: String = "Person" + + public const val Firstname: String = "firstname" + + public const val Lastname: String = "lastname" + + public const val Age: String = "age" + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/EmployeeProjection.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/EmployeeProjection.kt new file mode 100644 index 00000000..ac66bb3a --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/EmployeeProjection.kt @@ -0,0 +1,32 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.expected.client + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection + +public class EmployeeProjection( + inputValueSerializer: InputValueSerializerInterface? = null, +) : GraphQLProjection(inputValueSerializer) { + public val firstname: EmployeeProjection + get() { + field("firstname") + return this + } + + public val lastname: EmployeeProjection + get() { + field("lastname") + return this + } + + public val company: EmployeeProjection + get() { + field("company") + return this + } + + public val age: EmployeeProjection + get() { + field("age") + return this + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/PersonProjection.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/PersonProjection.kt new file mode 100644 index 00000000..4a6ae360 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/PersonProjection.kt @@ -0,0 +1,26 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.expected.client + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection + +public class PersonProjection( + inputValueSerializer: InputValueSerializerInterface? = null, +) : GraphQLProjection(inputValueSerializer) { + public val firstname: PersonProjection + get() { + field("firstname") + return this + } + + public val lastname: PersonProjection + get() { + field("lastname") + return this + } + + public fun onEmployee(_projection: EmployeeProjection.() -> EmployeeProjection): + PersonProjection { + fragment("Employee", EmployeeProjection(), _projection) + return this + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/QueryProjection.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/QueryProjection.kt new file mode 100644 index 00000000..4f99697b --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/client/QueryProjection.kt @@ -0,0 +1,15 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.expected.client + +import com.netflix.graphql.dgs.client.codegen.InputValueSerializerInterface +import com.netflix.graphql.dgs.codegen.GraphQLProjection +import kotlin.String + +public class QueryProjection( + inputValueSerializer: InputValueSerializerInterface? = null, +) : GraphQLProjection(inputValueSerializer) { + public fun people(_alias: String? = null, _projection: PersonProjection.() -> PersonProjection): + QueryProjection { + field(_alias, "people", PersonProjection(inputValueSerializer), _projection) + return this + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Employee.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Employee.kt new file mode 100644 index 00000000..6696e70c --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Employee.kt @@ -0,0 +1,101 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.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 = Employee.Builder::class) +public class Employee( + firstname: () -> String = firstnameDefault, + lastname: () -> String? = lastnameDefault, + company: () -> String? = companyDefault, + age: () -> Int = ageDefault, +) : Person { + private val __firstname: () -> String = firstname + + private val __lastname: () -> String? = lastname + + private val __company: () -> String? = company + + private val __age: () -> Int = age + + @Suppress("INAPPLICABLE_JVM_NAME") + @get:JvmName("getFirstname") + override val firstname: String + get() = __firstname.invoke() + + @Suppress("INAPPLICABLE_JVM_NAME") + @get:JvmName("getLastname") + override val lastname: String? + get() = __lastname.invoke() + + @get:JvmName("getCompany") + public val company: String? + get() = __company.invoke() + + @Suppress("INAPPLICABLE_JVM_NAME") + @get:JvmName("getAge") + override val age: Int + get() = __age.invoke() + + public companion object { + private val firstnameDefault: () -> String = + { throw IllegalStateException("Field `firstname` was not requested") } + + private val lastnameDefault: () -> String? = + { throw IllegalStateException("Field `lastname` was not requested") } + + private val companyDefault: () -> String? = + { throw IllegalStateException("Field `company` was not requested") } + + private val ageDefault: () -> Int = + { throw IllegalStateException("Field `age` was not requested") } + } + + @JsonPOJOBuilder + @JsonIgnoreProperties("__typename") + public class Builder { + private var firstname: () -> String = firstnameDefault + + private var lastname: () -> String? = lastnameDefault + + private var company: () -> String? = companyDefault + + private var age: () -> Int = ageDefault + + @JsonProperty("firstname") + public fun withFirstname(firstname: String): Builder = this.apply { + this.firstname = { firstname } + } + + @JsonProperty("lastname") + public fun withLastname(lastname: String?): Builder = this.apply { + this.lastname = { lastname } + } + + @JsonProperty("company") + public fun withCompany(company: String?): Builder = this.apply { + this.company = { company } + } + + @JsonProperty("age") + public fun withAge(age: Int): Builder = this.apply { + this.age = { age } + } + + public fun build(): Employee = Employee( + firstname = firstname, + lastname = lastname, + company = company, + age = age, + ) + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Person.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Person.kt new file mode 100644 index 00000000..157302a8 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Person.kt @@ -0,0 +1,30 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.expected.types + +import com.fasterxml.jackson.`annotation`.JsonSubTypes +import com.fasterxml.jackson.`annotation`.JsonTypeInfo +import kotlin.Int +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 = Employee::class, name = "Employee") +]) +public sealed interface Person { + @Suppress("INAPPLICABLE_JVM_NAME") + @get:JvmName("getFirstname") + public val firstname: String + + @Suppress("INAPPLICABLE_JVM_NAME") + @get:JvmName("getLastname") + public val lastname: String? + + @Suppress("INAPPLICABLE_JVM_NAME") + @get:JvmName("getAge") + public val age: Int +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Query.kt b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Query.kt new file mode 100644 index 00000000..13ef33bc --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/expected/types/Query.kt @@ -0,0 +1,42 @@ +package com.netflix.graphql.dgs.codegen.cases.dataClassWithExtendedInterfaceInheritance.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.collections.List +import kotlin.jvm.JvmName + +@JsonTypeInfo(use = JsonTypeInfo.Id.NONE) +@JsonDeserialize(builder = Query.Builder::class) +public class Query( + people: () -> List? = peopleDefault, +) { + private val __people: () -> List? = people + + @get:JvmName("getPeople") + public val people: List? + get() = __people.invoke() + + public companion object { + private val peopleDefault: () -> List? = + { throw IllegalStateException("Field `people` was not requested") } + } + + @JsonPOJOBuilder + @JsonIgnoreProperties("__typename") + public class Builder { + private var people: () -> List? = peopleDefault + + @JsonProperty("people") + public fun withPeople(people: List?): Builder = this.apply { + this.people = { people } + } + + public fun build(): Query = Query( + people = people, + ) + } +} diff --git a/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/schema.graphql b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/schema.graphql new file mode 100644 index 00000000..61fa5a66 --- /dev/null +++ b/graphql-dgs-codegen-core/src/integTest/kotlin/com/netflix/graphql/dgs/codegen/cases/dataClassWithExtendedInterfaceInheritance/schema.graphql @@ -0,0 +1,22 @@ +type Query { + people: [Person] +} + +interface Person { + firstname: String! + lastname: String +} + +type Employee implements Person { + firstname: String! + lastname: String + company: String +} + +extend interface Person { + age: Int! +} + +extend type Employee { + age: Int! +} diff --git a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt index 3fad8a11..6ea3b482 100644 --- a/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt +++ b/graphql-dgs-codegen-core/src/main/kotlin/com/netflix/graphql/dgs/codegen/generators/kotlin2/Kotlin2TypeLookup.kt @@ -143,7 +143,8 @@ class Kotlin2TypeLookup( */ private val interfaceFields: Map> = document .getDefinitionsOfType(InterfaceTypeDefinition::class.java) - .associate { i -> i.name to i.fieldDefinitions.map { it.name } } + .groupBy(keySelector = { it.name }, valueTransform = { it.fieldDefinitions }) + .mapValues { e -> e.value.flatten().map { i -> i.name } } /** * A map of interfaces to the types that implement them