Skip to content

Commit

Permalink
Support for multi-inheritance on interfaces via graphql-java 16
Browse files Browse the repository at this point in the history
This commit Fixes #41 by adding support to multi interface inheritance
via graphql-java 16.0. For example.

```
interface Person {
    firstname: String
    lastname: String
}

interface Employee implements Person {
    firstname: String
    lastname: String
    company: String
}

type Talent implements Employee {
    firstname: String
    lastname: String
    company: String
    imdbProfile: String
}
```
  • Loading branch information
berngp committed Feb 19, 2021
1 parent 03692e0 commit 6223f90
Show file tree
Hide file tree
Showing 10 changed files with 425 additions and 99 deletions.
4 changes: 2 additions & 2 deletions graphql-dgs-codegen-core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ dependencies {
implementation "com.netflix.graphql.dgs:graphql-dgs-client:latest.release"
implementation "com.squareup:javapoet:1.13.+"
implementation "com.squareup:kotlinpoet:1.7.+"
implementation "com.graphql-java:graphql-java:14.0"
implementation("com.github.ajalt.clikt:clikt:3.1.+")
implementation "com.graphql-java:graphql-java:16.+"
implementation "com.github.ajalt.clikt:clikt:3.1.+"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.12.+"

testImplementation "com.google.testing.compile:compile-testing:0.+"
Expand Down
50 changes: 25 additions & 25 deletions graphql-dgs-codegen-core/dependencies.lock
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"apiDependenciesMetadata": {
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": {
"org.jetbrains.kotlin:kotlin-stdlib": {
"locked": "1.4.30"
}
},
Expand All @@ -12,21 +12,21 @@
"locked": "3.1.0"
},
"com.graphql-java:graphql-java": {
"locked": "14.0"
"locked": "16.2"
},
"com.netflix.graphql.dgs:graphql-dgs": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.netflix.graphql.dgs:graphql-dgs-client": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.squareup:javapoet": {
"locked": "1.13.0"
},
"com.squareup:kotlinpoet": {
"locked": "1.7.2"
},
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": {
"org.jetbrains.kotlin:kotlin-stdlib": {
"locked": "1.4.30"
}
},
Expand All @@ -38,21 +38,21 @@
"locked": "3.1.0"
},
"com.graphql-java:graphql-java": {
"locked": "14.0"
"locked": "16.2"
},
"com.netflix.graphql.dgs:graphql-dgs": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.netflix.graphql.dgs:graphql-dgs-client": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.squareup:javapoet": {
"locked": "1.13.0"
},
"com.squareup:kotlinpoet": {
"locked": "1.7.2"
},
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": {
"org.jetbrains.kotlin:kotlin-stdlib": {
"locked": "1.4.30"
}
},
Expand All @@ -79,21 +79,21 @@
"locked": "3.1.0"
},
"com.graphql-java:graphql-java": {
"locked": "14.0"
"locked": "16.2"
},
"com.netflix.graphql.dgs:graphql-dgs": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.netflix.graphql.dgs:graphql-dgs-client": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.squareup:javapoet": {
"locked": "1.13.0"
},
"com.squareup:kotlinpoet": {
"locked": "1.7.2"
},
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": {
"org.jetbrains.kotlin:kotlin-stdlib": {
"locked": "1.4.30"
}
},
Expand All @@ -111,13 +111,13 @@
"locked": "1.1.2"
},
"com.graphql-java:graphql-java": {
"locked": "14.0"
"locked": "16.2"
},
"com.netflix.graphql.dgs:graphql-dgs": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.netflix.graphql.dgs:graphql-dgs-client": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.squareup:javapoet": {
"locked": "1.13.0"
Expand All @@ -128,7 +128,7 @@
"org.assertj:assertj-core": {
"locked": "3.11.1"
},
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": {
"org.jetbrains.kotlin:kotlin-stdlib": {
"locked": "1.4.30"
},
"org.junit.jupiter:junit-jupiter": {
Expand All @@ -155,13 +155,13 @@
"locked": "1.1.2"
},
"com.graphql-java:graphql-java": {
"locked": "14.0"
"locked": "16.2"
},
"com.netflix.graphql.dgs:graphql-dgs": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.netflix.graphql.dgs:graphql-dgs-client": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.squareup:javapoet": {
"locked": "1.13.0"
Expand All @@ -172,7 +172,7 @@
"org.assertj:assertj-core": {
"locked": "3.11.1"
},
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": {
"org.jetbrains.kotlin:kotlin-stdlib": {
"locked": "1.4.30"
},
"org.junit.jupiter:junit-jupiter": {
Expand All @@ -199,13 +199,13 @@
"locked": "1.1.2"
},
"com.graphql-java:graphql-java": {
"locked": "14.0"
"locked": "16.2"
},
"com.netflix.graphql.dgs:graphql-dgs": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.netflix.graphql.dgs:graphql-dgs-client": {
"locked": "3.3.0"
"locked": "3.5.1"
},
"com.squareup:javapoet": {
"locked": "1.13.0"
Expand All @@ -216,7 +216,7 @@
"org.assertj:assertj-core": {
"locked": "3.11.1"
},
"org.jetbrains.kotlin:kotlin-stdlib-jdk8": {
"org.jetbrains.kotlin:kotlin-stdlib": {
"locked": "1.4.30"
},
"org.junit.jupiter:junit-jupiter": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ class CodeGen(private val config: CodeGenConfig) {

private fun generateJavaClientEntitiesApi(definitions: MutableList<Definition<Definition<*>>>, document: Document): CodeGenResult {
return if (config.generateClientApi) {
val federatedDefinitions = definitions.filterIsInstance<ObjectTypeDefinition>().filter { it.getDirective("key") != null }
val federatedDefinitions = definitions.filterIsInstance<ObjectTypeDefinition>().filter { it.hasDirective("key") }
ClientApiGenerator(config, document).generateEntities(federatedDefinitions)
} else CodeGenResult()
}
Expand All @@ -121,7 +121,7 @@ class CodeGen(private val config: CodeGenConfig) {
return if (config.generateClientApi) {
val generatedRepresentations = mutableMapOf<String, Any>()
return definitions.filterIsInstance<ObjectTypeDefinition>()
.filter { it.getDirective("key") != null }
.filter { it.hasDirective("key") }
.map { d ->
EntitiesRepresentationTypeGenerator(config).generate(d, document, generatedRepresentations)
}.fold(CodeGenResult()) { t: CodeGenResult, u: CodeGenResult -> t.merge(u) }
Expand Down Expand Up @@ -195,7 +195,7 @@ class CodeGen(private val config: CodeGenConfig) {

private fun generateKotlinClientEntitiesApi(definitions: MutableList<Definition<Definition<*>>>, document: Document): KotlinCodeGenResult {
return if (config.generateClientApi) {
val federatedDefinitions = definitions.filterIsInstance<ObjectTypeDefinition>().filter { it.getDirective("key") != null }
val federatedDefinitions = definitions.filterIsInstance<ObjectTypeDefinition>().filter { it.hasDirective("key") }
KotlinClientApiGenerator(config, document).generateEntities(federatedDefinitions)
} else KotlinCodeGenResult()
}
Expand All @@ -204,7 +204,7 @@ class CodeGen(private val config: CodeGenConfig) {
return if (config.generateClientApi) {
val generatedRepresentations = mutableMapOf<String, Any>()
return definitions.filterIsInstance<ObjectTypeDefinition>()
.filter { it.getDirective("key") != null }
.filter { it.hasDirective("key") }
.map { d ->
KotlinEntitiesRepresentationTypeGenerator(config).generate(d, document, generatedRepresentations)
}.fold(KotlinCodeGenResult()) { t: KotlinCodeGenResult, u: KotlinCodeGenResult -> t.merge(u) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class ClientApiGenerator(private val config: CodeGenConfig, private val document

var entitiesRootProjection = CodeGenResult()
// generate for federation types, if present
val federatedTypes = definitions.filter { it.getDirective("key") != null }
val federatedTypes = definitions.filter { it.hasDirective("key") }
if (federatedTypes.isNotEmpty()) {
// create entities root projection
entitiesRootProjection = createEntitiesRootProjection(federatedTypes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,17 @@ import graphql.language.*


@Suppress("UNCHECKED_CAST")
class EntitiesRepresentationTypeGenerator(val config: CodeGenConfig): BaseDataTypeGenerator(config.packageNameClient, config) {
class EntitiesRepresentationTypeGenerator(val config: CodeGenConfig) : BaseDataTypeGenerator(config.packageNameClient, config) {
fun generate(definition: ObjectTypeDefinition, document: Document, generatedRepresentations: MutableMap<String, Any>): CodeGenResult {
if(config.skipEntityQueries) {
if (config.skipEntityQueries) {
return CodeGenResult()
}

val name = "${definition.name}Representation"
if (generatedRepresentations.containsKey(name)) {
return CodeGenResult()
}
val directiveArg = (definition.getDirective("key").argumentsByName["fields"]?.value as StringValue).value
val directiveArg = definition.getDirectives("key").map { it.argumentsByName["fields"]?.value as StringValue }.map { it.value }
val keyFields = parseKeyDirectiveValue(directiveArg)
return generateRepresentations(definition, document, generatedRepresentations, keyFields)
}
Expand All @@ -58,7 +58,7 @@ class EntitiesRepresentationTypeGenerator(val config: CodeGenConfig): BaseDataTy
val type = findType(it.type, document)
if (type != null && type is ObjectTypeDefinition) {
val representationType = typeUtils.findReturnType(it.type).toString().replace(type.name, "${type.name}Representation")
if (! generatedRepresentations.containsKey(name)) {
if (!generatedRepresentations.containsKey(name)) {
result = generateRepresentations(type, document, generatedRepresentations, keyFields[it.name] as Map<String, Any>)
}
generatedRepresentations["${type.name}Representation"] = representationType
Expand All @@ -82,10 +82,14 @@ class EntitiesRepresentationTypeGenerator(val config: CodeGenConfig): BaseDataTy
}
}

private fun parseKeyDirectiveValue(keyDirective: String): Map<String, Any> {
data class Node (val key: String, val map: MutableMap<String, Any>, val parent: Node?)
val sanitizedKeys = keyDirective.map { if (it == '{' || it == '}') " $it " else "$it" }
val keys = sanitizedKeys.joinToString("", "", "").split(" ")
private fun parseKeyDirectiveValue(keyDirective: List<String>): Map<String, Any> {
data class Node(val key: String, val map: MutableMap<String, Any>, val parent: Node?)

val keys = keyDirective.map { ds ->
ds.map { if (it == '{' || it == '}') " $it " else "$it" }
.joinToString("", "", "")
.split(" ")
}.flatten()

// handle simple keys and nested keys by constructing the path to each key
// e.g. type Movie @key(fields: "movieId") or type MovieCast @key(fields: movie { movieId } actors { name } }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ class KotlinClientApiGenerator(private val config: CodeGenConfig, private val do

var entitiesRootProjection = KotlinCodeGenResult()
// generate for federation types, if present
val federatedTypes = definitions.filter { it.getDirective("key") != null }
val federatedTypes = definitions.filter { it.hasDirective("key") }
if (federatedTypes.isNotEmpty()) {
// create entities root projection
entitiesRootProjection = createEntitiesRootProjection(federatedTypes)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,20 @@ import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.STRING
import graphql.language.*

class KotlinEntitiesRepresentationTypeGenerator(private val config: CodeGenConfig): AbstractKotlinDataTypeGenerator(config.packageNameClient, config) {
class KotlinEntitiesRepresentationTypeGenerator(private val config: CodeGenConfig) : AbstractKotlinDataTypeGenerator(config.packageNameClient, config) {

fun generate(definition: ObjectTypeDefinition, document: Document, generatedRepresentations: MutableMap<String, Any>): KotlinCodeGenResult {
val name = "${definition.name}Representation"
if (generatedRepresentations.containsKey(name)) {
return KotlinCodeGenResult()
}
val directiveArg = (definition.getDirective("key").argumentsByName["fields"]?.value as StringValue).value
val directiveArg = definition.getDirectives("key").map { it.argumentsByName["fields"]?.value as StringValue }.map { it.value }
val keyFields = parseKeyDirectiveValue(directiveArg)
return generateRepresentations(definition, document, generatedRepresentations, keyFields)
}

fun generateRepresentations(definition: ObjectTypeDefinition, document: Document, generatedRepresentations: MutableMap<String, Any>,
keyFields: Map<String, Any> ): KotlinCodeGenResult {
keyFields: Map<String, Any>): KotlinCodeGenResult {
val name = "${definition.name}Representation"
if (generatedRepresentations.containsKey(name)) {
return KotlinCodeGenResult()
Expand All @@ -49,7 +49,7 @@ class KotlinEntitiesRepresentationTypeGenerator(private val config: CodeGenConfi
var result = KotlinCodeGenResult()
// generate representations of entity types that have @key, including the __typename field, and the key fields
val typeName = Field("__typename", STRING, false, CodeBlock.of("%S", definition.name))
val fieldDefinitions= definition.fieldDefinitions
val fieldDefinitions = definition.fieldDefinitions
.filter {
keyFields.containsKey(it.name)
}
Expand All @@ -58,7 +58,7 @@ class KotlinEntitiesRepresentationTypeGenerator(private val config: CodeGenConfi
val fieldType = typeUtils.findReturnType(it.type)
if (type != null && type is ObjectTypeDefinition) {
val representationType = fieldType.toString().replace(type.name, "${type.name}Representation").removeSuffix("?")
if (! generatedRepresentations.containsKey(name)) {
if (!generatedRepresentations.containsKey(name)) {
result = generateRepresentations(type, document, generatedRepresentations, keyFields[it.name] as Map<String, Any>)
}
generatedRepresentations["${type.name}Representation"] = representationType
Expand Down Expand Up @@ -92,10 +92,14 @@ class KotlinEntitiesRepresentationTypeGenerator(private val config: CodeGenConfi
}
}

private fun parseKeyDirectiveValue(keyDirective: String): Map<String, Any> {
data class Node (val key: String, val map: MutableMap<String, Any>, val parent: Node?)
val sanitizedKeys = keyDirective.map { if (it == '{' || it == '}') " $it " else "$it" }
val keys = sanitizedKeys.joinToString("", "", "").split(" ")
private fun parseKeyDirectiveValue(keyDirective: List<String>): Map<String, Any> {
data class Node(val key: String, val map: MutableMap<String, Any>, val parent: Node?)

val keys = keyDirective.map { ds ->
ds.map { if (it == '{' || it == '}') " $it " else "$it" }
.joinToString("", "", "")
.split(" ")
}.flatten()

// handle simple keys and nested keys by constructing the path to each key
// e.g. type Movie @key(fields: "movieId") or type MovieCast @key(fields: movie { movieId } actors { name } }
Expand Down
Loading

0 comments on commit 6223f90

Please sign in to comment.