Skip to content

Commit 6413a98

Browse files
[Fusion] Ignore inapplicable type refinements on abstract types (#8773)
Co-authored-by: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com>
1 parent 9556888 commit 6413a98

8 files changed

+423
-4
lines changed

src/HotChocolate/Fusion/src/Core/Planning/Extensions/OperationExtensions.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
11
using HotChocolate.Execution.Processing;
2+
using HotChocolate.Fusion.Metadata;
3+
using HotChocolate.Types;
4+
using HotChocolate.Types.Introspection;
5+
using HotChocolate.Utilities;
26

37
namespace HotChocolate.Fusion.Planning;
48

@@ -28,4 +32,39 @@ public static ISelectionSet GetSelectionSet(this IOperation operation, Execution
2832

2933
return operation.GetSelectionSet(step.ParentSelection, step.SelectionSetType);
3034
}
35+
36+
public static IEnumerable<IObjectType> GetSchemaPossibleTypes(
37+
this IOperation operation,
38+
ISelection selection,
39+
FusionGraphConfiguration config,
40+
string schemaName)
41+
{
42+
var possibleTypes = new List<IObjectType>();
43+
44+
foreach (var possibleType in operation.GetPossibleTypes(selection))
45+
{
46+
if (selection.Type.IsInterfaceType() || selection.Type.IsUnionType())
47+
{
48+
var declaringType = config.GetType<ObjectTypeMetadata>(possibleType.Name);
49+
50+
// Due to a bug we currently do not properly annotate sources (bindings)
51+
// to types, so we can't directly check against the bindings of declaringType
52+
// and instead we have to look at the type's fields to determine if
53+
// it exists on the given subgraph.
54+
if (!declaringType.Fields.Any(field => HasFieldOnSubgraph(field, schemaName)))
55+
{
56+
continue;
57+
}
58+
}
59+
60+
possibleTypes.Add(possibleType);
61+
}
62+
63+
return possibleTypes;
64+
}
65+
66+
private static bool HasFieldOnSubgraph(ObjectFieldInfo field, string schemaName)
67+
{
68+
return !field.Name.EqualsOrdinal(IntrospectionFields.TypeName) && field.Bindings.ContainsSubgraph(schemaName);
69+
}
3170
}

src/HotChocolate/Fusion/src/Core/Planning/Pipeline/ExecutionStepDiscoveryMiddleware.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
using HotChocolate.Types;
77
using HotChocolate.Types.Introspection;
88
using HotChocolate.Utilities;
9-
using Microsoft.AspNetCore.Components.Forms;
109
using ThrowHelper = HotChocolate.Fusion.Utilities.ThrowHelper;
1110

1211
namespace HotChocolate.Fusion.Planning.Pipeline;
@@ -331,7 +330,10 @@ private bool CollectNestedSelections(
331330
}
332331

