From ea3da0afd5325c7e042c3a2b26429ee19a9339d5 Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Wed, 20 May 2026 12:39:46 +0200 Subject: [PATCH] Coerce single element value for list variable --- .../Processing/VariableCoercionHelperTests.cs | 43 ++++++ .../Execution/JsonVariableCoercion.cs | 24 +++- .../Execution/VariableCoercionHelperTests.cs | 132 ++++++++++++++++++ 3 files changed, 192 insertions(+), 7 deletions(-) diff --git a/src/HotChocolate/Core/test/Execution.Tests/Processing/VariableCoercionHelperTests.cs b/src/HotChocolate/Core/test/Execution.Tests/Processing/VariableCoercionHelperTests.cs index 939a73a40a7..465fee97bc6 100644 --- a/src/HotChocolate/Core/test/Execution.Tests/Processing/VariableCoercionHelperTests.cs +++ b/src/HotChocolate/Core/test/Execution.Tests/Processing/VariableCoercionHelperTests.cs @@ -611,6 +611,49 @@ public void Single_Value_Can_Be_Coerced_Into_List_Variable() """); } + [Fact] + public void Error_When_Single_Value_Type_Does_Not_Match_List_Element_Type() + { + // arrange + var schema = SchemaBuilder.New().AddStarWarsTypes().Create(); + + var variableDefinitions = new List + { + new(null, + new VariableNode("abc"), + description: null, + new ListTypeNode(new NamedTypeNode("Int")), + null, + Array.Empty()) + }; + + var variableValues = JsonDocument.Parse("""{"abc": "xyz"}"""); + var coercedValues = new Dictionary(); + var featureProvider = new MockFeatureProvider(); + var helper = new VariableCoercionHelper(new()); + + // act + void Action() => helper.CoerceVariableValues( + schema, variableDefinitions, variableValues.RootElement, coercedValues, featureProvider); + + // assert + Assert.Throws(Action) + .Errors.Select(t => t.WithException(null)) + .ToList() + .MatchInlineSnapshot( + """ + "errors": [ + { + "message": "Int cannot coerce the given value JSON element of type `String` to a runtime value.", + "path": [ + "abc", + 0 + ] + } + ] + """); + } + [Fact] public void StringValue_Representing_EnumValue_In_Single_Object_For_List_ShouldBe_Rewritten() { diff --git a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/JsonVariableCoercion.cs b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/JsonVariableCoercion.cs index 8e8cb56fa28..4809da21ac7 100644 --- a/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/JsonVariableCoercion.cs +++ b/src/HotChocolate/Fusion/src/Fusion.Execution/Execution/JsonVariableCoercion.cs @@ -95,17 +95,27 @@ private bool TryParseAndValidate( // Handle List types if (type.Kind is TypeKind.List) { + var elementType = (IInputType)type.ListType().ElementType; + if (element.ValueKind is not JsonValueKind.Array) { - value = null; - error = ErrorBuilder.New() - .SetMessage("The value is not a list value.") - .SetExtension("variable", $"{path}") - .Build(); - return false; + if (!TryParseAndValidate( + elementType, + element, + path, + depth + 1, + out var itemValue, + out error)) + { + value = null; + return false; + } + + value = new ListValueNode(itemValue); + error = null; + return true; } - var elementType = (IInputType)type.ListType().ElementType; var buffer = ArrayPool.Shared.Rent(64); var count = 0; diff --git a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/VariableCoercionHelperTests.cs b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/VariableCoercionHelperTests.cs index c58240f3e38..138bff78ef3 100644 --- a/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/VariableCoercionHelperTests.cs +++ b/src/HotChocolate/Fusion/test/Fusion.Execution.Tests/Execution/VariableCoercionHelperTests.cs @@ -48,6 +48,138 @@ public void Coerce_String_WithEscapedQuotes_IsUnescaped() Assert.Equal("tag:\"type_portable-lamp\"", stringValue.Value); } + [Fact] + public void Single_Value_Can_Be_Coerced_Into_List_Variable() + { + // arrange + var schema = CreateCompositeSchema(); + + var variableDefinitions = new List + { + new(null, + new VariableNode("abc"), + description: null, + new ListTypeNode(new NamedTypeNode("String")), + null, + Array.Empty()) + }; + + using var variableValues = JsonDocument.Parse("""{"abc": "xyz"}"""); + + // act + var success = VariableCoercionHelper.TryCoerceVariableValues( + new MockFeatureProvider(), + schema, + variableDefinitions, + variableValues.RootElement, + out var coercedVariableValues, + out var error); + + // assert + Assert.True(success, error?.Message); + Assert.Null(error); + Assert.NotNull(coercedVariableValues); + + var list = Assert.IsType(coercedVariableValues["abc"].Value); + var item = Assert.Single(list.Items); + var stringValue = Assert.IsType(item); + Assert.Equal("xyz", stringValue.Value); + } + + [Fact] + public void Error_When_Single_Value_Type_Does_Not_Match_List_Element_Type() + { + // arrange + var schema = CreateCompositeSchema(); + + var variableDefinitions = new List + { + new(null, + new VariableNode("abc"), + description: null, + new ListTypeNode(new NamedTypeNode("Int")), + null, + Array.Empty()) + }; + + using var variableValues = JsonDocument.Parse("""{"abc": "xyz"}"""); + + // act + var success = VariableCoercionHelper.TryCoerceVariableValues( + new MockFeatureProvider(), + schema, + variableDefinitions, + variableValues.RootElement, + out var coercedVariableValues, + out var error); + + // assert + Assert.False(success); + Assert.Null(coercedVariableValues); + Assert.NotNull(error); + + new[] { error.WithException(null) } + .ToList() + .MatchInlineSnapshot( + """ + "errors": [ + { + "message": "The value `\"xyz\"` is not a valid value for the scalar type `Int`.", + "extensions": { + "variable": "abc" + } + } + ] + """); + } + + [Fact] + public void Error_When_List_Element_Type_Does_Not_Match() + { + // arrange + var schema = CreateCompositeSchema(); + + var variableDefinitions = new List + { + new(null, + new VariableNode("abc"), + description: null, + new ListTypeNode(new NamedTypeNode("Int")), + null, + Array.Empty()) + }; + + using var variableValues = JsonDocument.Parse("""{"abc": [1, "xyz", 3]}"""); + + // act + var success = VariableCoercionHelper.TryCoerceVariableValues( + new MockFeatureProvider(), + schema, + variableDefinitions, + variableValues.RootElement, + out var coercedVariableValues, + out var error); + + // assert + Assert.False(success); + Assert.Null(coercedVariableValues); + Assert.NotNull(error); + + new[] { error.WithException(null) } + .ToList() + .MatchInlineSnapshot( + """ + "errors": [ + { + "message": "The value `\"xyz\"` is not a valid value for the scalar type `Int`.", + "extensions": { + "variable": "abc[1]" + } + } + ] + """); + } + private sealed class MockFeatureProvider : IFeatureProvider { public IFeatureCollection Features { get; } = new FeatureCollection();