diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs index 087de6cf90d..cacb7819bf8 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/OperationPlanContext.cs @@ -394,8 +394,7 @@ internal ImmutableArray CreateVariableValueSets( return [_resultStore.CreateVariableValueSets(ToResultPath(selectionSet), [])]; } - var variableValues = GetPathThroughVariables(forwardedVariables); - return [_resultStore.CreateVariableValueSets(CompactPath.Root, variableValues)]; + return [_resultStore.CreateVariableValueSets(CompactPath.Root, Variables, forwardedVariables)]; } else { @@ -442,8 +441,7 @@ internal ImmutableArray CreateVariableValueSets( return []; } - var variableValues = GetPathThroughVariables(forwardedVariables); - return [_resultStore.CreateVariableValueSets(CompactPath.Root, variableValues)]; + return [_resultStore.CreateVariableValueSets(CompactPath.Root, Variables, forwardedVariables)]; } else { diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs index a6932b1f919..918db759542 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/Results/FetchResultStore.cs @@ -1877,6 +1877,32 @@ private void WriteValueNode(IValueNode value) private void WriteCompositeResultValue(CompositeResultElement value) => value.WriteTo(_jsonWriter); + internal VariableValues CreateVariableValueSets( + CompactPath path, + IVariableValueCollection variables, + ReadOnlySpan forwardedVariables) + { + _jsonWriter.Reset(_variableWriter); + var startPosition = _variableWriter.Position; + _jsonWriter.WriteStartObject(); + + if (!variables.IsEmpty) + { + foreach (var name in forwardedVariables) + { + if (variables.TryGetValue(name, out var value)) + { + _jsonWriter.WritePropertyName(name); + WriteValueNode(value); + } + } + } + + _jsonWriter.WriteEndObject(); + var length = _variableWriter.Position - startPosition; + return new VariableValues(path, JsonSegment.Create(_variableWriter, startPosition, length)); + } + internal VariableValues CreateVariableValueSets( CompactPath path, IReadOnlyList fields) diff --git a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/OperationPlanContextRoutingTests.cs b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/OperationPlanContextRoutingTests.cs index b796ab7ec7e..6ba712ed2ea 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/OperationPlanContextRoutingTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/OperationPlanContextRoutingTests.cs @@ -22,6 +22,91 @@ namespace HotChocolate.Fusion.Execution; public sealed class OperationPlanContextRoutingTests : FusionTestBase { + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task CreateVariableValueSets_Should_PreserveForwardedValues_When_NoRequirements(bool multipleTargets) + { + // arrange + await using var fixture = await RoutingTestFixture.CreateAsync( + variables: new Dictionary + { + ["limit"] = new IntValueNode(10), + ["nullable"] = HotChocolate.Language.NullValueNode.Default, + ["input"] = new HotChocolate.Language.ObjectValueNode( + Field("name", new StringValueNode("quoted \"value\""))), + ["unused"] = new StringValueNode("not forwarded") + }); + var context = fixture.CreateContext(); + string[] forwardedVariables = ["nullable", "missing", "input", "limit", "limit"]; + + // act + var result = multipleTargets + ? context.CreateVariableValueSets([SelectionPath.Root], forwardedVariables, []) + : context.CreateVariableValueSets(SelectionPath.Root, forwardedVariables, []); + + // assert + var entry = Assert.Single(result); + Assert.True(entry.Path.IsRoot); + Normalize(entry.Values).MatchInlineSnapshot( + """ + {"nullable":null,"input":{"name":"quoted \u0022value\u0022"},"limit":10,"limit":10} + """); + } + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + [InlineData(true, true)] + public async Task CreateVariableValueSets_Should_WriteEmptyObject_When_ForwardedVariablesAreAbsent( + bool multipleTargets, + bool emptyVariables) + { + // arrange + await using var fixture = await RoutingTestFixture.CreateAsync( + variables: emptyVariables + ? null + : new Dictionary + { + ["unused"] = new IntValueNode(1) + }); + var context = fixture.CreateContext(); + string[] forwardedVariables = ["missing"]; + + // act + var result = multipleTargets + ? context.CreateVariableValueSets([SelectionPath.Root], forwardedVariables, []) + : context.CreateVariableValueSets(SelectionPath.Root, forwardedVariables, []); + + // assert + var entry = Assert.Single(result); + Normalize(entry.Values).MatchInlineSnapshot("{}"); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task CreateVariableValueSets_Should_ReturnNoVariables_When_NoRequirementsOrForwardedVariables( + bool multipleTargets) + { + // arrange + await using var fixture = await RoutingTestFixture.CreateAsync( + variables: new Dictionary + { + ["unused"] = new IntValueNode(1) + }); + var context = fixture.CreateContext(); + + // act + var result = multipleTargets + ? context.CreateVariableValueSets([SelectionPath.Root], [], []) + : context.CreateVariableValueSets(SelectionPath.Root, [], []); + + // assert + Assert.True(result.IsDefaultOrEmpty); + } + [Fact] public async Task CreateVariableValueSets_Should_RouteThroughResultStore_When_RequirementKeysIsNull() {