Skip to content
This repository has been archived by the owner on May 16, 2019. It is now read-only.

Commit

Permalink
Merge 9e3d582 into 42fef4a
Browse files Browse the repository at this point in the history
  • Loading branch information
mdnorman committed Feb 2, 2019
2 parents 42fef4a + 9e3d582 commit 734d849
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 17 deletions.
Expand Up @@ -24,7 +24,7 @@ sealed class Execution {

class Fragment(
val condition: TypeCondition,
val elements : List<Execution.Node>,
val elements : List<Execution>,
val directives: Map<Directive, Arguments?>?
) : Execution()

Expand Down
@@ -0,0 +1,36 @@
package com.github.pgutkowski.kgraphql.schema.execution

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.ObjectNode

fun MutableMap<String, JsonNode?>.merge(key: String, node: JsonNode?): MutableMap<String, JsonNode?> {
merge(key, node, this::get, this::set)
return this
}

fun ObjectNode.merge(other: ObjectNode) {
other.fields().forEach {
merge(it.key, it.value)
}
}

fun ObjectNode.merge(key: String, node: JsonNode?) {
merge(key, node, this::get, this::set)
}

fun merge(key: String, node: JsonNode?, get: (String) -> JsonNode?, set: (String, JsonNode?) -> Any?) {
val existingNode = get(key)
if (existingNode != null) {
when {
node == null -> throw IllegalStateException("trying to merge null with non-null for $key")
node is ObjectNode -> {
check(existingNode is ObjectNode) { "trying to merge object with simple node for $key" }
existingNode.merge(node)
}
existingNode is ObjectNode -> throw IllegalStateException("trying to merge simple node with object node for $key")
node != existingNode -> throw IllegalStateException("trying to merge different simple nodes for $key")
}
} else {
set(key, node)
}
}
Expand Up @@ -222,7 +222,12 @@ class ParallelRequestExecutor(val schema: DefaultSchema) : RequestExecutor, Coro
if (include) {
if (expectedType.kind == TypeKind.OBJECT || expectedType.kind == TypeKind.INTERFACE) {
if (expectedType.isInstance(value)) {
return container.elements.map { handleProperty(ctx, value, it, expectedType) }.toMap()
return container.elements.flatMap { child ->
when (child) {
is Execution.Fragment -> handleFragment(ctx, value, child).toList()
else -> listOf(handleProperty(ctx, value, child, expectedType))
}
}.fold(mutableMapOf()) { map, entry -> map.merge(entry.first, entry.second) }
}
} else {
throw IllegalStateException("fragments can be specified on object types, interfaces, and unions")
Expand Down
Expand Up @@ -59,21 +59,8 @@ class RequestInterpreter(val schemaModel: SchemaModel) {
return children
}

private fun handleReturnTypeChildOrFragment(node: SelectionNode, returnType: Type): Execution {
val unwrappedType = returnType.unwrapped()

return when(node){
is Fragment -> {
val conditionType = findFragmentType(node, unwrappedType)
val condition = TypeCondition(conditionType)
val elements = node.fragmentGraph.map { conditionType.handleSelection(it) }
Execution.Fragment(condition, elements, node.directives?.lookup())
}
else -> {
unwrappedType.handleSelection(node)
}
}
}
private fun handleReturnTypeChildOrFragment(node: SelectionNode, returnType: Type) =
returnType.unwrapped().handleSelectionFieldOrFragment(node)

private fun findFragmentType(fragment: Fragment, enclosingType: Type) : Type {
when(fragment){
Expand All @@ -91,6 +78,18 @@ class RequestInterpreter(val schemaModel: SchemaModel) {
}
}

private fun Type.handleSelectionFieldOrFragment(node: SelectionNode): Execution = when(node) {
is Fragment -> {
val conditionType = findFragmentType(node, this)
val condition = TypeCondition(conditionType)
val elements = node.fragmentGraph.map { conditionType.handleSelectionFieldOrFragment(it) }
Execution.Fragment(condition, elements, node.directives?.lookup())
}
else -> {
this.handleSelection(node)
}
}

private fun Type.handleSelection(selectionNode: SelectionNode, variables: List<OperationVariable>? = null): Execution.Node {
val field = this[selectionNode.key]

Expand Down
Expand Up @@ -171,6 +171,39 @@ class QueryTest : BaseSchemaTest() {
assertThat(map.extract<Int>("data/film/director/age"), equalTo(prestige.director.age))
}

@Test
fun `query with nested external fragment`() {
val map = execute("""
{
film {
title
...dir
}
}
fragment dir on Film {
director {
name
}
...dirAge
}
fragment dirIntermediate on Film {
...dirAge
}
fragment dirAge on Film {
director {
age
}
}
""".trimIndent())
assertNoErrors(map)
assertThat(map.extract<String>("data/film/title"), equalTo(prestige.title))
assertThat(map.extract<String>("data/film/director/name"), equalTo(prestige.director.name))
assertThat(map.extract<Int>("data/film/director/age"), equalTo(prestige.director.age))
}

@Test
fun `query with missing selection set`(){
expect<RequestException>("Missing selection set on property film of type Film"){
Expand Down
@@ -0,0 +1,69 @@
package com.github.pgutkowski.kgraphql.merge

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.github.pgutkowski.kgraphql.expect
import com.github.pgutkowski.kgraphql.schema.execution.merge
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test

class MapMergeTest {
private val jsonNodeFactory = JsonNodeFactory.instance

@Test
fun `merge should add property`() {
val existing = createMap("param1" to jsonNodeFactory.textNode("value1"))
val update: JsonNode? = jsonNodeFactory.textNode("value2")

existing.merge("param2", update)

assertThat(existing.get("param2"), equalTo(update))
}

@Test
fun `merge should add nested property`() {
val existing = createMap("param1" to jsonNodeFactory.textNode("value1"))
val update: JsonNode? = jsonNodeFactory.objectNode().put("param2", "value2")

existing.merge("sub", update)

assertThat(existing.get("sub"), equalTo(update))
}

@Test
fun `merge should not change simple node`() {
val existingValue: JsonNode? = jsonNodeFactory.textNode("value1")
val existing = createMap("param" to existingValue)
val update = jsonNodeFactory.textNode("value2")

expect<IllegalStateException>("different simple nodes") { existing.merge("param", update) }

assertThat(existing.get("param"), equalTo(existingValue))
}

@Test
fun `merge should not merge simple node with object node`() {
val existingValue: JsonNode? = jsonNodeFactory.textNode("value1")
val existing = createMap("param" to existingValue)
val update = jsonNodeFactory.objectNode()

expect<IllegalStateException>("merge object with simple node") { existing.merge("param", update) }

val expected: JsonNode? = jsonNodeFactory.textNode("value1")
assertThat(existing.get("param"), equalTo(expected))
}

@Test
fun `merge should not merge object node with simple node`() {
val existingObj: JsonNode? = jsonNodeFactory.objectNode().put("other", "value1")
val existing = createMap("param" to existingObj)
val update = jsonNodeFactory.textNode("value2")

expect<IllegalStateException>("merge simple node with object node") { existing.merge("param", update) }

assertThat(existing.get("param"), equalTo(existingObj))
}

private fun createMap(vararg pairs: Pair<String, JsonNode?>) = mutableMapOf(*pairs)
}
@@ -0,0 +1,70 @@
package com.github.pgutkowski.kgraphql.merge

import com.fasterxml.jackson.databind.JsonNode
import com.fasterxml.jackson.databind.node.JsonNodeFactory
import com.github.pgutkowski.kgraphql.expect
import com.github.pgutkowski.kgraphql.schema.execution.merge
import org.hamcrest.CoreMatchers.equalTo
import org.hamcrest.MatcherAssert.assertThat
import org.junit.Test

class ObjectNodeMergeTest {
private val jsonNodeFactory = JsonNodeFactory.instance

@Test
fun `merge should add property`() {
val existing = jsonNodeFactory.objectNode().put("param1", "value1")
val update = jsonNodeFactory.objectNode().put("param2", "value2")

existing.merge(update)

val expected: JsonNode? = jsonNodeFactory.textNode("value2")
assertThat(existing.get("param2"), equalTo(expected))
}

@Test
fun `merge should add nested property`() {
val existing = jsonNodeFactory.objectNode().put("param1", "value1")
val update = jsonNodeFactory.objectNode()
update.putObject("sub").put("param2", "value2")

existing.merge(update)

val expected: JsonNode? = jsonNodeFactory.objectNode().put("param2", "value2")
assertThat(existing.get("sub"), equalTo(expected))
}

@Test
fun `merge should not change simple node`() {
val existing = jsonNodeFactory.objectNode().put("param", "value1")
val update = jsonNodeFactory.objectNode().put("param", "value2")

expect<IllegalStateException>("different simple nodes") { existing.merge(update) }

val expected: JsonNode? = jsonNodeFactory.textNode("value1")
assertThat(existing.get("param"), equalTo(expected))
}

@Test
fun `merge should not merge simple node with object node`() {
val existing = jsonNodeFactory.objectNode().put("param", "value1")
val update = jsonNodeFactory.objectNode()
update.putObject("param")

expect<IllegalStateException>("merge object with simple node") { existing.merge(update) }

val expected: JsonNode? = jsonNodeFactory.textNode("value1")
assertThat(existing.get("param"), equalTo(expected))
}

@Test
fun `merge should not merge object node with simple node`() {
val existing = jsonNodeFactory.objectNode()
val existingObj: JsonNode? = existing.putObject("param").put("other", "value1")
val update = jsonNodeFactory.objectNode().put("param", "value2")

expect<IllegalStateException>("merge simple node with object node") { existing.merge(update) }

assertThat(existing.get("param"), equalTo(existingObj))
}
}
Expand Up @@ -226,4 +226,39 @@ class DocumentParserTest {
)
assertThat(map.first().selectionTree, equalTo(expected))
}

@Test
fun `nested fragment parsing`() {
val map = graphParser.parseDocument("""
{
hero {
id
...heroName
}
}
fragment heroName on Hero {
name {
real
}
...heroNameDetails
}
fragment heroNameDetails on Hero {
name {
asHero
}
}
""".trimIndent())
val expected = SelectionTree(
branch("hero",
leaf("id"),
extFragment("...heroName", "Hero",
branch("name", *leafs("real")),
extFragment("...heroNameDetails", "Hero", branch("name", *leafs("asHero")))
)
)
)
assertThat(map.first().selectionTree, equalTo(expected))
}
}

0 comments on commit 734d849

Please sign in to comment.