diff --git a/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs b/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs index f4633d41de2..e45062d5d2e 100644 --- a/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs +++ b/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs @@ -99,6 +99,154 @@ fragment mergeIdenticalFieldsWithIdenticalValues on Dog { """); } + [Fact] + public void IdenticalInputFieldValuesButDifferentOrdering() + { + ExpectValid( + """ + { + findDog(complex: { name: "A", owner: "B", child: { owner: "D", name: "C" } }) { + name + } + ... mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering + } + + fragment mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering on Query { + findDog(complex: { owner: "B", name: "A", child: { name: "C", owner: "D" } }) { + barks + } + } + """); + } + + [Fact] + public void ObjectValueWithNoFields() + { + ExpectValid( + """ + { + findDog(complex: { }) { + name + } + ... mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering + } + + fragment mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering on Query { + findDog(complex: { }) { + barks + } + } + """); + } + + [Fact] + public void ConflictingInputFieldValues() + { + ExpectErrors( + """ + { + findDog(complex: { name: "A", owner: "B" }) { + name + } + ... mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering + } + + fragment mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering on Query { + findDog(complex: { owner: "OTHER", name: "A" }) { + barks + } + } + """, + t => Assert.Equal( + "Encountered fields for the same object that cannot be merged.", + t.Message)); + } + + [Fact] + public void ConflictingNestedInputFieldValues() + { + ExpectErrors( + """ + { + findDog(complex: { name: "A", owner: "B", child: { owner: "D", name: "C" } }) { + name + } + ... mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering + } + + fragment mergeIdenticalFieldsWithIdenticalInputFieldValuesButDifferentOrdering on Query { + findDog(complex: { owner: "B", name: "A", child: { name: "C", owner: "OTHER" } }) { + barks + } + } + """, + t => Assert.Equal( + "Encountered fields for the same object that cannot be merged.", + t.Message)); + } + + [Fact] + public void IdenticalInputFieldListValuesButDifferentOrdering() + { + ExpectValid( + """ + { + findDog3(complexList: [ + { name: "A", owner: "B", childList: [{ name: "A1", owner: "B1" }, { owner: "C1", name: "D1" }] }, + { owner: "C", name: "D", childList: [{ name: "A1", owner: "B1" }, { owner: "C1", name: "D1" }] }]) { + name + } + findDog3(complexList: [ + { owner: "B", name: "A", childList: [{ owner: "B1", name: "A1" }, { name: "D1", owner: "C1" }] }, + { name: "D", owner: "C", childList: [{ owner: "B1", name: "A1" }, { name: "D1", owner: "C1" }] }]) { + barks + } + } + """); + } + + [Fact] + public void ConflictingInputFieldListValues() + { + ExpectErrors( + """ + { + findDog3(complexList: [{ name: "A", owner: "B" }, { owner: "C", name: "D" }]) { + name + } + findDog3(complexList: [{ owner: "B", name: "A" }, { name: "OTHER", owner: "C" }]) { + barks + } + } + """, + t => Assert.Equal( + "Encountered fields for the same object that cannot be merged.", + t.Message)); + } + + [Fact] + public void ConflictingNestedInputFieldListValues() + { + ExpectErrors( + """ + { + findDog3(complexList: [ + { name: "A", owner: "B", childList: [{ name: "A1", owner: "B1" }, { owner: "C1", name: "D1" }] }, + { owner: "C", name: "D", childList: [{ name: "A1", owner: "B1" }, { owner: "C1", name: "D1" }] }]) { + name + } + findDog3(complexList: [ + { owner: "B", name: "A", childList: [{ owner: "B1", name: "A1" }, { name: "D1", owner: "C1" }] }, + { name: "D", owner: "C", childList: [{ owner: "B1", name: "A1" }, { name: "D1", owner: "OTHER" }] }]) { + barks + } + } + """, + t => Assert.Equal( + "Encountered fields for the same object that cannot be merged.", + t.Message)); + } + [Fact] public void ConflictingArgsOnValues() { diff --git a/src/HotChocolate/Core/test/Validation.Tests/Models/ComplexInput3.cs b/src/HotChocolate/Core/test/Validation.Tests/Models/ComplexInput3.cs new file mode 100644 index 00000000000..f53fb4cfb26 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/Models/ComplexInput3.cs @@ -0,0 +1,3 @@ +namespace HotChocolate.Validation; + +public record ComplexInput3(string Name, string Owner, List? ChildList = null); diff --git a/src/HotChocolate/Core/test/Validation.Tests/Models/Query.cs b/src/HotChocolate/Core/test/Validation.Tests/Models/Query.cs index a8accd9be34..d52150f804c 100644 --- a/src/HotChocolate/Core/test/Validation.Tests/Models/Query.cs +++ b/src/HotChocolate/Core/test/Validation.Tests/Models/Query.cs @@ -35,6 +35,11 @@ public class Query return null; } + public Dog? FindDog3(List complexList) + { + return null; + } + public bool BooleanList(bool[]? booleanListArg) { return true; diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingInputFieldListValues.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingInputFieldListValues.snap new file mode 100644 index 00000000000..388cef45c4c --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingInputFieldListValues.snap @@ -0,0 +1,29 @@ +[ + { + "Message": "Encountered fields for the same object that cannot be merged.", + "Code": null, + "Path": null, + "Locations": [ + { + "Line": 2, + "Column": 5 + }, + { + "Line": 5, + "Column": 5 + } + ], + "Extensions": { + "declaringTypeA": "Query", + "declaringTypeB": "Query", + "fieldA": "findDog3", + "fieldB": "findDog3", + "typeA": "Dog", + "typeB": "Dog", + "responseNameA": "findDog3", + "responseNameB": "findDog3", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging" + }, + "Exception": null + } +] diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingInputFieldValues.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingInputFieldValues.snap new file mode 100644 index 00000000000..b7c573296b1 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingInputFieldValues.snap @@ -0,0 +1,29 @@ +[ + { + "Message": "Encountered fields for the same object that cannot be merged.", + "Code": null, + "Path": null, + "Locations": [ + { + "Line": 2, + "Column": 5 + }, + { + "Line": 9, + "Column": 5 + } + ], + "Extensions": { + "declaringTypeA": "Query", + "declaringTypeB": "Query", + "fieldA": "findDog", + "fieldB": "findDog", + "typeA": "Dog", + "typeB": "Dog", + "responseNameA": "findDog", + "responseNameB": "findDog", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging" + }, + "Exception": null + } +] diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingNestedInputFieldListValues.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingNestedInputFieldListValues.snap new file mode 100644 index 00000000000..5fbbb5d4130 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingNestedInputFieldListValues.snap @@ -0,0 +1,29 @@ +[ + { + "Message": "Encountered fields for the same object that cannot be merged.", + "Code": null, + "Path": null, + "Locations": [ + { + "Line": 2, + "Column": 5 + }, + { + "Line": 7, + "Column": 5 + } + ], + "Extensions": { + "declaringTypeA": "Query", + "declaringTypeB": "Query", + "fieldA": "findDog3", + "fieldB": "findDog3", + "typeA": "Dog", + "typeB": "Dog", + "responseNameA": "findDog3", + "responseNameB": "findDog3", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging" + }, + "Exception": null + } +] diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingNestedInputFieldValues.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingNestedInputFieldValues.snap new file mode 100644 index 00000000000..b7c573296b1 --- /dev/null +++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingNestedInputFieldValues.snap @@ -0,0 +1,29 @@ +[ + { + "Message": "Encountered fields for the same object that cannot be merged.", + "Code": null, + "Path": null, + "Locations": [ + { + "Line": 2, + "Column": 5 + }, + { + "Line": 9, + "Column": 5 + } + ], + "Extensions": { + "declaringTypeA": "Query", + "declaringTypeB": "Query", + "fieldA": "findDog", + "fieldB": "findDog", + "typeA": "Dog", + "typeB": "Dog", + "responseNameA": "findDog", + "responseNameB": "findDog", + "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging" + }, + "Exception": null + } +] diff --git a/src/HotChocolate/Language/src/Language.SyntaxTree/SyntaxEqualityComparer.cs b/src/HotChocolate/Language/src/Language.SyntaxTree/SyntaxEqualityComparer.cs index c18c0f4d9f0..f896f4899ef 100644 --- a/src/HotChocolate/Language/src/Language.SyntaxTree/SyntaxEqualityComparer.cs +++ b/src/HotChocolate/Language/src/Language.SyntaxTree/SyntaxEqualityComparer.cs @@ -357,7 +357,36 @@ private bool Equals(ObjectTypeExtensionNode x, ObjectTypeExtensionNode y) Equals(x.Fields, y.Fields); private bool Equals(ObjectValueNode x, ObjectValueNode y) - => Equals(x.Fields, y.Fields); + { + if (x.Fields.Count != y.Fields.Count) + { + return false; + } + + for (var i = 0; i < x.Fields.Count; i++) + { + var xField = x.Fields[i]; + ObjectFieldNode? matchingField = null; + + for (var j = 0; j < y.Fields.Count; j++) + { + var yField = y.Fields[j]; + + if (Equals(xField.Name, yField.Name)) + { + matchingField = yField; + break; + } + } + + if (matchingField is null || !Equals(xField.Value, matchingField.Value)) + { + return false; + } + } + + return true; + } private bool Equals(OperationDefinitionNode x, OperationDefinitionNode y) => SyntaxComparer.BySyntax.Equals(x.Name, y.Name) && @@ -1060,9 +1089,8 @@ private int GetHashCode(ObjectValueNode node) var hashCode = new HashCode(); hashCode.Add(node.Kind); - for (var i = 0; i < node.Fields.Count; i++) + foreach (var field in node.Fields.OrderBy(field => field.Name.Value, StringComparer.Ordinal)) { - var field = node.Fields[i]; hashCode.Add(GetHashCode(field)); }