Skip to content

Commit f2e96a1

Browse files
[HOT-304948] Fix empty namespace emitted as null
1 parent 81a0db2 commit f2e96a1

3 files changed

Lines changed: 150 additions & 1 deletion

File tree

lib/src/main/java/graphql/nadel/result/NadelResultMerger.kt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,18 @@ internal object NadelResultMerger {
121121
// NOTE: please ensure all fields are from object types and will NOT have multiple field defs
122122
// Other code in this file relies on this contract
123123
for (field in topLevelFields) {
124+
val namespaced = isNamespacedField(field, engineSchema)
125+
126+
if (namespaced && field.children.isEmpty()) {
127+
continue
128+
}
129+
124130
val requiredChildFields = requiredFields
125131
.computeIfAbsent(NadelResultKey(field.resultKey)) {
126132
mutableListOf()
127133
}
128134

129-
if (isNamespacedField(field, engineSchema)) {
135+
if (namespaced) {
130136
requiredChildFields.addAll(field.children)
131137
}
132138
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package graphql.nadel.tests.next.fixtures.namespaced
2+
3+
import graphql.nadel.NadelExecutionHints
4+
import graphql.nadel.tests.next.NadelIntegrationTest
5+
6+
/**
7+
* Regression test for the HOT/bug where an empty namespace was materialised as `"jira": null`.
8+
*
9+
* When every child of a namespaced field is skipped (e.g. `@skip(if: true)`), the namespace has no
10+
* children, no service call is made, and it must be OMITTED from the response entirely - NOT emitted
11+
* as `jira: null`. Emitting `null` changed the response shape and tripped a downstream Relay non-null
12+
* handler, turning the whole response into `data: null`.
13+
*
14+
* Expected result: `{ "hello": "world" }` with no `jira` key.
15+
*/
16+
class EmptyNamespaceIsOmittedTest : NadelIntegrationTest(
17+
query = """
18+
query {
19+
hello
20+
jira {
21+
sprint @skip(if: true)
22+
}
23+
}
24+
""".trimIndent(),
25+
services = listOf(
26+
Service(
27+
name = "monolith",
28+
overallSchema = """
29+
type Query {
30+
hello: String
31+
jira: JiraQuery @namespaced
32+
}
33+
type JiraQuery {
34+
sprint: String
35+
}
36+
""".trimIndent(),
37+
underlyingSchema = """
38+
type Query {
39+
hello: String
40+
jira: JiraQuery
41+
}
42+
type JiraQuery {
43+
sprint: String
44+
}
45+
""".trimIndent(),
46+
runtimeWiring = { wiring ->
47+
wiring
48+
.type("Query") { type ->
49+
type
50+
.dataFetcher("hello") { "world" }
51+
.dataFetcher("jira") { emptyMap<String, Any?>() }
52+
}
53+
},
54+
),
55+
),
56+
) {
57+
override fun makeExecutionHints(): NadelExecutionHints.Builder {
58+
return super.makeExecutionHints()
59+
.newResultMergerAndNamespacedTypename { true }
60+
}
61+
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
// @formatter:off
2+
package graphql.nadel.tests.next.fixtures.namespaced
3+
4+
import graphql.nadel.tests.next.ExpectedNadelResult
5+
import graphql.nadel.tests.next.ExpectedServiceCall
6+
import graphql.nadel.tests.next.TestSnapshot
7+
import graphql.nadel.tests.next.listOfJsonStrings
8+
import kotlin.Suppress
9+
import kotlin.collections.List
10+
import kotlin.collections.listOf
11+
12+
private suspend fun main() {
13+
graphql.nadel.tests.next.update<EmptyNamespaceIsOmittedTest>()
14+
}
15+
16+
/**
17+
* This class is generated. Do NOT modify.
18+
*
19+
* Refer to [graphql.nadel.tests.next.UpdateTestSnapshots]
20+
*/
21+
@Suppress("unused")
22+
public class EmptyNamespaceIsOmittedTestSnapshot : TestSnapshot() {
23+
/**
24+
* Query
25+
*
26+
* ```graphql
27+
* query {
28+
* hello
29+
* jira {
30+
* sprint @skip(if: true)
31+
* }
32+
* }
33+
* ```
34+
*
35+
* Variables
36+
*
37+
* ```json
38+
* {}
39+
* ```
40+
*/
41+
override val calls: List<ExpectedServiceCall> = listOf(
42+
ExpectedServiceCall(
43+
service = "monolith",
44+
query = """
45+
| {
46+
| hello
47+
| }
48+
""".trimMargin(),
49+
variables = "{}",
50+
result = """
51+
| {
52+
| "data": {
53+
| "hello": "world"
54+
| }
55+
| }
56+
""".trimMargin(),
57+
delayedResults = listOfJsonStrings(
58+
),
59+
),
60+
)
61+
62+
/**
63+
* ```json
64+
* {
65+
* "data": {
66+
* "hello": "world"
67+
* }
68+
* }
69+
* ```
70+
*/
71+
override val result: ExpectedNadelResult = ExpectedNadelResult(
72+
result = """
73+
| {
74+
| "data": {
75+
| "hello": "world"
76+
| }
77+
| }
78+
""".trimMargin(),
79+
delayedResults = listOfJsonStrings(
80+
),
81+
)
82+
}

0 commit comments

Comments
 (0)