Skip to content

Commit

Permalink
Fixed issue with null value serialization when using GraphQL literals. (
Browse files Browse the repository at this point in the history
  • Loading branch information
A360JMaxxgamer authored Jul 19, 2023
1 parent e6ac5b3 commit 89e65f0
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ internal static void WriteFieldValue(
Utf8JsonWriter writer,
object? value)
{
if (value is null or FileReference or FileReferenceNode)
if (value is null or NullValueNode or FileReference or FileReferenceNode)
{
writer.WriteNullValue();
return;
Expand Down
49 changes: 48 additions & 1 deletion src/HotChocolate/Fusion/test/Core.Tests/DemoIntegrationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1343,7 +1343,7 @@ query Requires {

Assert.Null(result.ExpectQueryResult().Errors);
}

[Fact]
public async Task Require_Data_In_Context_3()
{
Expand Down Expand Up @@ -1410,6 +1410,53 @@ query Large {
Assert.Null(result.ExpectQueryResult().Errors);
}

[Fact]
public async Task GetFirstPage_With_After_Null()
{
using var demoProject = await DemoProject.CreateAsync();

// act
var fusionGraph = await new FusionGraphComposer(logFactory: _logFactory).ComposeAsync(
new[]
{
demoProject.Appointment.ToConfiguration()
},
new FusionFeatureCollection(FusionFeatures.NodeField));

var executor = await new ServiceCollection()
.AddSingleton(demoProject.HttpClientFactory)
.AddSingleton(demoProject.WebSocketConnectionFactory)
.AddFusionGatewayServer()
.ConfigureFromDocument(SchemaFormatter.FormatAsDocument(fusionGraph))
.BuildRequestExecutorAsync();

var request = Parse(
"""
query AfterNull($after: String) {
appointments(after: $after) {
nodes {
id
}
}
}
""");

// act
var result = await executor.ExecuteAsync(
QueryRequestBuilder
.New()
.SetQuery(request)
.SetVariableValue("after", null)
.Create());

// assert
var snapshot = new Snapshot();
CollectSnapshotData(snapshot, request, result, fusionGraph);
await snapshot.MatchAsync();

Assert.Null(result.ExpectQueryResult().Errors);
}

public sealed class HotReloadConfiguration : IObservable<GatewayConfiguration>
{
private GatewayConfiguration _configuration;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
User Request
---------------
query AfterNull($after: String) {
appointments(after: $after) {
nodes {
id
}
}
}
---------------

QueryPlan
---------------
{
"document": "query AfterNull($after: String) { appointments(after: $after) { nodes { id } } }",
"operation": "AfterNull",
"rootNode": {
"type": "Sequence",
"nodes": [
{
"type": "Resolve",
"subgraph": "Appointment",
"document": "query AfterNull_1($after: String) { appointments(after: $after, before: null, first: null, last: null) { nodes { id } } }",
"selectionSetId": 0,
"forwardedVariables": [
{
"variable": "after"
}
]
},
{
"type": "Compose",
"selectionSetIds": [
0
]
}
]
}
}
---------------

Result
---------------
{
"data": {
"appointments": {
"nodes": [
{
"id": "QXBwb2ludG1lbnQKaTE="
},
{
"id": "QXBwb2ludG1lbnQKaTI="
}
]
}
}
}
---------------

Fusion Graph
---------------
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" ]) {
query: Query
}

type Query {
appointmentById(appointmentId: ID!): Appointment @variable(subgraph: "Appointment", name: "appointmentId", argument: "appointmentId") @resolver(subgraph: "Appointment", select: "{ appointmentById(appointmentId: $appointmentId) }", arguments: [ { name: "appointmentId", type: "ID!" } ])
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" } ])
"Fetches an object given its ID."
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!" } ])
"Lookup nodes by a list of IDs."
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!]!" } ])
patient(id: ID!): Patient1 @variable(subgraph: "Appointment", name: "id", argument: "id") @resolver(subgraph: "Appointment", select: "{ patient(id: $id) }", arguments: [ { name: "id", type: "ID!" } ])
}

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") {
id: ID! @source(subgraph: "Appointment")
patient: IPatient! @source(subgraph: "Appointment")
}

"A connection to a list of items."
type AppointmentsConnection {
"A list of edges."
edges: [AppointmentsEdge!] @source(subgraph: "Appointment")
"A flattened list of the nodes."
nodes: [Appointment!] @source(subgraph: "Appointment")
"Information to aid in pagination."
pageInfo: PageInfo! @source(subgraph: "Appointment")
}

"An edge in a connection."
type AppointmentsEdge {
"A cursor for use in pagination."
cursor: String! @source(subgraph: "Appointment")
"The item at the end of the edge."
node: Appointment! @source(subgraph: "Appointment")
}

"Information about pagination in a connection."
type PageInfo {
"When paginating forwards, the cursor to continue."
endCursor: String @source(subgraph: "Appointment")
"Indicates whether more edges exist following the set defined by the clients arguments."
hasNextPage: Boolean! @source(subgraph: "Appointment")
"Indicates whether more edges exist prior the set defined by the clients arguments."
hasPreviousPage: Boolean! @source(subgraph: "Appointment")
"When paginating backwards, the cursor to continue."
startCursor: String @source(subgraph: "Appointment")
}

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") {
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")
id: ID! @source(subgraph: "Appointment")
}

type Patient2 implements IPatient {
id: ID! @source(subgraph: "Appointment")
}

interface IPatient {
id: ID!
}

interface Node {
id: ID!
}
---------------

0 comments on commit 89e65f0

Please sign in to comment.