Skip to content

Commit

Permalink
Merge pull request #479 from jbaxleyiii/support-interfaces-in-mapped-…
Browse files Browse the repository at this point in the history
…types

Support type mapping when implementing an interface
  • Loading branch information
srinivasankavitha authored Oct 28, 2022
2 parents b3bb058 + a3a0582 commit 0c764b1
Show file tree
Hide file tree
Showing 23 changed files with 399 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class Kotlin2CodeGenTest {
"DateTime" to "java.time.OffsetDateTime",
"PageInfo" to "graphql.relay.PageInfo"
)
"dataClassWithMappedInterfaces" -> mapOf(
"Node" to "com.netflix.graphql.dgs.codegen.fixtures.Node"
)
else -> emptyMap()
}
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.expected

import com.netflix.graphql.dgs.codegen.GraphQLProjection
import com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.expected.client.QueryProjection
import graphql.language.OperationDefinition
import kotlin.String

public object DgsClient {
public fun buildQuery(_projection: QueryProjection.() -> QueryProjection): String =
GraphQLProjection.asQuery(OperationDefinition.Operation.QUERY, QueryProjection(), _projection)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.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 Products: String = "products"
}

public object PRODUCT {
public const val TYPE_NAME: String = "Product"

public const val Id: String = "id"
}

public object NODE {
public const val TYPE_NAME: String = "Node"

public const val Id: String = "id"
}

