Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -1293,6 +1293,21 @@ ... @defer(label: "b") {
"Expected both labeled deferred payloads to include product.name.");
}

private static void AssertContainsOverlapIncrementalLegacyPayload(string content)
{
const string subPathPayload =
"\"incremental\":[{\"data\":{\"name\":\"Abc\",\"description\":\"Abc desc\",\"reviews\":[{\"rating\":5}]},\"path\":[\"product\"],\"label\":\"foo\"}]";

const string rootPathPayload =
"\"incremental\":[{\"data\":{\"product\":{\"name\":\"Abc\",\"description\":\"Abc desc\",\"reviews\":[{\"rating\":5}]}},"
+ "\"path\":[],\"label\":\"foo\"}]";

Assert.True(
content.Contains(subPathPayload, StringComparison.Ordinal)
|| content.Contains(rootPathPayload, StringComparison.Ordinal),
"Expected overlap incremental payload in either legacy-compatible shape.");
}

private TestServer CreateDeferServer(
HttpTransportVersion serverTransportVersion = HttpTransportVersion.Latest)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ private static IInputType AssertInputType(
throw ThrowHelper.VariableIsNotAnInputType(variableDefinition);
}

private IValueNode CoerceInputLiteral(
private static IValueNode CoerceInputLiteral(
JsonElement inputValue,
IInputType type,
IFeatureProvider context,
Expand Down Expand Up @@ -227,18 +227,20 @@ private IValueNode CoerceInputLiteral(
}

case TypeKind.List:
if (inputValue.ValueKind is not JsonValueKind.Array)
{
throw new InvalidOperationException();
}

var items = new List<IValueNode>();
var elementType = type.ElementType().EnsureInputType();
var elementDepth = depth + 1;

foreach (var item in inputValue.EnumerateArray())
if (inputValue.ValueKind is JsonValueKind.Array)
{
foreach (var item in inputValue.EnumerateArray())
{
items.Add(CoerceInputLiteral(item, elementType, context, elementDepth));
}
}
else
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should gate this. We don't want new projects to introduce this issue again, it should be exclusively for existing projects that wouldn't be able to upgrade due to old client operations.

{
items.Add(CoerceInputLiteral(item, elementType, context, elementDepth));
items.Add(CoerceInputLiteral(inputValue, elementType, context, elementDepth));
}

return new ListValueNode(items);
Expand Down
13 changes: 8 additions & 5 deletions src/HotChocolate/Core/src/Types/Types/InputParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -475,21 +475,24 @@ private object DeserializeList(
InputField? field,
IFeatureProvider context)
{
var list = CreateList(type);

if (inputValue.ValueKind is JsonValueKind.Array)
{
var list = CreateList(type);

var i = 0;
foreach (var element in inputValue.EnumerateArray())
{
var newPath = path.Append(i++);
list.Add(Deserialize(element, type.ElementType, newPath, field, context));
}

return list;
}
else
{
var newPath = path.Append(0);
list.Add(Deserialize(inputValue, type.ElementType, newPath, field, context));
}

throw ParseList_InvalidValueKind(type, path);
return list;
}

private object DeserializeObject(JsonElement inputValue, InputObjectType type, Path path, IFeatureProvider context)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,107 @@ void Action() => helper.CoerceVariableValues(
""");
}

[Fact]
public void Single_Value_Can_Be_Coerced_Into_List_Variable()
{
// arrange
var schema = SchemaBuilder.New().AddStarWarsTypes().Create();

var variableDefinitions = new List<VariableDefinitionNode>
{
new(null,
new VariableNode("abc"),
description: null,
new ListTypeNode(new NamedTypeNode("String")),
null,
Array.Empty<DirectiveNode>())
};

var variableValues = JsonDocument.Parse("""{"abc": "xyz"}""");
var coercedValues = new Dictionary<string, VariableValue>();
var featureProvider = new MockFeatureProvider();
var helper = new VariableCoercionHelper(new());

// act
helper.CoerceVariableValues(
schema, variableDefinitions, variableValues.RootElement, coercedValues, featureProvider);

// assert
var entry = Assert.Single(coercedValues);
Assert.Equal("abc", entry.Key);

var runtimeValues = Assert.IsAssignableFrom<System.Collections.IList>(entry.Value.RuntimeValue);
var runtimeValue = Assert.Single(runtimeValues.Cast<object?>());
Assert.Equal("xyz", runtimeValue);

entry.Value.ValueLiteral.MatchInlineSnapshot(
"""
[
"xyz"
]
""");
}

[Fact]
public void StringValue_Representing_EnumValue_In_Single_Object_For_List_ShouldBe_Rewritten()
{
// arrange
var schema = SchemaBuilder.New()
.AddDocumentFromString(
@"
type Query {
test(list: [FooInput]): String
}

input FooInput {
enum: TestEnum
}

enum TestEnum {
Foo
Bar
}")
.Use(_ => _ => default)
.Create();

var variableDefinitions = new List<VariableDefinitionNode>
{
new(null,
new VariableNode("abc"),
description: null,
new ListTypeNode(new NamedTypeNode("FooInput")),
null,
Array.Empty<DirectiveNode>())
};

var variableValues = JsonDocument.Parse(
"""
{
"abc": { "enum": "Foo" }
}
""");

var coercedValues = new Dictionary<string, VariableValue>();
var featureProvider = new MockFeatureProvider();
var helper = new VariableCoercionHelper(new());

// act
helper.CoerceVariableValues(
schema, variableDefinitions, variableValues.RootElement, coercedValues, featureProvider);

// assert
var entry = Assert.Single(coercedValues);
Assert.Equal("abc", entry.Key);
entry.Value.ValueLiteral.MatchInlineSnapshot(
"""
[
{
enum: Foo
}
]
""");
}

[Fact]
public void StringValues_Representing_EnumValues_In_Lists_ShouldBe_Rewritten()
{
Expand Down
19 changes: 19 additions & 0 deletions src/HotChocolate/Core/test/Types.Tests/Types/InputParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,25 @@ public void Parse_InputObject_NullableEnumList()
t => Assert.Equal(Bar.Baz, t));
}

[Fact]
public void Deserialize_List_Can_Be_Coerced_From_Single_Value()
{
// arrange
var parser = new InputParser(new DefaultTypeConverter());
var type = (IType)new ListType(new BooleanType());
var inputValue = JsonDocument.Parse("true");

var context = new Mock<IFeatureProvider>();
context.Setup(t => t.Features).Returns(FeatureCollection.Empty);

// act
var runtimeValue =
parser.ParseInputValue(inputValue.RootElement, type, context.Object, Path.Root.Append("root"));

// assert
Assert.Collection(Assert.IsType<List<bool?>>(runtimeValue), Assert.True);
}

[Fact]
public async Task Integration_InputObjectDefaultValue_ValueIsInitialized()
{
Expand Down
Loading