Skip to content

Commit 2c3fb8b

Browse files
Merge pull request #380 from Netflix/query-and-projection-serialization-with-graphql-java
Leverage graphql-java in GraphQLQueryRequest and ProjectionSerializer
2 parents 58a48bf + 163bc88 commit 2c3fb8b

File tree

7 files changed

+342
-110
lines changed

7 files changed

+342
-110
lines changed

graphql-dgs-codegen-client-core/src/main/kotlin/com/netflix/graphql/dgs/client/codegen/EntitiesGraphQLQuery.kt

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,40 +17,53 @@
1717
package com.netflix.graphql.dgs.client.codegen
1818

1919
import com.fasterxml.jackson.databind.ObjectMapper
20-
import java.util.*
20+
import graphql.language.ListType
21+
import graphql.language.NonNullType
22+
import graphql.language.TypeName
23+
import graphql.language.VariableDefinition
24+
import graphql.language.VariableReference
2125

22-
class EntitiesGraphQLQuery : GraphQLQuery {
23-
val variables: MutableMap<String, Any> = LinkedHashMap()
26+
class EntitiesGraphQLQuery(representations: List<Any>) : GraphQLQuery() {
2427

25-
constructor(representations: List<Any>?) {
26-
variables["representations"] = representations!!
27-
}
28-
29-
constructor()
28+
val variables: Map<String, Any> = mapOf(REPRESENTATIONS_NAME to representations)
3029

31-
override fun getOperationType(): String {
32-
return "query(\$representations: [_Any!]!)"
30+
init {
31+
variableDefinitions += VariableDefinition.newVariableDefinition()
32+
.name(REPRESENTATIONS_NAME)
33+
.type(
34+
NonNullType.newNonNullType(
35+
ListType.newListType(
36+
NonNullType.newNonNullType(
37+
TypeName.newTypeName("_Any").build()
38+
).build()
39+
).build()
40+
).build()
41+
)
42+
.build()
43+
input[REPRESENTATIONS_NAME] = VariableReference.newVariableReference().name(REPRESENTATIONS_NAME).build()
3344
}
3445

3546
override fun getOperationName(): String {
36-
return "_entities(representations: \$representations)"
47+
return "_entities"
3748
}
3849

3950
class Builder {
40-
private val representations: MutableList<Any> = ArrayList()
41-
val mapper = ObjectMapper()
51+
private val representations = mutableListOf<Any>()
4252

4353
fun build(): EntitiesGraphQLQuery {
4454
return EntitiesGraphQLQuery(representations)
4555
}
4656

4757
fun addRepresentationAsVariable(representation: Any): Builder {
48-
representations.add(mapper.convertValue(representation, HashMap::class.java))
58+
representations += mapper.convertValue(representation, HashMap::class.java)
4959
return this
5060
}
5161
}
5262

5363
companion object {
64+
private val mapper = ObjectMapper()
65+
private const val REPRESENTATIONS_NAME = "representations"
66+
5467
fun newRequest(): Builder {
5568
return Builder()
5669
}

graphql-dgs-codegen-client-core/src/main/kotlin/com/netflix/graphql/dgs/client/codegen/GraphQLQuery.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@
1616

1717
package com.netflix.graphql.dgs.client.codegen
1818

19-
import java.util.*
19+
import graphql.language.VariableDefinition
2020

2121
abstract class GraphQLQuery(val operation: String, val name: String?) {
22-
val input: MutableMap<String, Any> = LinkedHashMap()
22+
val input: MutableMap<String, Any> = mutableMapOf()
23+
val variableDefinitions = mutableListOf<VariableDefinition>()
2324

2425
constructor(operation: String) : this(operation, null)
2526
constructor() : this("query")

graphql-dgs-codegen-client-core/src/main/kotlin/com/netflix/graphql/dgs/client/codegen/GraphQLQueryRequest.kt

Lines changed: 31 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@
1616

1717
package com.netflix.graphql.dgs.client.codegen
1818

19+
import graphql.language.Argument
20+
import graphql.language.AstPrinter
21+
import graphql.language.Field
22+
import graphql.language.OperationDefinition
23+
import graphql.language.SelectionSet
1924
import graphql.schema.Coercing
2025

2126
class GraphQLQueryRequest(
@@ -30,35 +35,37 @@ class GraphQLQueryRequest(
3035
private val projectionSerializer = ProjectionSerializer(inputValueSerializer)
3136

3237
fun serialize(): String {
33-
val builder = StringBuilder()
34-
builder.append(query.getOperationType())
35-
if (query.name != null) {
36-
builder.append(" ").append(query.name)
38+
val operationDef = OperationDefinition.newOperationDefinition()
39+
40+
query.name?.let { operationDef.name(it) }
41+
query.getOperationType()?.let { operationDef.operation(OperationDefinition.Operation.valueOf(it.uppercase())) }
42+
43+
if (query.variableDefinitions.isNotEmpty()) {
44+
operationDef.variableDefinitions(query.variableDefinitions)
3745
}
38-
builder.append(" {").append(query.getOperationName())
39-
val input: Map<String, Any?> = query.input
40-
if (input.isNotEmpty()) {
41-
builder.append("(")
42-
val inputEntryIterator = input.entries.iterator()
43-
while (inputEntryIterator.hasNext()) {
44-
val (key, value) = inputEntryIterator.next()
45-
builder.append(key)
46-
builder.append(": ")
47-
builder.append(inputValueSerializer.serialize(value))
48-
if (inputEntryIterator.hasNext()) {
49-
builder.append(", ")
46+
47+
val selection = Field.newField(query.getOperationName())
48+
if (query.input.isNotEmpty()) {
49+
selection.arguments(
50+
query.input.map { (name, value) ->
51+
Argument(name, inputValueSerializer.toValue(value))
5052
}
51-
}
52-
builder.append(")")
53+
)
5354
}
5455

55-
if (projection is BaseSubProjectionNode<*, *>) {
56-
builder.append(projectionSerializer.serialize(projection.root() as BaseProjectionNode))
57-
} else if (projection != null) {
58-
builder.append(projectionSerializer.serialize(projection))
56+
if (projection != null) {
57+
val selectionSet = if (projection is BaseSubProjectionNode<*, *>) {
58+
projectionSerializer.toSelectionSet(projection.root() as BaseProjectionNode)
59+
} else {
60+
projectionSerializer.toSelectionSet(projection)
61+
}
62+
if (selectionSet.selections.isNotEmpty()) {
63+
selection.selectionSet(selectionSet)
64+
}
5965
}
6066

61-
builder.append(" }")
62-
return builder.toString()
67+
operationDef.selectionSet(SelectionSet.newSelectionSet().selection(selection.build()).build())
68+
69+
return AstPrinter.printAst(operationDef.build())
6370
}
6471
}

graphql-dgs-codegen-client-core/src/main/kotlin/com/netflix/graphql/dgs/client/codegen/InputValueSerializer.kt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,15 @@ class InputValueSerializer(private val scalars: Map<Class<*>, Coercing<*, *>> =
6868
return AstPrinter.printAst(toValue(input))
6969
}
7070

71-
private fun toValue(input: Any?): Value<*> {
71+
fun toValue(input: Any?): Value<*> {
7272
if (input == null) {
7373
return NullValue.newNullValue().build()
7474
}
7575

76+
if (input is Value<*>) {
77+
return input
78+
}
79+
7680
if (input::class.java in scalars) {
7781
return scalars.getValue(input::class.java).valueToLiteral(input)
7882
}

graphql-dgs-codegen-client-core/src/main/kotlin/com/netflix/graphql/dgs/client/codegen/ProjectionSerializer.kt

Lines changed: 48 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -16,52 +16,63 @@
1616

1717
package com.netflix.graphql.dgs.client.codegen
1818

19-
import java.util.*
19+
import graphql.language.Argument
20+
import graphql.language.AstPrinter
21+
import graphql.language.Field
22+
import graphql.language.InlineFragment
23+
import graphql.language.SelectionSet
24+
import graphql.language.TypeName
2025

2126
class ProjectionSerializer(private val inputValueSerializer: InputValueSerializer) {
2227

23-
fun serialize(projection: BaseProjectionNode, isFragment: Boolean = false): String {
24-
if (projection.fields.isEmpty() && projection.fragments.isEmpty()) {
25-
return ""
26-
}
27-
28-
val prefix = if (isFragment) {
29-
val schemaType = projection.schemaType.orElse(
30-
projection::class.java.name
31-
.substringAfterLast(".")
32-
.substringAfterLast("_")
33-
.substringBefore("Projection")
34-
)
35-
"... on $schemaType { "
36-
} else {
37-
"{ "
38-
}
28+
fun toSelectionSet(projection: BaseProjectionNode): SelectionSet {
29+
val selectionSet = SelectionSet.newSelectionSet()
3930

40-
val joiner = StringJoiner(" ", prefix, " }")
41-
projection.fields.forEach { (key, value) ->
42-
val field = if (projection.inputArguments[key] != null) {
43-
val inputArgsJoiner = StringJoiner(", ", "(", ")")
44-
projection.inputArguments[key]?.forEach {
45-
inputArgsJoiner.add("${it.name}: ${inputValueSerializer.serialize(it.value)}")
31+
for ((fieldName, value) in projection.fields) {
32+
val fieldSelection = Field.newField()
33+
.name(fieldName)
34+
.arguments(
35+
projection.inputArguments[fieldName].orEmpty().map { (argName, values) ->
36+
Argument(argName, inputValueSerializer.toValue(values))
37+
}
38+
)
39+
if (value is BaseProjectionNode) {
40+
val fieldSelectionSet = toSelectionSet(value)
41+
if (fieldSelectionSet.selections.isNotEmpty()) {
42+
fieldSelection.selectionSet(fieldSelectionSet)
4643
}
47-
48-
key + inputArgsJoiner.toString()
49-
} else {
50-
key
44+
} else if (value != null) {
45+
fieldSelection.selectionSet(
46+
SelectionSet.newSelectionSet()
47+
.selection(Field.newField(value.toString()).build())
48+
.build()
49+
)
5150
}
51+
selectionSet.selection(fieldSelection.build())
52+
}
5253

53-
joiner.add(field)
54-
if (value != null) {
55-
if (value is BaseProjectionNode) {
56-
joiner.add(" ").add(serialize(value))
57-
} else {
58-
joiner.add(" ").add(value.toString())
54+
for (fragment in projection.fragments) {
55+
val typeCondition = fragment.schemaType.map { TypeName(it) }
56+
.orElseGet {
57+
val className = fragment::class.simpleName
58+
?: throw AssertionError("Unable to determine class name for projection: $fragment")
59+
TypeName(
60+
className.substringAfterLast("_")
61+
.substringBefore("Projection")
62+
)
5963
}
60-
}
61-
}
6264

63-
projection.fragments.forEach { joiner.add(serialize(it, true)) }
65+
selectionSet.selection(
66+
InlineFragment.newInlineFragment()
67+
.typeCondition(typeCondition)
68+
.selectionSet(toSelectionSet(fragment))
69+
.build()
70+
)
71+
}
72+
return selectionSet.build()
73+
}
6474

65-
return joiner.toString()
75+
fun serialize(projection: BaseProjectionNode): String {
76+
return AstPrinter.printAst(toSelectionSet(projection))
6677
}
6778
}

0 commit comments

Comments
 (0)