333332
var overallCouldPlanSelections = false;
334-
foreach (var possibleType in operation.GetPossibleTypes(parentSelection))
333+
foreach (var possibleType in operation.GetSchemaPossibleTypes(
334+
parentSelection,
335+
_config,
336+
executionStep.SubgraphName))
335337
{
336338
var couldPlanSelections = CollectNestedSelections(
337339
context,

src/HotChocolate/Fusion/src/Core/Planning/QueryPlannerHelpers.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,10 @@ private static int EvaluateSubgraphCompatibilityScore(
7676

7777
if (selection.SelectionSet is not null)
7878
{
79-
foreach (var possibleType in operation.GetPossibleTypes(selection))
79+
foreach (var possibleType in operation.GetSchemaPossibleTypes(
80+
selection,
81+
configuration,
82+
schemaName))
8083
{
8184
var type = configuration.GetType<ObjectTypeMetadata>(possibleType.Name);
8285
var selectionSet = operation.GetSelectionSet(selection, possibleType);

src/HotChocolate/Fusion/src/Core/Planning/RequestFormatters/RequestDocumentFormatter.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -339,7 +339,10 @@ protected virtual SelectionSetNode CreateSelectionSetNode(
339339
{
340340
var selectionNodes = new List<ISelectionNode>();
341341
var typeSelectionNodes = selectionNodes;
342-
var possibleTypes = context.Operation.GetPossibleTypes(parentSelection);
342+
var possibleTypes = context.Operation.GetSchemaPossibleTypes(
343+
parentSelection,
344+
_config,
345+
executionStep.SubgraphName);
343346
var parentType = parentSelection.Type.NamedType();
344347

345348
using var typeEnumerator = possibleTypes.GetEnumerator();
@@ -393,6 +396,11 @@ protected virtual SelectionSetNode CreateSelectionSetNode(
393396
AddInlineFragment(possibleType);
394397
}
395398

399+
if (isAbstractType && selectionNodes.Count == 0)
400+
{
401+
selectionNodes.Add(TypeNameField);
402+
}
403+
396404
return new SelectionSetNode(selectionNodes);
397405

398406
void AddInlineFragment(IObjectType possibleType)

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

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -773,4 +773,195 @@ ... on Item2 {
773773
CollectSnapshotData(snapshot, request, result);
774774
await snapshot.MatchMarkdownAsync();
775775
}
776+
777+
[Fact]
778+
public async Task Interface_Field_With_Only_Type_Refinements_On_Same_Schema()
779+
{
780+
// arrange
781+
var subgraphA = await TestSubgraph.CreateAsync(
782+
"""
783+
type Query {
784+
someField: SomeInterface
785+
}
786+
787+
interface SomeInterface {
788+
value: String
789+
}
790+
791+
type ConcreteTypeA implements SomeInterface {
792+
value: String
793+
specificToA: String
794+
}
795+
""");
796+
797+
var subgraphB = await TestSubgraph.CreateAsync(
798+
"""
799+
type Query {
800+
anotherField: SomeInterface
801+
}
802+
803+
interface SomeInterface {
804+
value: String
805+
}
806+
807+
type ConcreteTypeB implements SomeInterface {
808+
value: String
809+
specificToB: String
810+
}
811+
""");
812+
813+
using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB]);
814+
var executor = await subgraphs.GetExecutorAsync();
815+
var request = Parse(
816+
"""
817+
{
818+
someField {
819+
value
820+
... on ConcreteTypeA {
821+
specificToA
822+
}
823+
}
824+
}
825+
""");
826+
827+
// act
828+
var result = await executor.ExecuteAsync(
829+
OperationRequestBuilder
830+
.New()
831+
.SetDocument(request)
832+
.Build());
833+
834+
// assert
835+
var snapshot = new Snapshot();
836+
CollectSnapshotData(snapshot, request, result);
837+
await snapshot.MatchMarkdownAsync();
838+
}
839+
840+
[Fact]
841+
public async Task Interface_Field_With_Type_Refinements_Exclusive_To_Other_Schema()
842+
{
843+
// arrange
844+
var subgraphA = await TestSubgraph.CreateAsync(
845+
"""
846+
type Query {
847+
someField: SomeInterface
848+
}
849+
850+
interface SomeInterface {
851+
value: String
852+
}
853+
854+
type ConcreteTypeA implements SomeInterface {
855+
value: String
856+
specificToA: String
857+
}
858+
""");
859+
860+
var subgraphB = await TestSubgraph.CreateAsync(
861+
"""
862+
type Query {
863+
anotherField: SomeInterface
864+
}
865+
866+
interface SomeInterface {
867+
value: String
868+
}
869+
870+
type ConcreteTypeB implements SomeInterface {
871+
value: String
872+
specificToB: String
873+
}
874+
""");
875+
876+
using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB]);
877+
var executor = await subgraphs.GetExecutorAsync();
878+
var request = Parse(
879+
"""
880+
{
881+
someField {
882+
value
883+
... on ConcreteTypeA {
884+
specificToA
885+
}
886+
... on ConcreteTypeB {
887+
specificToB
888+
}
889+
}
890+
}
891+
""");
892+
893+
// act
894+
var result = await executor.ExecuteAsync(
895+
OperationRequestBuilder
896+
.New()
897+
.SetDocument(request)
898+
.Build());
899+
900+
// assert
901+
var snapshot = new Snapshot();
902+
CollectSnapshotData(snapshot, request, result);
903+
await snapshot.MatchMarkdownAsync();
904+
}
905+
906+
[Fact]
907+
public async Task Interface_Field_With_Only_Type_Refinements_Exclusive_To_Other_Schema()
908+
{
909+
// arrange
910+
var subgraphA = await TestSubgraph.CreateAsync(
911+
"""
912+
type Query {
913+
someField: SomeInterface
914+
}
915+
916+
interface SomeInterface {
917+
value: String
918+
}
919+
920+
type ConcreteTypeA implements SomeInterface {
921+
value: String
922+
specificToA: String
923+
}
924+
""");
925+
926+
var subgraphB = await TestSubgraph.CreateAsync(
927+
"""
928+
type Query {
929+
anotherField: SomeInterface
930+
}
931+
932+
interface SomeInterface {
933+
value: String
934+
}
935+
936+
type ConcreteTypeB implements SomeInterface {
937+
value: String
938+
specificToB: String
939+
}
940+
""");
941+
942+
using var subgraphs = new TestSubgraphCollection(output, [subgraphA, subgraphB]);
943+
var executor = await subgraphs.GetExecutorAsync();
944+
var request = Parse(
945+
"""
946+
{
947+
someField {
948+
... on ConcreteTypeB {
949+
specificToB
950+
}
951+
}
952+
}
953+
""");
954+
955+
// act
956+
var result = await executor.ExecuteAsync(
957+
OperationRequestBuilder
958+
.New()
959+
.SetDocument(request)
960+
.Build());
961+
962+
// assert
963+
var snapshot = new Snapshot();
964+
CollectSnapshotData(snapshot, request, result);
965+
await snapshot.MatchMarkdownAsync();
966+
}
776967
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Interface_Field_With_Only_Type_Refinements_Exclusive_To_Other_Schema
2+
3+
## Result
4+
5+
```json
6+
{
7+
"data": {
8+
"someField": {}
9+
}
10+
}
11+
```
12+
13+
## Request
14+
15+
```graphql
16+
{
17+
someField {
18+
... on ConcreteTypeB {
19+
specificToB
20+
}
21+
}
22+
}
23+
```
24+
25+
## QueryPlan Hash
26+
27+
```text
28+
D517577711CC066E36DD6785CA938437DAA08994
29+
```
30+
31+
## QueryPlan
32+
33+
```json
34+
{
35+
"document": "{ someField { ... on ConcreteTypeB { specificToB } } }",
36+
"rootNode": {
37+
"type": "Sequence",
38+
"nodes": [
39+
{
40+
"type": "Resolve",
41+
"subgraph": "Subgraph_1",
42+
"document": "query fetch_someField_1 { someField { __typename } }",
43+
"selectionSetId": 0
44+
},
45+
{
46+
"type": "Compose",
47+
"selectionSetIds": [
48+
0
49+
]
50+
}
51+
]
52+
}
53+
}
54+
```
55+

0 commit comments

Comments
 (0)