Skip to content

Commit 89e65f0

Browse files
Fixed issue with null value serialization when using GraphQL literals. (#6357)
1 parent e6ac5b3 commit 89e65f0

File tree

3 files changed

+176
-2
lines changed

3 files changed

+176
-2
lines changed

src/HotChocolate/AspNetCore/src/Transport.Abstractions/Serialization/Utf8JsonWriterHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ internal static void WriteFieldValue(
6161
Utf8JsonWriter writer,
6262
object? value)
6363
{
64-
if (value is null or FileReference or FileReferenceNode)
64+
if (value is null or NullValueNode or FileReference or FileReferenceNode)
6565
{
6666
writer.WriteNullValue();
6767
return;

src/HotChocolate/Fusion/test/Core.Tests/DemoIntegrationTests.cs

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,7 @@ query Requires {
13431343

13441344
Assert.Null(result.ExpectQueryResult().Errors);
13451345
}
1346-
1346+
13471347
[Fact]
13481348
public async Task Require_Data_In_Context_3()
13491349
{
@@ -1410,6 +1410,53 @@ query Large {
14101410
Assert.Null(result.ExpectQueryResult().Errors);
14111411
}
14121412

1413+
[Fact]
1414+
public async Task GetFirstPage_With_After_Null()
1415+
{
1416+
using var demoProject = await DemoProject.CreateAsync();
1417+
1418+
// act
1419+
var fusionGraph = await new FusionGraphComposer(logFactory: _logFactory).ComposeAsync(
1420+
new[]
1421+
{
1422+
demoProject.Appointment.ToConfiguration()
1423+
},
1424+
new FusionFeatureCollection(FusionFeatures.NodeField));
1425+
1426+
var executor = await new ServiceCollection()
1427+
.AddSingleton(demoProject.HttpClientFactory)
1428+
.AddSingleton(demoProject.WebSocketConnectionFactory)
1429+
.AddFusionGatewayServer()
1430+
.ConfigureFromDocument(SchemaFormatter.FormatAsDocument(fusionGraph))
1431+
.BuildRequestExecutorAsync();
1432+
1433+
var request = Parse(
1434+
"""
1435+
query AfterNull($after: String) {
1436+
appointments(after: $after) {
1437+
nodes {
1438+
id
1439+
}
1440+
}
1441+
}
1442+
""");
1443+
1444+
// act
1445+
var result = await executor.ExecuteAsync(
1446+
QueryRequestBuilder
1447+
.New()
1448+
.SetQuery(request)
1449+
.SetVariableValue("after", null)
1450+
.Create());
1451+
1452+
// assert
1453+
var snapshot = new Snapshot();
1454+
CollectSnapshotData(snapshot, request, result, fusionGraph);
1455+
await snapshot.MatchAsync();
1456+
1457+
Assert.Null(result.ExpectQueryResult().Errors);
1458+
}
1459+
14131460
public sealed class HotReloadConfiguration : IObservable<GatewayConfiguration>
14141461
{
14151462
private GatewayConfiguration _configuration;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
User Request
2+
---------------
3+
query AfterNull($after: String) {
4+
appointments(after: $after) {
5+
nodes {
6+
id
7+
}
8+
}
9+
}
10+
---------------
11+
12+
QueryPlan
13+
---------------
14+
{
15+
"document": "query AfterNull($after: String) { appointments(after: $after) { nodes { id } } }",
16+
"operation": "AfterNull",
17+
"rootNode": {
18+
"type": "Sequence",
19+
"nodes": [
20+
{
21+
"type": "Resolve",
22+
"subgraph": "Appointment",
23+
"document": "query AfterNull_1($after: String) { appointments(after: $after, before: null, first: null, last: null) { nodes { id } } }",
24+
"selectionSetId": 0,
25+
"forwardedVariables": [
26+
{
27+
"variable": "after"
28+
}
29+
]
30+
},
31+
{
32+
"type": "Compose",
33+
"selectionSetIds": [
34+
0
35+
]
36+
}
37+
]
38+
}
39+
}
40+
---------------
41+
42+
Result
43+
---------------
44+
{
45+
"data": {
46+
"appointments": {
47+
"nodes": [
48+
{
49+
"id": "QXBwb2ludG1lbnQKaTE="
50+
},
51+
{
52+
"id": "QXBwb2ludG1lbnQKaTI="
53+
}
54+
]
55+
}
56+
}
57+
}
58+
---------------
59+
60+
Fusion Graph
61+
---------------
62+
schema @fusion(version: 1) @httpClient(subgraph: "Appointment", baseAddress: "http:\/\/localhost:5000\/graphql") @webSocketClient(subgraph: "Appointment", baseAddress: "ws:\/\/localhost:5000\/graphql") @node(subgraph: "Appointment", types: [ "Patient1", "Appointment" ]) {
63+
query: Query
64+
}
65+
66+
type Query {
67+
appointmentById(appointmentId: ID!): Appointment @variable(subgraph: "Appointment", name: "appointmentId", argument: "appointmentId") @resolver(subgraph: "Appointment", select: "{ appointmentById(appointmentId: $appointmentId) }", arguments: [ { name: "appointmentId", type: "ID!" } ])
68+
appointments("Returns the elements in the list that come after the specified cursor." after: String "Returns the elements in the list that come before the specified cursor." before: String "Returns the first _n_ elements from the list." first: Int "Returns the last _n_ elements from the list." last: Int): AppointmentsConnection @variable(subgraph: "Appointment", name: "after", argument: "after") @variable(subgraph: "Appointment", name: "before", argument: "before") @variable(subgraph: "Appointment", name: "first", argument: "first") @variable(subgraph: "Appointment", name: "last", argument: "last") @resolver(subgraph: "Appointment", select: "{ appointments(after: $after, before: $before, first: $first, last: $last) }", arguments: [ { name: "after", type: "String" }, { name: "before", type: "String" }, { name: "first", type: "Int" }, { name: "last", type: "Int" } ])
69+
"Fetches an object given its ID."
70+
node("ID of the object." id: ID!): Node @variable(subgraph: "Appointment", name: "id", argument: "id") @resolver(subgraph: "Appointment", select: "{ node(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
71+
"Lookup nodes by a list of IDs."
72+
nodes("The list of node IDs." ids: [ID!]!): [Node]! @variable(subgraph: "Appointment", name: "ids", argument: "ids") @resolver(subgraph: "Appointment", select: "{ nodes(ids: $ids) }", arguments: [ { name: "ids", type: "[ID!]!" } ])
73+
patient(id: ID!): Patient1 @variable(subgraph: "Appointment", name: "id", argument: "id") @resolver(subgraph: "Appointment", select: "{ patient(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
74+
}
75+
76+
type Appointment implements Node @variable(subgraph: "Appointment", name: "Appointment_id", select: "id") @resolver(subgraph: "Appointment", select: "{ node(id: $Appointment_id) { ... on Appointment { ... Appointment } } }", arguments: [ { name: "Appointment_id", type: "ID!" } ]) @resolver(subgraph: "Appointment", select: "{ nodes(ids: $Appointment_id) { ... on Appointment { ... Appointment } } }", arguments: [ { name: "Appointment_id", type: "[ID!]!" } ], kind: "BATCH_BY_KEY") {
77+
id: ID! @source(subgraph: "Appointment")
78+
patient: IPatient! @source(subgraph: "Appointment")
79+
}
80+
81+
"A connection to a list of items."
82+
type AppointmentsConnection {
83+
"A list of edges."
84+
edges: [AppointmentsEdge!] @source(subgraph: "Appointment")
85+
"A flattened list of the nodes."
86+
nodes: [Appointment!] @source(subgraph: "Appointment")
87+
"Information to aid in pagination."
88+
pageInfo: PageInfo! @source(subgraph: "Appointment")
89+
}
90+
91+
"An edge in a connection."
92+
type AppointmentsEdge {
93+
"A cursor for use in pagination."
94+
cursor: String! @source(subgraph: "Appointment")
95+
"The item at the end of the edge."
96+
node: Appointment! @source(subgraph: "Appointment")
97+
}
98+
99+
"Information about pagination in a connection."
100+
type PageInfo {
101+
"When paginating forwards, the cursor to continue."
102+
endCursor: String @source(subgraph: "Appointment")
103+
"Indicates whether more edges exist following the set defined by the clients arguments."
104+
hasNextPage: Boolean! @source(subgraph: "Appointment")
105+
"Indicates whether more edges exist prior the set defined by the clients arguments."
106+
hasPreviousPage: Boolean! @source(subgraph: "Appointment")
107+
"When paginating backwards, the cursor to continue."
108+
startCursor: String @source(subgraph: "Appointment")
109+
}
110+
111+
type Patient1 implements IPatient & Node @variable(subgraph: "Appointment", name: "Patient1_id", select: "id") @resolver(subgraph: "Appointment", select: "{ node(id: $Patient1_id) { ... on Patient1 { ... Patient1 } } }", arguments: [ { name: "Patient1_id", type: "ID!" } ]) @resolver(subgraph: "Appointment", select: "{ nodes(ids: $Patient1_id) { ... on Patient1 { ... Patient1 } } }", arguments: [ { name: "Patient1_id", type: "[ID!]!" } ], kind: "BATCH_BY_KEY") {
112+
appointments("Returns the elements in the list that come after the specified cursor." after: String "Returns the elements in the list that come before the specified cursor." before: String "Returns the first _n_ elements from the list." first: Int "Returns the last _n_ elements from the list." last: Int): AppointmentsConnection @source(subgraph: "Appointment") @variable(subgraph: "Appointment", name: "after", argument: "after") @variable(subgraph: "Appointment", name: "before", argument: "before") @variable(subgraph: "Appointment", name: "first", argument: "first") @variable(subgraph: "Appointment", name: "last", argument: "last")
113+
id: ID! @source(subgraph: "Appointment")
114+
}
115+
116+
type Patient2 implements IPatient {
117+
id: ID! @source(subgraph: "Appointment")
118+
}
119+
120+
interface IPatient {
121+
id: ID!
122+
}
123+
124+
interface Node {
125+
id: ID!
126+
}
127+
---------------

0 commit comments

Comments
 (0)