public object ENTITY {
public const val TYPE_NAME: String = "Entity"

public const val Id: String = "id"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.expected.client

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

public class EntityProjection : GraphQLProjection() {
public val id: EntityProjection
get() {
field("id")
return this
}

public fun onProduct(_projection: ProductProjection.() -> ProductProjection): EntityProjection {
fragment("Product", ProductProjection(), _projection)
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.expected.client

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

public class NodeProjection : GraphQLProjection() {
public val id: NodeProjection
get() {
field("id")
return this
}

public fun onEntity(_projection: EntityProjection.() -> EntityProjection): NodeProjection {
fragment("Entity", EntityProjection(), _projection)
return this
}

public fun onProduct(_projection: ProductProjection.() -> ProductProjection): NodeProjection {
fragment("Product", ProductProjection(), _projection)
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.expected.client

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

public class ProductProjection : GraphQLProjection() {
public val id: ProductProjection
get() {
field("id")
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.expected.client

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

public class QueryProjection : GraphQLProjection() {
public fun products(_projection: ProductProjection.() -> ProductProjection): QueryProjection {
field("products", ProductProjection(), _projection)
return this
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.expected.types

import com.fasterxml.jackson.`annotation`.JsonSubTypes
import com.fasterxml.jackson.`annotation`.JsonTypeInfo
import com.netflix.graphql.dgs.codegen.fixtures.Node
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 = Product::class, name = "Product")
])
public sealed interface Entity : Node {
@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("getId")
public override val id: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.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 com.netflix.graphql.dgs.codegen.fixtures.Node
import java.lang.IllegalStateException
import kotlin.String
import kotlin.Suppress
import kotlin.jvm.JvmName

@JsonTypeInfo(use = JsonTypeInfo.Id.NONE)
@JsonDeserialize(builder = Product.Builder::class)
public class Product(
id: () -> String = idDefault,
) : Entity, Node {
private val _id: () -> String = id

@Suppress("INAPPLICABLE_JVM_NAME")
@get:JvmName("getId")
public override val id: String
get() = _id.invoke()

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

}

@JsonPOJOBuilder
@JsonIgnoreProperties("__typename")
public class Builder {
private var id: () -> String = idDefault

@JsonProperty("id")
public fun withId(id: String): Builder = this.apply {
this.id = { id }
}

public fun build() = Product(
id = id,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.netflix.graphql.dgs.codegen.cases.dataClassWithMappedInterfaces.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(
products: () -> List<Product?>? = productsDefault,
) {
private val _products: () -> List<Product?>? = products

@get:JvmName("getProducts")
public val products: List<Product?>?
get() = _products.invoke()

public companion object {
private val productsDefault: () -> List<Product?>? =
{ throw IllegalStateException("Field `products` was not requested") }

}

@JsonPOJOBuilder
@JsonIgnoreProperties("__typename")
public class Builder {
private var products: () -> List<Product?>? = productsDefault

@JsonProperty("products")
public fun withProducts(products: List<Product?>?): Builder = this.apply {
this.products = { products }
}

public fun build() = Query(
products = products,
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
type Query {
products: [Product]
}

interface Node {
id: ID!
}

interface Entity implements Node {
id: ID!
}

type Product implements Entity & Node {
id: ID!
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
*
* Copyright 2020 Netflix, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/

package com.netflix.graphql.dgs.codegen.fixtures

interface Node {
val id: String
}
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ abstract class BaseDataTypeGenerator(
.addModifiers(Modifier.PUBLIC)

superInterfaces.forEach {
javaType.addSuperinterface(ClassName.get(packageName, (it as TypeName).name))
javaType.addSuperinterface(typeUtils.findJavaInterfaceName((it as TypeName).name, packageName))
}

fields.forEach {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class InterfaceGenerator(private val config: CodeGenConfig, private val document
definition.implements
.filterIsInstance<TypeName>()
.forEach {
javaType.addSuperinterface(ClassName.get(packageName, it.name))
javaType.addSuperinterface(typeUtils.findJavaInterfaceName(it.name, packageName))
}

val mergedFieldDefinitions = definition.fieldDefinitions + extensions.flatMap { it.fieldDefinitions }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,30 @@ class TypeUtils(private val packageName: String, private val config: CodeGenConf
return NodeTraverser().postOrder(visitor, fieldType) as JavaTypeName
}

/**
* Takes a GQL interface type name and returns the appropriate kotlin type given all of the mappings defined in the schema and config
*/
fun findJavaInterfaceName(interfaceName: String, packageName: String): JavaTypeName {
// check config
if (interfaceName in config.typeMapping) {
val mappedType = config.typeMapping.getValue(interfaceName)

return parseMappedType(
mappedType = mappedType,
toTypeName = String::toTypeName,
parameterize = { current ->
ParameterizedTypeName.get(
current.first as ClassName,
*current.second.toTypedArray()
)
},
onCloseBracketCallBack = { current, typeString -> current.second.add(typeString.toTypeName(true)) }
)
}

return ClassName.get(packageName, interfaceName)
}

private fun unboxType(typeName: JavaTypeName): JavaTypeName {
return if (typeName.isBoxedPrimitive) {
typeName.unbox()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ abstract class AbstractKotlinDataTypeGenerator(
val interfaceTypes = interfaces + unionTypes
interfaceTypes.forEach {
if (it is NamedNode<*>) {
kotlinType.addSuperinterface(ClassName.bestGuess("${getPackageName()}.${it.name}"))
kotlinType.addSuperinterface(typeUtils.findKtInterfaceName(it.name, getPackageName()))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class KotlinInterfaceTypeGenerator(private val config: CodeGenConfig, private va

superInterfacesNames(definition)
.forEach {
interfaceBuilder.addSuperinterface(ClassName(packageName, it))
interfaceBuilder.addSuperinterface(typeUtils.findKtInterfaceName(it, packageName))
}

val mergedFieldDefinitions = definition.fieldDefinitions + extensions.flatMap { it.fieldDefinitions }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,32 @@ class KotlinTypeUtils(private val packageName: String, private val config: CodeG
}
}

/**
* Takes a GQL interface type name and returns the appropriate kotlin type given all of the mappings defined in the schema and config
*/
fun findKtInterfaceName(interfaceName: String, packageName: String): KtTypeName {
// check config
if (interfaceName in config.typeMapping) {
val mappedType = config.typeMapping.getValue(interfaceName)

return parseMappedType(
mappedType = mappedType,
toTypeName = String::toKtTypeName,
parameterize = { (it.first as ClassName).parameterizedBy(it.second) },
onCloseBracketCallBack = { current, typeString ->
if (typeString.trim() == "?") {
val last = current.second.removeLast()
current.second.add(last.copy(nullable = true))
} else {
current.second.add(typeString.toKtTypeName(true))
}
}
)
}

return "$packageName.$interfaceName".toKtTypeName()
}

private fun TypeName.toKtTypeName(): KtTypeName {
if (name in config.typeMapping) {
val mappedType = config.typeMapping.getValue(name)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ fun generateKotlin2DataTypes(
.addType(builder)
// add interfaces to implement
.addSuperinterfaces(
superInterfaces.map { ClassName.bestGuess("${config.packageNameTypes}.$it") }
superInterfaces.map { typeLookup.findKtInterfaceName(it, config.packageNameTypes) }
)
// add a constructor with a supplier for every field
.primaryConstructor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ fun generateKotlin2Interfaces(
}
// add interfaces to implement
.addSuperinterfaces(
implementedInterfaces.map { ClassName(config.packageNameTypes, it) }
implementedInterfaces.map { typeLookup.findKtInterfaceName(it, config.packageNameTypes) }
)
// add fields, overriding if needed
.addProperties(
Expand Down
Loading

0 comments on commit 0c764b1

Please sign in to comment.