Skip to content

Commit d3dbcad

Browse files
authored
Merge pull request #22 from aPureBase/pgutkowski-pull-51
support nested fragments
2 parents 5276f15 + a34971c commit d3dbcad

File tree

8 files changed

+264
-17
lines changed

8 files changed

+264
-17
lines changed

src/main/kotlin/com/apurebase/kgraphql/schema/execution/Execution.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ sealed class Execution {
2424

2525
class Fragment(
2626
val condition: TypeCondition,
27-
val elements : List<Execution.Node>,
27+
val elements : List<Execution>,
2828
val directives: Map<Directive, Arguments?>?
2929
) : Execution()
3030

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.apurebase.kgraphql.schema.execution
2+
3+
import com.fasterxml.jackson.databind.JsonNode
4+
import com.fasterxml.jackson.databind.node.ObjectNode
5+
6+
fun MutableMap<String, JsonNode?>.merge(key: String, node: JsonNode?): MutableMap<String, JsonNode?> {
7+
merge(key, node, this::get, this::set)
8+
return this
9+
}
10+
11+
fun ObjectNode.merge(other: ObjectNode) {
12+
other.fields().forEach {
13+
merge(it.key, it.value)
14+
}
15+
}
16+
17+
fun ObjectNode.merge(key: String, node: JsonNode?) {
18+
merge(key, node, this::get, this::set)
19+
}
20+
21+
fun merge(key: String, node: JsonNode?, get: (String) -> JsonNode?, set: (String, JsonNode?) -> Any?) {
22+
val existingNode = get(key)
23+
if (existingNode != null) {
24+
when {
25+
node == null -> throw IllegalStateException("trying to merge null with non-null for $key")
26+
node is ObjectNode -> {
27+
check(existingNode is ObjectNode) { "trying to merge object with simple node for $key" }
28+
existingNode.merge(node)
29+
}
30+
existingNode is ObjectNode -> throw IllegalStateException("trying to merge simple node with object node for $key")
31+
node != existingNode -> throw IllegalStateException("trying to merge different simple nodes for $key")
32+
}
33+
} else {
34+
set(key, node)
35+
}
36+
}

src/main/kotlin/com/apurebase/kgraphql/schema/execution/ParallelRequestExecutor.kt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,12 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor, Coro
227227
if (include) {
228228
if (expectedType.kind == TypeKind.OBJECT || expectedType.kind == TypeKind.INTERFACE) {
229229
if (expectedType.isInstance(value)) {
230-
return container.elements.map { handleProperty(ctx, value, it, expectedType) }.toMap()
230+
return container.elements.flatMap { child ->
231+
when (child) {
232+
is Execution.Fragment -> handleFragment(ctx, value, child).toList()
233+
else -> listOf(handleProperty(ctx, value, child, expectedType))
234+
}
235+
}.fold(mutableMapOf()) { map, entry -> map.merge(entry.first, entry.second) }
231236
}
232237
} else {
233238
throw IllegalStateException("fragments can be specified on object types, interfaces, and unions")

src/main/kotlin/com/apurebase/kgraphql/schema/structure2/RequestInterpreter.kt

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,21 +59,8 @@ class RequestInterpreter(val schemaModel: SchemaModel) {
5959
return children
6060
}
6161

62-
private fun handleReturnTypeChildOrFragment(node: SelectionNode, returnType: Type): Execution {
63-
val unwrappedType = returnType.unwrapped()
64-
65-
return when(node){
66-
is Fragment -> {
67-
val conditionType = findFragmentType(node, unwrappedType)
68-
val condition = TypeCondition(conditionType)
69-
val elements = node.fragmentGraph.map { conditionType.handleSelection(it) }
70-
Execution.Fragment(condition, elements, node.directives?.lookup())
71-
}
72-
else -> {
73-
unwrappedType.handleSelection(node)
74-
}
75-
}
76-
}
62+
private fun handleReturnTypeChildOrFragment(node: SelectionNode, returnType: Type) =
63+
returnType.unwrapped().handleSelectionFieldOrFragment(node)
7764

7865
private fun findFragmentType(fragment: Fragment, enclosingType: Type) : Type {
7966
when(fragment){
@@ -91,6 +78,18 @@ class RequestInterpreter(val schemaModel: SchemaModel) {
9178
}
9279
}
9380

81+
private fun Type.handleSelectionFieldOrFragment(node: SelectionNode): Execution = when(node) {
82+
is Fragment -> {
83+
val conditionType = findFragmentType(node, this)
84+
val condition = TypeCondition(conditionType)
85+
val elements = node.fragmentGraph.map { conditionType.handleSelectionFieldOrFragment(it) }
86+
Execution.Fragment(condition, elements, node.directives?.lookup())
87+
}
88+
else -> {
89+
this.handleSelection(node)
90+
}
91+
}
92+
9493
private fun Type.handleSelection(selectionNode: SelectionNode, variables: List<OperationVariable>? = null): Execution.Node {
9594
val field = this[selectionNode.key]
9695

src/test/kotlin/com/apurebase/kgraphql/integration/QueryTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,39 @@ class QueryTest : BaseSchemaTest() {
171171
assertThat(map.extract<Int>("data/film/director/age"), equalTo(prestige.director.age))
172172
}
173173

174+
@Test
175+
fun `query with nested external fragment`() {
176+
val map = execute("""
177+
{
178+
film {
179+
title
180+
...dir
181+
}
182+
}
183+
184+
fragment dir on Film {
185+
director {
186+
name
187+
}
188+
...dirAge
189+
}
190+
191+
fragment dirIntermediate on Film {
192+
...dirAge
193+
}
194+
195+
fragment dirAge on Film {
196+
director {
197+
age
198+
}
199+
}
200+
""".trimIndent())
201+
assertNoErrors(map)
202+
assertThat(map.extract<String>("data/film/title"), equalTo(prestige.title))
203+
assertThat(map.extract<String>("data/film/director/name"), equalTo(prestige.director.name))
204+
assertThat(map.extract<Int>("data/film/director/age"), equalTo(prestige.director.age))
205+
}
206+
174207
@Test
175208
fun `query with missing selection set`(){
176209
expect<RequestException>("Missing selection set on property film of type Film"){
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.apurebase.kgraphql.merge
2+
3+
import com.fasterxml.jackson.databind.JsonNode
4+
import com.fasterxml.jackson.databind.node.JsonNodeFactory
5+
import com.apurebase.kgraphql.expect
6+
import com.apurebase.kgraphql.schema.execution.merge
7+
import org.hamcrest.CoreMatchers.equalTo
8+
import org.hamcrest.MatcherAssert.assertThat
9+
import org.junit.Test
10+
11+
class MapMergeTest {
12+
private val jsonNodeFactory = JsonNodeFactory.instance
13+
14+
@Test
15+
fun `merge should add property`() {
16+
val existing = createMap("param1" to jsonNodeFactory.textNode("value1"))
17+
val update: JsonNode? = jsonNodeFactory.textNode("value2")
18+
19+
existing.merge("param2", update)
20+
21+
assertThat(existing.get("param2"), equalTo(update))
22+
}
23+
24+
@Test
25+
fun `merge should add nested property`() {
26+
val existing = createMap("param1" to jsonNodeFactory.textNode("value1"))
27+
val update: JsonNode? = jsonNodeFactory.objectNode().put("param2", "value2")
28+
29+
existing.merge("sub", update)
30+
31+
assertThat(existing.get("sub"), equalTo(update))
32+
}
33+
34+
@Test
35+
fun `merge should not change simple node`() {
36+
val existingValue: JsonNode? = jsonNodeFactory.textNode("value1")
37+
val existing = createMap("param" to existingValue)
38+
val update = jsonNodeFactory.textNode("value2")
39+
40+
expect<IllegalStateException>("different simple nodes") { existing.merge("param", update) }
41+
42+
assertThat(existing.get("param"), equalTo(existingValue))
43+
}
44+
45+
@Test
46+
fun `merge should not merge simple node with object node`() {
47+
val existingValue: JsonNode? = jsonNodeFactory.textNode("value1")
48+
val existing = createMap("param" to existingValue)
49+
val update = jsonNodeFactory.objectNode()
50+
51+
expect<IllegalStateException>("merge object with simple node") { existing.merge("param", update) }
52+
53+
val expected: JsonNode? = jsonNodeFactory.textNode("value1")
54+
assertThat(existing.get("param"), equalTo(expected))
55+
}
56+
57+
@Test
58+
fun `merge should not merge object node with simple node`() {
59+
val existingObj: JsonNode? = jsonNodeFactory.objectNode().put("other", "value1")
60+
val existing = createMap("param" to existingObj)
61+
val update = jsonNodeFactory.textNode("value2")
62+
63+
expect<IllegalStateException>("merge simple node with object node") { existing.merge("param", update) }
64+
65+
assertThat(existing.get("param"), equalTo(existingObj))
66+
}
67+
68+
private fun createMap(vararg pairs: Pair<String, JsonNode?>) = mutableMapOf(*pairs)
69+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package com.apurebase.kgraphql.merge
2+
3+
import com.fasterxml.jackson.databind.JsonNode
4+
import com.fasterxml.jackson.databind.node.JsonNodeFactory
5+
import com.apurebase.kgraphql.expect
6+
import com.apurebase.kgraphql.schema.execution.merge
7+
import org.hamcrest.CoreMatchers.equalTo
8+
import org.hamcrest.MatcherAssert.assertThat
9+
import org.junit.Test
10+
11+
class ObjectNodeMergeTest {
12+
private val jsonNodeFactory = JsonNodeFactory.instance
13+
14+
@Test
15+
fun `merge should add property`() {
16+
val existing = jsonNodeFactory.objectNode().put("param1", "value1")
17+
val update = jsonNodeFactory.objectNode().put("param2", "value2")
18+
19+
existing.merge(update)
20+
21+
val expected: JsonNode? = jsonNodeFactory.textNode("value2")
22+
assertThat(existing.get("param2"), equalTo(expected))
23+
}
24+
25+
@Test
26+
fun `merge should add nested property`() {
27+
val existing = jsonNodeFactory.objectNode().put("param1", "value1")
28+
val update = jsonNodeFactory.objectNode()
29+
update.putObject("sub").put("param2", "value2")
30+
31+
existing.merge(update)
32+
33+
val expected: JsonNode? = jsonNodeFactory.objectNode().put("param2", "value2")
34+
assertThat(existing.get("sub"), equalTo(expected))
35+
}
36+
37+
@Test
38+
fun `merge should not change simple node`() {
39+
val existing = jsonNodeFactory.objectNode().put("param", "value1")
40+
val update = jsonNodeFactory.objectNode().put("param", "value2")
41+
42+
expect<IllegalStateException>("different simple nodes") { existing.merge(update) }
43+
44+
val expected: JsonNode? = jsonNodeFactory.textNode("value1")
45+
assertThat(existing.get("param"), equalTo(expected))
46+
}
47+
48+
@Test
49+
fun `merge should not merge simple node with object node`() {
50+
val existing = jsonNodeFactory.objectNode().put("param", "value1")
51+
val update = jsonNodeFactory.objectNode()
52+
update.putObject("param")
53+
54+
expect<IllegalStateException>("merge object with simple node") { existing.merge(update) }
55+
56+
val expected: JsonNode? = jsonNodeFactory.textNode("value1")
57+
assertThat(existing.get("param"), equalTo(expected))
58+
}
59+
60+
@Test
61+
fun `merge should not merge object node with simple node`() {
62+
val existing = jsonNodeFactory.objectNode()
63+
val existingObj: JsonNode? = existing.putObject("param").put("other", "value1")
64+
val update = jsonNodeFactory.objectNode().put("param", "value2")
65+
66+
expect<IllegalStateException>("merge simple node with object node") { existing.merge(update) }
67+
68+
assertThat(existing.get("param"), equalTo(existingObj))
69+
}
70+
}

src/test/kotlin/com/apurebase/kgraphql/request/DocumentParserTest.kt

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,4 +226,39 @@ class DocumentParserTest {
226226
)
227227
assertThat(map.first().selectionTree, equalTo(expected))
228228
}
229+
230+
@Test
231+
fun `nested fragment parsing`() {
232+
val map = graphParser.parseDocument("""
233+
{
234+
hero {
235+
id
236+
...heroName
237+
}
238+
}
239+
240+
fragment heroName on Hero {
241+
name {
242+
real
243+
}
244+
...heroNameDetails
245+
}
246+
247+
fragment heroNameDetails on Hero {
248+
name {
249+
asHero
250+
}
251+
}
252+
""".trimIndent())
253+
val expected = SelectionTree(
254+
branch("hero",
255+
leaf("id"),
256+
extFragment("...heroName", "Hero",
257+
branch("name", *leafs("real")),
258+
extFragment("...heroNameDetails", "Hero", branch("name", *leafs("asHero")))
259+
)
260+
)
261+
)
262+
assertThat(map.first().selectionTree, equalTo(expected))
263+
}
229264
}

0 commit comments

Comments
 (0)