Skip to content

Commit f6c4cde

Browse files
Determine providing selection set
1 parent e6f96fe commit f6c4cde

File tree

3 files changed

+159
-0
lines changed

3 files changed

+159
-0
lines changed

src/HotChocolate/Fusion-vnext/src/Fusion.Execution/Execution/Nodes/OperationExecutionNode.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,57 @@ public OperationExecutionNode(
5555
}
5656

5757
_variables = variables.ToArray();
58+
59+
var selectionSet = GetSelectionSetNodeFromPath(operation, source);
60+
61+
_providingSelectionSet = selectionSet ?? throw new InvalidOperationException("Could not determine source selection set");
62+
}
63+
64+
private readonly SelectionSetNode _providingSelectionSet;
65+
66+
// TODO: Move
67+
private static SelectionSetNode? GetSelectionSetNodeFromPath(OperationDefinitionNode operationDefinition, SelectionPath path)
68+
{
69+
var current = operationDefinition.SelectionSet;
70+
71+
if (path.IsRoot)
72+
{
73+
return current;
74+
}
75+
76+
for (var i = path.Segments.Length - 1; i >= 0; i--)
77+
{
78+
var segment = path.Segments[i];
79+
80+
if (segment.Kind == SelectionPathSegmentKind.InlineFragment)
81+
{
82+
// TODO: Do we need to handle the case without a type condition?
83+
var selection = current.Selections
84+
.OfType<InlineFragmentNode>()
85+
.FirstOrDefault(s => s.TypeCondition?.Name.Value == segment.Name);
86+
87+
if (selection is null)
88+
{
89+
return null;
90+
}
91+
92+
current = selection.SelectionSet;
93+
}
94+
else if (segment.Kind is SelectionPathSegmentKind.Field)
95+
{
96+
var selection = current.Selections
97+
.OfType<FieldNode>()
98+
.FirstOrDefault(s => s.Alias?.Value == segment.Name || s.Name.Value == segment.Name);
99+
100+
if (selection?.SelectionSet is null)
101+
{
102+
return null;
103+
}
104+
current = selection.SelectionSet;
105+
}
106+
}
107+
108+
return current;
58109
}
59110

60111
public override int Id { get; }

src/HotChocolate/Fusion-vnext/test/Fusion.AspNetCore.Tests/SubgraphErrorTests.cs

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,44 @@ public async Task No_Data_And_Error_With_Path_For_Lookup()
304304
response.MatchSnapshot();
305305
}
306306

307+
[Fact]
308+
public async Task No_Data_And_Error_With_Path_For_Lookup_Leaf()
309+
{
310+
// arrange
311+
using var server1 = CreateSourceSchema(
312+
"A",
313+
b => b.AddQueryType<SourceSchema1.Query>());
314+
315+
using var server2 = CreateSourceSchema(
316+
"B",
317+
b => b.AddQueryType<SourceSchema6.Query>());
318+
319+
// act
320+
using var gateway = await CreateCompositeSchemaAsync(
321+
[
322+
("A", server1),
323+
("B", server2),
324+
]);
325+
326+
// assert
327+
using var client = GraphQLHttpClient.Create(gateway.CreateClient());
328+
329+
using var result = await client.PostAsync(
330+
"""
331+
{
332+
topProduct {
333+
price
334+
name
335+
}
336+
}
337+
""",
338+
new Uri("http://localhost:5000/graphql"));
339+
340+
// act
341+
using var response = await result.ReadAsResultAsync();
342+
response.MatchSnapshot();
343+
}
344+
307345
[Fact]
308346
public async Task No_Data_And_Error_Without_Path_For_Lookup()
309347
{
@@ -475,4 +513,22 @@ public record Product(int Id)
475513
public string GetName() => "Product " + Id;
476514
}
477515
}
516+
517+
public static class SourceSchema6
518+
{
519+
public class Query
520+
{
521+
[Lookup]
522+
public Product GetProductById(int id) => new(id);
523+
}
524+
525+
public record Product(int Id)
526+
{
527+
public string GetName(IResolverContext context)
528+
{
529+
throw new GraphQLException(ErrorBuilder.New().SetMessage("Could not resolve Product.name")
530+
.SetPath(context.Path).Build());
531+
}
532+
}
533+
}
478534
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{
2+
"data": {
3+
"topProduct": {
4+
"price": 13.99,
5+
"name": null
6+
}
7+
},
8+
"errors": [
9+
{
10+
"message": "Could not resolve Product.name",
11+
"path": [
12+
"topProduct",
13+
"name"
14+
]
15+
}
16+
],
17+
"extensions": {
18+
"fusion": {
19+
"operationPlan": {
20+
"operation": {
21+
"document": "{\n topProduct {\n price\n name\n id @fusion__requirement\n }\n}",
22+
"hash": "e24d93b6245cedd878fff4452f5f16b9"
23+
},
24+
"nodes": [
25+
{
26+
"id": 1,
27+
"type": "Operation",
28+
"schema": "A",
29+
"operation": "query Op_e24d93b6_1 {\n topProduct {\n price\n id\n }\n}"
30+
},
31+
{
32+
"id": 2,
33+
"type": "Operation",
34+
"schema": "B",
35+
"operation": "query Op_e24d93b6_2(\n $__fusion_1_id: Int!\n) {\n productById(id: $__fusion_1_id) {\n name\n }\n}",
36+
"source": "$.productById",
37+
"target": "$.topProduct",
38+
"requirements": [
39+
{
40+
"name": "__fusion_1_id",
41+
"selectionMap": "id"
42+
}
43+
],
44+
"dependencies": [
45+
1
46+
]
47+
}
48+
]
49+
}
50+
}
51+
}
52+
}

0 commit comments

Comments
 (0)