Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/src/main/java/graphql/nadel/result/NadelResultMerger.kt
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,13 @@ internal object NadelResultMerger {

// Ensure field is present in result
if (topLevelResultKey.value !in data) {
data[topLevelResultKey.value] = null
val topLevelField = topLevelFields.first { it.resultKey == topLevelResultKey.value }
data[topLevelResultKey.value] =
if (isNamespacedField(topLevelField, engineSchema) && children.isEmpty()) {
mutableMapOf<String, Any?>()
} else {
null
}
}

if (children.isNotEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package graphql.nadel.tests.next.fixtures.namespaced

import graphql.nadel.NadelExecutionHints
import graphql.nadel.tests.next.NadelIntegrationTest

/**
* Regression test for the bug where an empty namespace was materialised as `"namespace": null`.
*
* When every child of a namespaced field is skipped (e.g. `@skip(if: true)`), the namespace has no
* children and no service call is made. It must be materialised as an empty object `"namespace": {}`,
* NOT as `"namespace": null` (which tripped a downstream non-null handler, turning the whole
* response into `data: null`).
*
* Expected result: `{ "hello": "world", "namespace": {} }`.
*/
class EmptyNamespaceIsEmptyObjectTest : NadelIntegrationTest(
query = """
query {
hello
namespace {
foo @skip(if: true)
}
}
""".trimIndent(),
services = listOf(
Service(
name = "monolith",
overallSchema = """
type Query {
hello: String
namespace: NamespaceQuery @namespaced
}
type NamespaceQuery {
foo: String
}
""".trimIndent(),
underlyingSchema = """
type Query {
hello: String
namespace: NamespaceQuery
}
type NamespaceQuery {
foo: String
}
""".trimIndent(),
runtimeWiring = { wiring ->
wiring
.type("Query") { type ->
type
.dataFetcher("hello") { "world" }
.dataFetcher("namespace") { emptyMap<String, Any?>() }
}
},
),
),
) {
override fun makeExecutionHints(): NadelExecutionHints.Builder {
return super.makeExecutionHints()
.newResultMergerAndNamespacedTypename { true }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// @formatter:off
package graphql.nadel.tests.next.fixtures.namespaced

import graphql.nadel.tests.next.ExpectedNadelResult
import graphql.nadel.tests.next.ExpectedServiceCall
import graphql.nadel.tests.next.TestSnapshot
import graphql.nadel.tests.next.listOfJsonStrings
import kotlin.Suppress
import kotlin.collections.List
import kotlin.collections.listOf

private suspend fun main() {
graphql.nadel.tests.next.update<EmptyNamespaceIsEmptyObjectTest>()
}

/**
* This class is generated. Do NOT modify.
*
* Refer to [graphql.nadel.tests.next.UpdateTestSnapshots]
*/
@Suppress("unused")
public class EmptyNamespaceIsEmptyObjectTestSnapshot : TestSnapshot() {
/**
* Query
*
* ```graphql
* query {
* hello
* namespace {
* foo @skip(if: true)
* }
* }
* ```
*
* Variables
*
* ```json
* {}
* ```
*/
override val calls: List<ExpectedServiceCall> = listOf(
ExpectedServiceCall(
service = "monolith",
query = """
| {
| hello
| }
""".trimMargin(),
variables = "{}",
result = """
| {
| "data": {
| "hello": "world"
| }
| }
""".trimMargin(),
delayedResults = listOfJsonStrings(
),
),
)

/**
* ```json
* {
* "data": {
* "hello": "world",
* "namespace": {}
* }
* }
* ```
*/
override val result: ExpectedNadelResult = ExpectedNadelResult(
result = """
| {
| "data": {
| "hello": "world",
| "namespace": {}
| }
| }
""".trimMargin(),
delayedResults = listOfJsonStrings(
),
)
}
Loading