diff --git a/src/All.slnx b/src/All.slnx
index 994d5d8b2ba..1be3ede579a 100644
--- a/src/All.slnx
+++ b/src/All.slnx
@@ -118,6 +118,7 @@
+
@@ -130,9 +131,7 @@
-
-
-
+
diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index bbd9056be49..1ef93dc8c70 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -13,6 +13,7 @@
+
@@ -132,4 +133,4 @@
-
+
\ No newline at end of file
diff --git a/src/HotChocolate/Core/HotChocolate.Core.slnx b/src/HotChocolate/Core/HotChocolate.Core.slnx
index cadf7faeb97..3fe231c6326 100644
--- a/src/HotChocolate/Core/HotChocolate.Core.slnx
+++ b/src/HotChocolate/Core/HotChocolate.Core.slnx
@@ -80,8 +80,9 @@
+
-
-
+
+
-
\ No newline at end of file
+
diff --git a/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs b/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs
index 2a4c85e0a7b..0f1dff3cec7 100644
--- a/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs
+++ b/src/HotChocolate/Core/src/Validation/Extensions/ValidationBuilderExtensions.Rules.cs
@@ -121,6 +121,14 @@ public static IValidationBuilder AddDocumentRules(
///
public static IValidationBuilder AddFieldRules(
this IValidationBuilder builder)
+ {
+ return builder
+ .TryAddValidationRule()
+ .TryAddValidationRule();
+ }
+
+ public static IValidationBuilder AddFieldRulesLegacy(
+ this IValidationBuilder builder)
{
return builder.TryAddValidationVisitor();
}
diff --git a/src/HotChocolate/Core/src/Validation/HotChocolate.Validation.csproj b/src/HotChocolate/Core/src/Validation/HotChocolate.Validation.csproj
index 139cbd4a666..e8a6b1637c3 100644
--- a/src/HotChocolate/Core/src/Validation/HotChocolate.Validation.csproj
+++ b/src/HotChocolate/Core/src/Validation/HotChocolate.Validation.csproj
@@ -4,7 +4,6 @@
HotChocolate.Validation
HotChocolate.Validation
HotChocolate.Validation
-
@@ -13,6 +12,7 @@
+
diff --git a/src/HotChocolate/Core/src/Validation/Rules/LeafFieldSelectionsRule.cs b/src/HotChocolate/Core/src/Validation/Rules/LeafFieldSelectionsRule.cs
new file mode 100644
index 00000000000..b1fb5fe5555
--- /dev/null
+++ b/src/HotChocolate/Core/src/Validation/Rules/LeafFieldSelectionsRule.cs
@@ -0,0 +1,94 @@
+using HotChocolate.Language;
+using HotChocolate.Types;
+
+namespace HotChocolate.Validation.Rules;
+
+internal sealed class LeafFieldSelectionsRule : IDocumentValidatorRule
+{
+ public ushort Priority => ushort.MaxValue;
+
+ public bool IsCacheable => true;
+
+ public void Validate(IDocumentValidatorContext context, DocumentNode document)
+ {
+ foreach (var operation in document.Definitions)
+ {
+ if(operation is not OperationDefinitionNode operationDef)
+ {
+ continue;
+ }
+
+ if (context.Schema.GetOperationType(operationDef.Operation) is { } rootType)
+ {
+ ValidateSelectionSet(context, operationDef.SelectionSet, rootType);
+ }
+ }
+ }
+
+ private void ValidateSelectionSet(IDocumentValidatorContext context, SelectionSetNode selectionSet, IType type)
+ {
+ foreach (var selection in selectionSet.Selections)
+ {
+ if (selection is FieldNode field)
+ {
+ ValidateField(context, field, type);
+ }
+ else if (selection is InlineFragmentNode inlineFrag)
+ {
+ var typeCondition = inlineFrag.TypeCondition is null
+ ? type
+ : context.Schema.GetType(inlineFrag.TypeCondition.Name.Value);
+ ValidateSelectionSet(context, inlineFrag.SelectionSet, typeCondition);
+ }
+ else if (selection is FragmentSpreadNode spread
+ && context.Fragments.TryGetValue(spread.Name.Value, out var frag))
+ {
+ if (context.VisitedFragments.Add(spread.Name.Value))
+ {
+ var typeCondition = context.Schema.GetType(frag.TypeCondition.Name.Value);
+ ValidateSelectionSet(context, frag.SelectionSet, typeCondition);
+ context.VisitedFragments.Remove(spread.Name.Value);
+ }
+ }
+ }
+ }
+
+ private void ValidateField(IDocumentValidatorContext context, FieldNode field, IType parentType)
+ {
+ if (parentType is not IComplexOutputType complex ||
+ !complex.Fields.TryGetField(field.Name.Value, out var fieldDef))
+ {
+ // handled by other rules like KnownFieldNames
+ return;
+ }
+
+ var namedType = fieldDef.Type.NamedType();
+
+ if (namedType.IsLeafType())
+ {
+ if (field.SelectionSet is not null)
+ {
+ context.ReportError(
+ ErrorBuilder.New()
+ .SetMessage($"Field \"{field.Name.Value}\" must not have a selection since type \"{namedType.Name}\" has no subfields.")
+ .AddLocation(field)
+ .Build());
+ }
+ }
+ else
+ {
+ if (field.SelectionSet is null)
+ {
+ context.ReportError(
+ ErrorBuilder.New()
+ .SetMessage($"Field \"{field.Name.Value}\" of type \"{namedType.Name}\" must have a selection of subfields. Did you mean \"{field.Name.Value} {{ ... }}\"?")
+ .AddLocation(field)
+ .Build());
+ }
+ else
+ {
+ ValidateSelectionSet(context, field.SelectionSet, fieldDef.Type);
+ }
+ }
+ }
+}
diff --git a/src/HotChocolate/Core/src/Validation/Rules/OverlappingFieldsCanBeMergedRule.cs b/src/HotChocolate/Core/src/Validation/Rules/OverlappingFieldsCanBeMergedRule.cs
new file mode 100644
index 00000000000..8521da0130f
--- /dev/null
+++ b/src/HotChocolate/Core/src/Validation/Rules/OverlappingFieldsCanBeMergedRule.cs
@@ -0,0 +1,713 @@
+using System.Diagnostics.CodeAnalysis;
+using HotChocolate.Language;
+using HotChocolate.Types;
+using Microsoft.Extensions.ObjectPool;
+
+namespace HotChocolate.Validation.Rules;
+
+///
+/// Implements the field selection merging rule as described in the GraphQL Spec.
+/// https://spec.graphql.org/June2018/#sec-Field-Selection-Merging
+///
+///
+/// The algorithm implemented here is not the one from the Spec, but is based on
+/// https://tech.xing.com/graphql-overlapping-fields-can-be-merged-fast-ea6e92e0a01
+/// It is not the final version (Listing 11), but Listing 10 adopted to this code base.
+///
+internal sealed class OverlappingFieldsCanBeMergedRule : IDocumentValidatorRule
+{
+ public ushort Priority => ushort.MaxValue;
+
+ public bool IsCacheable => true;
+
+ public void Validate(IDocumentValidatorContext context, DocumentNode document)
+ {
+ if (context == null)
+ {
+ throw new ArgumentNullException(nameof(context));
+ }
+
+ if (document == null)
+ {
+ throw new ArgumentNullException(nameof(document));
+ }
+
+ ValidateInternal(new MergeContext(context), document);
+ }
+
+ private static void ValidateInternal(MergeContext context, DocumentNode document)
+ {
+ foreach (var operation in document.Definitions)
+ {
+ if (operation is not OperationDefinitionNode operationDef)
+ {
+ continue;
+ }
+
+ if (context.Schema.GetOperationType(operationDef.Operation) is not { } rootType)
+ {
+ continue;
+ }
+
+ var fieldMap = new Dictionary>();
+ var visitedFragmentSpreads = new HashSet();
+ CollectFields(context, fieldMap, operationDef.SelectionSet, rootType, visitedFragmentSpreads);
+ var conflicts = FindConflicts(context, fieldMap);
+ List? fieldNodes = null;
+
+ foreach (var conflict in conflicts)
+ {
+ if (context.ConflictsReported.Any(r => r.SetEquals(conflict.Fields)))
+ {
+ continue;
+ }
+
+ context.ConflictsReported.Add(conflict.Fields);
+
+ fieldNodes ??= [];
+ fieldNodes.Clear();
+ fieldNodes.AddRange(conflict.Fields);
+ fieldNodes.Order(FieldLocationComparer.Instance);
+
+ context.ReportError(
+ ErrorBuilder.New()
+ .SetMessage(conflict.Reason)
+ .SetLocations(fieldNodes)
+ .SetPath(conflict.Path)
+ .SpecifiedBy("sec-Field-Selection-Merging")
+ .Build());
+ }
+ }
+ }
+
+ private static void CollectFields(
+ MergeContext context,
+ Dictionary> fieldMap,
+ SelectionSetNode selectionSet, IType parentType,
+ HashSet visitedFragmentSpreads)
+ {
+ foreach (var selection in selectionSet.Selections)
+ {
+ switch (selection)
+ {
+ case FieldNode field:
+ CollectFieldsForField(fieldMap, parentType, field);
+ break;
+
+ case InlineFragmentNode inlineFragment:
+ var type = inlineFragment.TypeCondition is null
+ ? parentType
+ : context.Schema.GetType(inlineFragment.TypeCondition.Name.Value);
+ CollectFields(context, fieldMap, inlineFragment.SelectionSet, type, visitedFragmentSpreads);
+ break;
+
+ case FragmentSpreadNode spread:
+ if (!visitedFragmentSpreads.Add(spread.Name.Value))
+ {
+ continue;
+ }
+
+ if (context.Fragments.TryGetValue(spread.Name.Value, out var fragment))
+ {
+ var fragType = context.Schema.GetType(fragment.TypeCondition.Name.Value);
+ CollectFields(context, fieldMap, fragment.SelectionSet, fragType, visitedFragmentSpreads);
+ }
+
+ break;
+ }
+ }
+ }
+
+ private static void CollectFieldsForField(
+ Dictionary> fieldMap,
+ IType parentType,
+ FieldNode field)
+ {
+ var responseName = field.Alias?.Value ?? field.Name.Value;
+ if (!fieldMap.TryGetValue(responseName, out var fields))
+ {
+ fields = [];
+ fieldMap[responseName] = fields;
+ }
+
+ var unwrappedParentType = parentType.NamedType();
+
+ if (unwrappedParentType is IComplexOutputType complexType
+ && complexType.Fields.TryGetField(field.Name.Value, out var fieldDef))
+ {
+ fields.Add(new FieldAndType(field, fieldDef.Type, complexType));
+ }
+ else
+ {
+ fields.Add(new FieldAndType(field, null, unwrappedParentType));
+ }
+ }
+
+ private static List FindConflicts(
+ MergeContext context,
+ Dictionary> fieldMap)
+ {
+ var result = new List();
+
+ SameResponseShapeByName(context, fieldMap, Path.Root, result);
+ SameForCommonParentsByName(context, fieldMap, Path.Root, result);
+
+ return result;
+ }
+
+ private static void SameResponseShapeByName(
+ MergeContext context,
+ Dictionary> fieldMap,
+ Path path,
+ List conflictsResult)
+ {
+ foreach (var entry in fieldMap)
+ {
+ if (context.SameResponseShapeChecked.Any(set => set.SetEquals(entry.Value)))
+ {
+ continue;
+ }
+
+ context.SameResponseShapeChecked.Add(entry.Value);
+
+ var newPath = path.Append(entry.Key);
+ var conflict = RequireSameOutputTypeShape(entry.Value, newPath);
+
+ if (conflict != null)
+ {
+ conflictsResult.Add(conflict);
+ continue;
+ }
+
+ var subSelections = MergeSubSelections(context, entry.Value);
+ SameResponseShapeByName(context, subSelections, newPath, conflictsResult);
+ }
+ }
+
+ private static void SameForCommonParentsByName(
+ MergeContext context,
+ Dictionary> fieldMap,
+ Path path,
+ List conflictsResult)
+ {
+ foreach (var entry in fieldMap)
+ {
+ var groups = GroupByCommonParents(entry.Value);
+ var newPath = path.Append(entry.Key);
+
+ foreach (var group in groups)
+ {
+ if (context.SameForCommonParentsChecked.Any(g => g.SetEquals(group)))
+ {
+ continue;
+ }
+
+ context.SameForCommonParentsChecked.Add(group);
+
+ var conflict = RequireSameNameAndArguments(newPath, group);
+ if (conflict is not null)
+ {
+ conflictsResult.Add(conflict);
+ continue;
+ }
+
+ conflict = RequireStreamDirectiveMergeable(context, group, newPath);
+ if (conflict is not null)
+ {
+ conflictsResult.Add(conflict);
+ continue;
+ }
+
+ var subSelections = MergeSubSelections(context, group);
+ SameForCommonParentsByName(context, subSelections, newPath, conflictsResult);
+ }
+ }
+ }
+
+ private static List> GroupByCommonParents(HashSet fields)
+ {
+ // Separate abstract and concrete parent types
+ var abstractFields = new List();
+ var concreteGroups = new Dictionary>();
+
+ foreach (var field in fields)
+ {
+ var parent = field.ParentType.NamedType();
+
+ switch (parent.Kind)
+ {
+ case TypeKind.Interface or TypeKind.Union:
+ abstractFields.Add(field);
+ break;
+
+ case TypeKind.Object:
+ {
+ if (!concreteGroups.TryGetValue(parent, out var list))
+ {
+ list = [];
+ concreteGroups[parent] = list;
+ }
+
+ list.Add(field);
+ break;
+ }
+ }
+ }
+
+ // If there are no concrete groups, just return one group with all abstract fields
+ if (concreteGroups.Count == 0)
+ {
+ return [new HashSet(abstractFields)];
+ }
+
+ var result = new List>();
+
+ foreach (var group in concreteGroups.Values)
+ {
+ var set = new HashSet();
+
+ foreach (var field in group)
+ {
+ set.Add(field);
+ }
+
+ foreach (var abstractField in abstractFields)
+ {
+ set.Add(abstractField);
+ }
+
+ result.Add(set);
+ }
+
+ return result;
+ }
+
+ private static Conflict? RequireSameNameAndArguments(Path path, HashSet fieldGroup)
+ {
+ if (fieldGroup.Count <= 1)
+ {
+ return null;
+ }
+
+ var first = fieldGroup.First();
+ var name = first.Field.Name.Value;
+ var arguments = first.Field.Arguments;
+ var responseName = first.Field.Alias?.Value ?? name;
+
+ foreach (var (field, _, _) in fieldGroup.Skip(1))
+ {
+ if (field.Name.Value != name)
+ {
+ return NameMismatchConflict(name, field.Name.Value, path, fieldGroup);
+ }
+
+ if (!SameArguments(arguments, field.Arguments))
+ {
+ return ArgumentMismatchConflict(responseName, path, fieldGroup);
+ }
+ }
+
+ return null;
+ }
+
+ private static bool SameArguments(IReadOnlyList a, IReadOnlyList b)
+ {
+ if (a.Count != b.Count)
+ {
+ return false;
+ }
+
+ if (a.Count == 0)
+ {
+ return true;
+ }
+
+ for (var i = 0; i < a.Count; i++)
+ {
+ var argA = a[i];
+ var argB = b.FirstOrDefault(x => x.Name.Value == argA.Name.Value);
+ if (argB is null || !argA.Value.Equals(argB.Value, SyntaxComparison.Syntax))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ private static Dictionary> MergeSubSelections(
+ MergeContext context,
+ HashSet fields)
+ {
+ var merged = new Dictionary>();
+ HashSet? visited = null;
+
+ foreach (var item in fields)
+ {
+ if (item.Field.SelectionSet is null || item.Type is null)
+ {
+ continue;
+ }
+
+ visited ??= [];
+ visited.Clear();
+
+ CollectFields(context, merged, item.Field.SelectionSet, item.Type, visited);
+ }
+
+ return merged;
+ }
+
+ private static Conflict? RequireSameOutputTypeShape(
+ HashSet fields,
+ Path path)
+ {
+ if (fields.Count <= 1)
+ {
+ return null;
+ }
+
+ var baseField = fields.First();
+
+ foreach (var current in fields.Skip(1))
+ {
+ var a = baseField.Type;
+ var b = current.Type;
+
+ while (true)
+ {
+ if (a is NonNullType || b is NonNullType)
+ {
+ if (a is not NonNullType || b is not NonNullType)
+ {
+ return TypeMismatchConflict(
+ current.Field.Name.Value,
+ path,
+ a,
+ b,
+ fields);
+ }
+ }
+
+ if (a is ListType || b is ListType)
+ {
+ if (a is not ListType || b is not ListType)
+ {
+ return TypeMismatchConflict(
+ current.Field.Name.Value,
+ path,
+ a,
+ b,
+ fields);
+ }
+ }
+
+ if (a is not (NonNullType or ListType) && b is not (NonNullType or ListType))
+ {
+ break;
+ }
+
+ a = a?.InnerType();
+ b = b?.InnerType();
+ }
+
+ if (!SameType(a, b))
+ {
+ return TypeMismatchConflict(
+ current.Field.Name.Value,
+ path,
+ a,
+ b,
+ fields);
+ }
+ }
+
+ return null;
+ }
+
+ private static bool SameType(IType? a, IType? b)
+ {
+ if (a is null)
+ {
+ return b is null;
+ }
+
+ if (b is null)
+ {
+ return false;
+ }
+
+ // if the return type of field is a union type, interface type or an object type we can merge
+ // the selection set.
+ if (b.Kind is TypeKind.Interface or TypeKind.Union or TypeKind.Object)
+ {
+ return true;
+ }
+
+ return a.Equals(b);
+ }
+
+ private static Conflict TypeMismatchConflict(
+ string responseName,
+ Path path,
+ IType? typeA,
+ IType? typeB,
+ HashSet fields)
+ {
+ var typeNameA = typeA?.Print() ?? "null";
+ var typeNameB = typeB?.Print() ?? "null";
+
+ return new Conflict(
+ string.Format(
+ "Fields `{0}` conflict because they return conflicting types `{1}` and `{2}`. "
+ + "Use different aliases on the fields to fetch both if this was intentional.",
+ responseName,
+ typeNameA,
+ typeNameB),
+ GetFieldNodes(fields),
+ path);
+ }
+
+ private static Conflict ArgumentMismatchConflict(
+ string responseName,
+ Path path,
+ HashSet fields)
+ {
+ return new Conflict(
+ string.Format(
+ "Fields `{0}` conflict because they have differing arguments. "
+ + "Use different aliases on the fields to fetch both if this was intentional. ",
+ responseName),
+ GetFieldNodes(fields),
+ path);
+ }
+
+ private static Conflict NameMismatchConflict(
+ string fieldName1,
+ string fieldName2,
+ Path path,
+ HashSet fields)
+ {
+ return new Conflict(
+ string.Format(
+ "Fields `{0}` and `{1}` conflict because they have differing names. "
+ + "Use different aliases on the fields to fetch both if this was intentional.",
+ fieldName1,
+ fieldName2),
+ GetFieldNodes(fields),
+ path);
+ }
+
+ private static Conflict? RequireStreamDirectiveMergeable(
+ MergeContext context,
+ HashSet fields,
+ Path path)
+ {
+ // if the stream directive is disabled we can skip this check.
+ if (!context.IsStreamEnabled)
+ {
+ return null;
+ }
+
+ var baseField = fields.FirstOrDefault(
+ f => f.Field.Directives.Any(
+ d => d.Name.Value == WellKnownDirectives.Stream));
+
+ // if there is no stream directive on any field in this group we can skip this check.
+ if (baseField is null)
+ {
+ return null;
+ }
+
+ var baseInitialCount = GetStreamInitialCount(baseField.Field);
+
+ foreach (var (field, _, _) in fields)
+ {
+ if (!TryGetStreamDirective(field, out var streamDirective))
+ {
+ return StreamDirectiveMismatch(field.Name.Value, path, fields);
+ }
+
+ var initialCount = GetStreamInitialCount(streamDirective);
+
+ if (!SyntaxComparer.BySyntax.Equals(baseInitialCount, initialCount))
+ {
+ return StreamDirectiveMismatch(field.Name.Value, path, fields);
+ }
+ }
+
+ return null;
+ }
+
+
+ private static IntValueNode? GetStreamInitialCount(FieldNode field)
+ {
+ var streamDirective = field.Directives.First(
+ d => d.Name.Value == WellKnownDirectives.Stream);
+
+ return GetStreamInitialCount(streamDirective);
+ }
+
+ private static bool TryGetStreamDirective(FieldNode field, [NotNullWhen(true)] out DirectiveNode? streamDirective)
+ {
+ streamDirective = field.Directives.FirstOrDefault(
+ d => d.Name.Value == WellKnownDirectives.Stream);
+ return streamDirective is not null;
+ }
+
+ private static IntValueNode? GetStreamInitialCount(DirectiveNode streamDirective)
+ {
+ var initialCountArgument = streamDirective.Arguments.FirstOrDefault(
+ a => a.Name.Value == WellKnownDirectives.InitialCount);
+ return initialCountArgument?.Value as IntValueNode;
+ }
+
+ private static Conflict StreamDirectiveMismatch(
+ string fieldName,
+ Path path,
+ HashSet fields)
+ {
+ return new Conflict(
+ string.Format(
+ "Fields `{0}` conflict because they have differing stream directives. ",
+ fieldName),
+ GetFieldNodes(fields),
+ path);
+ }
+
+ private static HashSet GetFieldNodes(HashSet fields)
+ {
+ var fieldNodes = new HashSet(fields.Count);
+
+ foreach (var field in fields)
+ {
+ fieldNodes.Add(field.Field);
+ }
+
+ return fieldNodes;
+ }
+
+ private sealed class FieldAndType
+ {
+ public FieldAndType(FieldNode field, IType? type, IType parentType)
+ {
+ Field = field;
+ Type = type;
+ ParentType = parentType;
+ }
+
+ public FieldNode Field { get; }
+
+ public IType? Type { get; }
+
+ public IType ParentType { get; }
+
+ public override bool Equals(object? obj)
+ => obj is FieldAndType other && Field.Equals(other.Field);
+
+ public override int GetHashCode()
+ => SyntaxComparer.BySyntax.GetHashCode(Field);
+
+ public void Deconstruct(out FieldNode field, out IType? type, out IType parentType)
+ {
+ field = Field;
+ type = Type;
+ parentType = ParentType;
+ }
+ }
+
+ private sealed class Conflict(string reason, HashSet fields, Path? path = null)
+ {
+ public string Reason { get; } = reason;
+
+ public HashSet Fields { get; } = fields;
+
+ public Path? Path { get; } = path;
+ }
+
+ private class HashSetComparer : IEqualityComparer> where T : notnull
+ {
+ public static readonly HashSetComparer Instance = new();
+
+ public bool Equals(HashSet? x, HashSet? y)
+ => x != null && y != null && x.SetEquals(y);
+
+ public int GetHashCode(HashSet obj)
+ {
+ var hash = new HashCode();
+
+ foreach (var item in obj)
+ {
+ hash.Add(item);
+ }
+
+ return hash.ToHashCode();
+ }
+ }
+
+ private sealed class MergeContext(IDocumentValidatorContext context)
+ {
+ public ISchema Schema => context.Schema;
+
+ public HashSet> SameResponseShapeChecked { get; }
+ = new(HashSetComparer.Instance);
+
+ public HashSet> SameForCommonParentsChecked { get; }
+ = new(HashSetComparer.Instance);
+
+ public HashSet> ConflictsReported { get; }
+ = new(HashSetComparer.Instance);
+
+ public IDictionary Fragments => context.Fragments;
+
+ public bool IsStreamEnabled { get; } = context.Schema.TryGetDirectiveType(WellKnownDirectives.Stream, out _);
+
+ public void ReportError(IError error)
+ {
+ context.ReportError(error);
+ }
+ }
+
+ private sealed class FieldLocationComparer : IComparer
+ {
+ public static FieldLocationComparer Instance { get; } = new();
+
+ public int Compare(FieldNode? x, FieldNode? y)
+ {
+ if (x is null && y is null)
+ {
+ return 0;
+ }
+
+ if (x is null)
+ {
+ return -1;
+ }
+
+ if (y is null)
+ {
+ return 1;
+ }
+
+ var xLocation = x.Location;
+ var yLocation = y.Location;
+
+ if (xLocation is null && yLocation is null)
+ {
+ return 0;
+ }
+
+ if (xLocation is null)
+ {
+ return -1;
+ }
+
+ if (yLocation is null)
+ {
+ return 1;
+ }
+
+ return xLocation.CompareTo(yLocation);
+ }
+ }
+}
diff --git a/src/HotChocolate/Core/test/Types.Analyzer.Integration.Tests/HotChocolate.Types.Analyzers.Tests.csproj b/src/HotChocolate/Core/test/Types.Analyzer.Integration.Tests/HotChocolate.Types.Analyzer.Integration.Tests.csproj
similarity index 100%
rename from src/HotChocolate/Core/test/Types.Analyzer.Integration.Tests/HotChocolate.Types.Analyzers.Tests.csproj
rename to src/HotChocolate/Core/test/Types.Analyzer.Integration.Tests/HotChocolate.Types.Analyzer.Integration.Tests.csproj
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/Directory.Build.props b/src/HotChocolate/Core/test/Validation.Benchmarks/Directory.Build.props
new file mode 100644
index 00000000000..501ff72b9c9
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/Directory.Build.props
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/Directory.Build.targets b/src/HotChocolate/Core/test/Validation.Benchmarks/Directory.Build.targets
new file mode 100644
index 00000000000..501ff72b9c9
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/Directory.Build.targets
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/HotChocolate.Validation.Benchmarks.csproj b/src/HotChocolate/Core/test/Validation.Benchmarks/HotChocolate.Validation.Benchmarks.csproj
new file mode 100644
index 00000000000..75bc9b9e3d4
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/HotChocolate.Validation.Benchmarks.csproj
@@ -0,0 +1,19 @@
+
+
+
+ Exe
+ net9.0
+ enable
+ enable
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/OverlappingFieldsCanBeMergedRuleBenchmarks.cs b/src/HotChocolate/Core/test/Validation.Benchmarks/OverlappingFieldsCanBeMergedRuleBenchmarks.cs
new file mode 100644
index 00000000000..b1834585454
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/OverlappingFieldsCanBeMergedRuleBenchmarks.cs
@@ -0,0 +1,61 @@
+using BenchmarkDotNet.Attributes;
+using HotChocolate.Language;
+using HotChocolate.Validation.Rules;
+
+namespace HotChocolate.Validation.Benchmarks;
+
+[RPlotExporter, CategoriesColumn, RankColumn, MeanColumn, MedianColumn, MemoryDiagnoser]
+public class OverlappingFieldsCanBeMergedRuleBenchmarks
+{
+ private readonly DocumentValidatorRule _currentRule = new(new FieldVisitor());
+ private readonly OverlappingFieldsCanBeMergedRule _newRule = new();
+ private readonly DocumentValidatorContext _context = new();
+ private readonly Dictionary _contextData = new();
+ private readonly ISchema _schema;
+ private readonly DocumentNode _document;
+
+ public OverlappingFieldsCanBeMergedRuleBenchmarks()
+ {
+ _schema = CreateTestSchema("/Users/michael/local/hc-0/src/HotChocolate/Core/test/Validation.Benchmarks/schema.graphqls");
+ var query = File.ReadAllBytes("/Users/michael/local/hc-0/src/HotChocolate/Core/test/Validation.Benchmarks/query.graphql");
+ _document = Utf8GraphQLParser.Parse(query);
+ }
+
+ [Benchmark]
+ public void Field_Merge_Current()
+ {
+ // clear last run data
+ _context.Clear();
+ _contextData.Clear();
+
+ // prepare context for this run
+ _context.Schema = _schema;
+ _context.ContextData = _contextData;
+ _context.Prepare(_document);
+
+ // run the rule
+ _currentRule.Validate(_context, _document);
+ }
+
+ [Benchmark]
+ public void Field_Merge_New()
+ {
+ // clear last run data
+ _context.Clear();
+ _contextData.Clear();
+
+ // prepare context for this run
+ _context.Schema = _schema;
+ _context.ContextData = _contextData;
+ _context.Prepare(_document);
+
+ // run the rule
+ _newRule.Validate(_context, _document);
+ }
+
+ private static ISchema CreateTestSchema(string fileName) =>
+ SchemaBuilder.New()
+ .AddDocumentFromFile(fileName)
+ .Use(next => next)
+ .Create();
+}
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/Program.cs b/src/HotChocolate/Core/test/Validation.Benchmarks/Program.cs
new file mode 100644
index 00000000000..8d9c0065471
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/Program.cs
@@ -0,0 +1,4 @@
+using BenchmarkDotNet.Running;
+using HotChocolate.Validation.Benchmarks;
+
+BenchmarkRunner.Run();
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/ValidationUtils.cs b/src/HotChocolate/Core/test/Validation.Benchmarks/ValidationUtils.cs
new file mode 100644
index 00000000000..f72140fa854
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/ValidationUtils.cs
@@ -0,0 +1,30 @@
+using HotChocolate.Language;
+using HotChocolate.Types;
+using DirectiveLocation = HotChocolate.Types.DirectiveLocation;
+
+namespace HotChocolate.Validation;
+
+public static class ValidationUtils
+{
+ public static DocumentValidatorContext CreateContext(ISchema schema)
+ => new()
+ {
+ Schema = schema,
+ ContextData = new Dictionary(),
+ };
+
+ public static void Prepare(this IDocumentValidatorContext context, DocumentNode document)
+ {
+ context.Fragments.Clear();
+
+ for (var i = 0; i < document.Definitions.Count; i++)
+ {
+ var definitionNode = document.Definitions[i];
+ if (definitionNode.Kind == SyntaxKind.FragmentDefinition)
+ {
+ var fragmentDefinition = (FragmentDefinitionNode)definitionNode;
+ context.Fragments[fragmentDefinition.Name.Value] = fragmentDefinition;
+ }
+ }
+ }
+}
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/query.graphql b/src/HotChocolate/Core/test/Validation.Benchmarks/query.graphql
new file mode 100644
index 00000000000..cd00c563708
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/query.graphql
@@ -0,0 +1,406 @@
+query useOrgExplorerQueryRef_OrganizationQuery(
+ $aadObjectId: String!
+ $jobTitleFilter: [String]
+ $hybridLocationFilter: String
+ $directsToLoad: Int!
+ $worksWithToLoad: Int!
+ $withHighlights: Boolean = true
+ $withFiltering: Boolean = true
+) {
+ orgexplorer {
+ personInFocusV2(aadObjectId: $aadObjectId) {
+ debugInformation {
+ dataSource
+ isYggdrasilCacheEnabled
+ }
+ fullName
+ ...OrganizationNova
+ id
+ }
+ }
+}
+fragment ColumnPerson on PersonInFocusV2Type {
+ fullName
+ directReportsCount
+ ...Direct
+ ...ExpandedDirects
+}
+fragment Direct on PersonInFocusV2Type {
+ aadObjectId
+ email
+ fullName
+ jobTitle
+ profilePictureUrl
+ transitiveReportsCount
+ directReportsCount
+ location
+ ...PersonaPhotoBlobResolver
+ ...DirectsOverview
+ ...HighlightHint_DataFragment
+ @defer(
+ label: "Direct$defer$HighlightHint_DataFragment"
+ if: $withHighlights
+ )
+ ...PersonaPresenceBadge_PresenceFragment
+ @defer(label: "Direct$defer$PersonaPresenceBadge_PresenceFragment")
+ ...HybridLocation_PresenceFragment
+ @defer(label: "Direct$defer$HybridLocation_PresenceFragment")
+}
+fragment DirectsColonnade on PersonInFocusV2Type {
+ id
+ directReportsCount
+ isManager
+ directs(
+ first: $directsToLoad
+ jobTitleFilter: $jobTitleFilter
+ hybridLocationFilter: $hybridLocationFilter
+ ) {
+ edges {
+ node {
+ aadObjectId
+ directReportsCount
+ id
+ __typename
+ }
+ cursor
+ }
+ ...DirectsColonnadeList
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ ...DirectsColonnadeHeader
+ ...DirectsColonnadeFiltering
+}
+fragment DirectsColonnadeFiltering on PersonInFocusV2Type {
+ ...NextLevelReportsTogglePersonInFocus
+ ...FilterDropDownMenu
+ @defer(
+ label: "DirectsColonnadeFiltering$defer$FilterDropDownMenu"
+ if: $withFiltering
+ )
+}
+fragment DirectsColonnadeHeader on PersonInFocusV2Type {
+ fullName
+ isMe
+}
+fragment DirectsColonnadeList on DirectsConnection {
+ edges {
+ node {
+ aadObjectId
+ ...ColumnPerson
+ directs(first: $directsToLoad) {
+ edges {
+ node {
+ aadObjectId
+ id
+ }
+ }
+ }
+ id
+ }
+ }
+}
+fragment DirectsOverview on PersonInFocusV2Type {
+ fullName
+ directReportsCount
+ transitiveReportsCount
+}
+fragment ExpandedDirects on PersonInFocusV2Type {
+ directs(first: $directsToLoad) {
+ edges {
+ node {
+ aadObjectId
+ ...Direct
+ id
+ __typename
+ }
+ cursor
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ id
+}
+fragment FilterDropDownMenu on PersonInFocusV2Type {
+ directsJobTitles @include(if: $withFiltering)
+}
+fragment HighlightHint_DataFragment on PersonInFocusV2Type {
+ highlights @include(if: $withHighlights) {
+ type
+ ...HighlightsFragment
+ ...HighlightPopupHintFragmentForTelemetry
+ }
+}
+fragment HighlightPopupHintFragmentForTelemetry on PersonaHighlightType {
+ type
+ id
+ metadata {
+ occurrenceType
+ displayRing
+ }
+}
+fragment HighlightsFragment on PersonaHighlightType {
+ hash
+ type
+ id
+ socialDistance
+ vieweeDisplayName
+ vieweeEmail
+ metadata {
+ occurrenceType
+ }
+ highlight {
+ __typename
+ ... on BirthdayType {
+ year
+ month
+ day
+ }
+ ... on PendingRSVPToMeetingType {
+ id
+ meetingId
+ meetingStartDateTime
+ meetingSubject
+ sendResponse
+ }
+ ... on PositionChangeType {
+ newPosition {
+ title
+ company {
+ companyName
+ }
+ }
+ positionChangeType
+ profileUrl
+ }
+ ... on NewHireType {
+ company {
+ companyName
+ }
+ }
+ ... on WorkAnniversaryType {
+ company {
+ companyName
+ }
+ yearsCompleted
+ }
+ }
+}
+fragment HybridLocation_PresenceFragment on PersonInFocusV2Type {
+ location
+ presence {
+ workLocation {
+ location
+ }
+ id
+ }
+}
+fragment ManagerChainToggleButton_PersonFragment on PersonInFocusV2Type {
+ fullName
+}
+fragment ManagerChain_PersonFragment on PersonInFocusV2Type {
+ managerChainTopToBottom(first: 100) {
+ nodes {
+ aadObjectId
+ fullName
+ profilePictureUrl
+ ...PersonaPhotoBlobResolver
+ ...Manager_PersonFragment
+ id
+ }
+ }
+ ...ManagerChainToggleButton_PersonFragment
+}
+fragment Manager_PersonFragment on PersonInFocusV2Type {
+ aadObjectId
+ department
+ fullName
+ jobTitle
+ profilePictureUrl
+ transitiveReportsCount
+ directReportsCount
+ ...PersonaPhotoBlobResolver
+ ...HighlightHint_DataFragment
+ @defer(
+ label: "Manager_PersonFragment$defer$HighlightHint_DataFragment"
+ if: $withHighlights
+ )
+ ...PersonaPresenceBadge_PresenceFragment
+ @defer(
+ label: "Manager_PersonFragment$defer$PersonaPresenceBadge_PresenceFragment"
+ )
+}
+fragment NextLevelReportsTogglePersonInFocus on PersonInFocusV2Type {
+ fullName
+}
+fragment OrganizationNova on PersonInFocusV2Type {
+ ...ManagerChain_PersonFragment
+ ...PeerPillsDirectManager
+ ...PersonInFocusView_PersonFragment
+ ...DirectsColonnade
+ ...WorksWithView
+ managersChain: managerChainTopToBottom(first: 1) {
+ nodes {
+ id
+ }
+ }
+ directsList: directs(first: 1) {
+ nodes {
+ id
+ }
+ }
+ workswith: workingWith(first: 1) {
+ nodes {
+ id
+ }
+ }
+}
+fragment PeerPillsDirectManager on PersonInFocusV2Type {
+ aadObjectId
+ fullName
+ manager: managerChainTopToBottom(last: 1) {
+ nodes {
+ aadObjectId
+ fullName
+ isMe
+ id
+ }
+ }
+ peers(first: $directsToLoad) {
+ edges {
+ node {
+ aadObjectId
+ fullName
+ ...PersonaPillPerson
+ id
+ __typename
+ }
+ cursor
+ }
+ pageInfo {
+ endCursor
+ hasNextPage
+ }
+ }
+ id
+}
+fragment PersonInFocusCopyLinkButton on PersonInFocusV2Type {
+ aadObjectId
+}
+fragment PersonInFocusPersona_PersonFragment on PersonInFocusV2Type {
+ department
+ fullName
+ jobTitle
+ profilePictureUrl
+ location
+ ...PersonaPhotoBlobResolver
+ ...HighlightHint_DataFragment
+ @defer(
+ label: "PersonInFocusPersona_PersonFragment$defer$HighlightHint_DataFragment"
+ if: $withHighlights
+ )
+ ...PersonaPresenceBadge_PresenceFragment
+ @defer(
+ label: "PersonInFocusPersona_PersonFragment$defer$PersonaPresenceBadge_PresenceFragment"
+ )
+ ...HybridLocation_PresenceFragment
+ @defer(
+ label: "PersonInFocusPersona_PersonFragment$defer$HybridLocation_PresenceFragment"
+ )
+}
+fragment PersonInFocusView_PersonFragment on PersonInFocusV2Type {
+ aadObjectId
+ department
+ directReportsCount
+ fullName
+ jobTitle
+ isManager
+ email
+ manager: managerChainTopToBottom(last: 1) {
+ nodes {
+ aadObjectId
+ fullName
+ id
+ }
+ }
+ ...PersonInFocusPersona_PersonFragment
+ ...PersonInFocusCopyLinkButton
+ transitiveReportsCount
+}
+fragment PersonaPhotoBlobResolver on PersonInFocusV2Type {
+ profilePictureUrl
+}
+fragment PersonaPillPerson on PersonInFocusV2Type {
+ aadObjectId
+ fullName
+ profilePictureUrl
+ transitiveReportsCount
+ directReportsCount
+ ...PersonaPhotoBlobResolver
+}
+fragment PersonaPresenceBadge_PresenceFragment on PersonInFocusV2Type {
+ presence {
+ availability
+ calendarData {
+ isOutOfOffice
+ }
+ id
+ }
+}
+fragment PresenceOrHighlightsBadge_PresenceFragment on PersonInFocusV2Type {
+ highlights @include(if: $withHighlights) {
+ id
+ }
+ ...HighlightHint_DataFragment @include(if: $withHighlights)
+ presence {
+ availability
+ calendarData {
+ isOutOfOffice
+ }
+ id
+ }
+ ...PersonaPresenceBadge_PresenceFragment
+}
+fragment WorksWithCarousel on PersonInFocusV2Type {
+ fullName
+ workingWith(first: $worksWithToLoad) {
+ nodes {
+ aadObjectId
+ ...WorksWithPerson
+ id
+ }
+ }
+}
+fragment WorksWithHeader on PersonInFocusV2Type {
+ fullName
+ isMe
+ isManager
+}
+fragment WorksWithPerson on PersonInFocusV2Type {
+ aadObjectId
+ fullName
+ email
+ jobTitle
+ profilePictureUrl
+ ...PersonaPhotoBlobResolver
+ location
+ ...HybridLocation_PresenceFragment
+ @defer(label: "WorksWithPerson$defer$HybridLocation_PresenceFragment")
+ ...PresenceOrHighlightsBadge_PresenceFragment
+ @defer(
+ label: "WorksWithPerson$defer$PresenceOrHighlightsBadge_PresenceFragment"
+ )
+}
+fragment WorksWithView on PersonInFocusV2Type {
+ workingWith(first: $worksWithToLoad) {
+ nodes {
+ id
+ }
+ }
+ ...WorksWithHeader
+ ...WorksWithCarousel
+}
diff --git a/src/HotChocolate/Core/test/Validation.Benchmarks/schema.graphqls b/src/HotChocolate/Core/test/Validation.Benchmarks/schema.graphqls
new file mode 100644
index 00000000000..c95bcbc4db2
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Benchmarks/schema.graphqls
@@ -0,0 +1,786 @@
+schema {
+ query: Query
+}
+
+interface CollabNetDocumentActivity {
+ activityType: CollabNetDocumentActivityTypeEnumType!
+ timestamp: DateTime
+ "Preview text of the activity."
+ preview: String
+ file: DocumentFileType!
+ associatedActors: [CollabNetActorType!]!
+}
+
+"The node interface is implemented by entities that have a global unique identifier."
+interface Node {
+ id: ID!
+}
+
+interface OrgExplorerPersonType {
+ id: ID!
+ aadObjectId: UUID
+ fullName: String
+ profilePictureUrl: String
+ email: String
+ jobTitle: String
+ department: String
+ location: String
+ directReportsCount: Int
+ transitiveReportsCount: Int
+ presence: PresenceType
+ "Gets the person highlights data"
+ highlights: [PersonaHighlightType!]
+ "Gets the person's directs with a limit"
+ directsV2("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): DirectsV2Connection
+}
+
+type ActorType {
+ displayName: String!
+}
+
+type BasicUserInfoType implements OrgExplorerPersonType & Node {
+ id: ID!
+ aadObjectId: UUID!
+ fullName: String!
+ email: String
+ jobTitle: String
+ department: String
+ location: String
+ profilePictureUrl: String
+ directReportsCount: Int
+ transitiveReportsCount: Int
+ presence: PresenceType
+ "Gets the person highlights data"
+ highlights: [PersonaHighlightType!]
+ "Gets the person's directs with paging"
+ directsV2("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): DirectsV2Connection
+}
+
+type BirthdayType {
+ day: Int
+ month: Int
+ year: Int
+}
+
+type CalendarDataType {
+ isOutOfOffice: Boolean!
+}
+
+type CollabNetActorType {
+ aadObjectId: String!
+ emailAddress: String!
+ displayName: String!
+}
+
+type CollabNetDocumentCommentType implements CollabNetDocumentActivity {
+ activityType: CollabNetDocumentActivityTypeEnumType!
+ timestamp: DateTime
+ preview: String
+ file: DocumentFileType!
+ associatedActors: [CollabNetActorType!]!
+ "Deep link URL to specific activity in the file."
+ deepLinkUrl: String!
+}
+
+type CollabNetDocumentMentionedMeActivity implements CollabNetDocumentActivity {
+ activityType: CollabNetDocumentActivityTypeEnumType!
+ timestamp: DateTime
+ preview: String
+ file: DocumentFileType!
+ associatedActors: [CollabNetActorType!]!
+ "Deep link URL to specific activity in the file."
+ deepLinkUrl: String!
+}
+
+type CollabNetDocumentModifyType implements CollabNetDocumentActivity {
+ activityType: CollabNetDocumentActivityTypeEnumType!
+ timestamp: DateTime
+ preview: String
+ file: DocumentFileType!
+ associatedActors: [CollabNetActorType!]!
+}
+
+type CollabNetDocumentMultipleModifyType implements CollabNetDocumentActivity {
+ activityType: CollabNetDocumentActivityTypeEnumType!
+ timestamp: DateTime
+ preview: String
+ file: DocumentFileType!
+ associatedActors: [CollabNetActorType!]!
+}
+
+type CollabNetDocumentReplyType implements CollabNetDocumentActivity {
+ activityType: CollabNetDocumentActivityTypeEnumType!
+ timestamp: DateTime
+ preview: String
+ file: DocumentFileType!
+ associatedActors: [CollabNetActorType!]!
+ "Deep link URL to specific activity in the file."
+ deepLinkUrl: String!
+}
+
+type CollabNetDocumentSharingType implements CollabNetDocumentActivity {
+ activityType: CollabNetDocumentActivityTypeEnumType!
+ timestamp: DateTime
+ preview: String
+ file: DocumentFileType!
+ associatedActors: [CollabNetActorType!]!
+}
+
+"A connection to a list of items."
+type DirectsConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [DirectsEdge!]
+ "A flattened list of the nodes."
+ nodes: [PersonInFocusV2Type!]
+}
+
+"An edge in a connection."
+type DirectsEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: PersonInFocusV2Type!
+}
+
+"A connection to a list of items."
+type DirectsV2Connection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [DirectsV2Edge!]
+ "A flattened list of the nodes."
+ nodes: [BasicUserInfoType!]
+}
+
+"An edge in a connection."
+type DirectsV2Edge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: BasicUserInfoType!
+}
+
+type DocumentFileType {
+ name: String!
+ extension: String!
+ webUrl: String!
+}
+
+type EmailMessageType {
+ "This ID is in EWS format."
+ id: String!
+ subject: String!
+ bodyPreview: String!
+}
+
+type EmailWithLotsOfReactionsType {
+ reactionSummary: ReactionSummaryType!
+ actors: [ActorType]!
+ message: EmailMessageType!
+}
+
+type EmploymentType {
+ title: String!
+ startEndDate: TimeframeType
+ company: LinkedInCompanyType
+}
+
+type HighlightMetadataType {
+ occurrenceType: OccurrenceType
+ displayRing: Boolean
+}
+
+type Identity {
+ displayName: String
+ id: String
+}
+
+type IdentitySet {
+ user: Identity
+ application: Identity
+ device: Identity
+}
+
+type InferenceData {
+ confidenceScore: Float!
+ userHasVerifiedAccuracy: Boolean
+}
+
+type LanguageProficiencyType {
+ id: String!
+ displayName: String!
+ tag: String!
+ written: String!
+ reading: String!
+ allowedAudiences: String!
+ inference: InferenceData
+ lastModifiedBy: IdentitySet
+}
+
+type LinkedInCompanyType {
+ companyName: String!
+ companyLocation: String!
+ companyLogo: String!
+ linkedInUrl: String!
+}
+
+type LinkedInDurationType {
+ numDays: Int
+ numMonths: Int
+ numYears: Int
+}
+
+type LinkedInProfileSuggestionType {
+ displayName: String
+ emails: [String]
+ headline: String
+ photoUrl: String
+ linkedInUrl: String
+ linkedInId: String
+}
+
+type LinkedInSchoolType {
+ schoolName: String!
+ linkedInUrl: String
+ schoolLocation: String
+ schoolLogo: String
+}
+
+"A connection to a list of items."
+type ManagerChainTopToBottomConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [ManagerChainTopToBottomEdge!]
+ "A flattened list of the nodes."
+ nodes: [PersonInFocusV2Type!]
+}
+
+"An edge in a connection."
+type ManagerChainTopToBottomEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: PersonInFocusV2Type!
+}
+
+"A connection to a list of items."
+type ManagersConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [ManagersEdge!]
+ "A flattened list of the nodes."
+ nodes: [BasicUserInfoType!]
+}
+
+"An edge in a connection."
+type ManagersEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: BasicUserInfoType!
+}
+
+type MediaPostType {
+ title: String!
+ description: String!
+ url: String!
+ createdOn: TimestampType
+ thumbnails: [String]!
+ mediaCategory: ShareMediaCategory
+}
+
+type MissedMeetingType {
+ meetingRecapTeamsUrl: String
+ meetingName: String
+}
+
+type NamePronunciationType {
+ name: String
+ pronunciation: String
+}
+
+type NewHireType {
+ company: LinkedInCompanyType
+ hiredOn: TimestampType
+}
+
+type NewSmartNotesAvailableType {
+ notesCount: Int!
+}
+
+type NotFirstDegreeConnectionType {
+ linkedInMemberId: String
+ connectionStatus: ConnectionStatusEnumType
+ connectionDegree: ConnectionDegreeEnumType
+}
+
+type NoteBasedActionType {
+ text: String
+}
+
+type NoteContent {
+ text: String
+ mri: String
+}
+
+type NoteType {
+ message: String
+ messageV2: [NoteContent]
+ publishTime: DateTime!
+ expiry: DateTime!
+}
+
+type OrgExplorerDebugInformationType {
+ dataSource: OrgExplorerDataSource
+ isYggdrasilCacheEnabled: Boolean
+}
+
+type OrgExplorerType {
+ "Gets the person in focus' data using Yggdrasil"
+ personInFocusV2(aadObjectId: String!): PersonInFocusV2Type
+}
+
+type OverlapInfoDetailType {
+ startEndDate: TimeframeType
+ duration: LinkedInDurationType
+}
+
+type OverlapInfoType {
+ detail: OverlapInfoDetailType
+ overlapType: OverlapType
+}
+
+"Information about pagination in a connection."
+type PageInfo {
+ "Indicates whether more edges exist following the set defined by the clients arguments."
+ hasNextPage: Boolean!
+ "Indicates whether more edges exist prior the set defined by the clients arguments."
+ hasPreviousPage: Boolean!
+ "When paginating backwards, the cursor to continue."
+ startCursor: String
+ "When paginating forwards, the cursor to continue."
+ endCursor: String
+}
+
+"A connection to a list of items."
+type PeersConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [PeersEdge!]
+ "A flattened list of the nodes."
+ nodes: [PersonInFocusV2Type!]
+}
+
+"An edge in a connection."
+type PeersEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: PersonInFocusV2Type!
+}
+
+"A connection to a list of items."
+type PeersV2Connection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [PeersV2Edge!]
+ "A flattened list of the nodes."
+ nodes: [BasicUserInfoType!]
+}
+
+"An edge in a connection."
+type PeersV2Edge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: BasicUserInfoType!
+}
+
+type PendingLinkedInInvitationType {
+ linkedInMemberId: String
+ invitationId: String
+ validationToken: String
+}
+
+type PendingRSVPToMeetingType {
+ id: String!
+ meetingId: String!
+ meetingSubject: String!
+ sendResponse: Boolean!
+ meetingStartDateTime: DateTime
+}
+
+type PersonInFocusV2Type implements OrgExplorerPersonType & Node {
+ id: ID!
+ aadObjectId: UUID
+ profilePictureUrl: String
+ fullName: String
+ email: String
+ jobTitle: String
+ department: String
+ location: String
+ phoneNumber: String
+ directReportsCount: Int
+ transitiveReportsCount: Int
+ isMe: Boolean
+ isManager: Boolean
+ "Gets the person in focus' managers chain top to bottom with paging"
+ managerChainTopToBottom("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): ManagerChainTopToBottomConnection
+ "Gets the person in focus' managers chain data with paging"
+ managers("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): ManagersConnection
+ "Gets the person in focus' directs data with paging"
+ directs("Job titles used as a filter for matching directs" jobTitleFilter: [String] "Viva Skills used as a filter for matching directs" vivaSkillsFilter: [String] "Hybrid location used as a filter for matching directs" hybridLocationFilter: String "Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): DirectsConnection
+ "Gets the person in focus' directs data with paging"
+ directsV2("Job titles used as a filter for matching directs" jobTitleFilter: [String] "Hybrid location used as a filter for matching directs" hybridLocationFilter: String "Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): DirectsV2Connection
+ "Gets the person in focus' peers data with paging"
+ peers("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): PeersConnection
+ "Gets the person in focus' peers data with paging"
+ peersV2("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): PeersV2Connection
+ presence: PresenceType
+ "Gets the person in focus' highlights data"
+ highlights: [PersonaHighlightType!]
+ "Gets the person in focus' working with data with paging"
+ workingWith("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): WorkingWithConnection
+ "Gets the person in focus' working with data with paging"
+ workingWithV2("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): WorkingWithV2Connection
+ isCallingYggdrasilEnabled: Boolean @deprecated
+ debugInformation: OrgExplorerDebugInformationType
+ "Gets the person in focus' directs' job titles"
+ directsJobTitles: [String!]
+ "Gets the person in focus' directs' Viva Skills"
+ directsVivaSkills: [String!]
+ "Get the Viva Skills for the user. For other users returns only visible and confirmed skills, for the user gives all skills"
+ vivaSkills("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): VivaSkillsConnection
+}
+
+type PersonaHighlightType {
+ vieweeDisplayName: String!
+ vieweeEmail: String!
+ socialDistance: SocialDistance
+ metadata: HighlightMetadataType
+ id: String!
+ hash: String!
+ highlight: HighlightUnionType
+ type: InsightType
+}
+
+type PositionChangeType {
+ newPosition: EmploymentType
+ positionChangeType: PositionChangeTypeEnumType
+ profileUrl: String!
+}
+
+type PostedAnnouncementType {
+ id: String
+ announcementTitle: String
+ subject: String
+ preview: String
+ channelName: String
+ teamName: String
+ clientThreadId: String
+ internetMessageId: String
+ dateTimeCreated: DateTime!
+}
+
+type PresenceType implements Node {
+ id: ID!
+ aadObjectId: String!
+ activity: String!
+ availability: String!
+ lastActiveTime: DateTime!
+ calendarData: CalendarDataType
+ workLocation: WorkLocationType
+ note: NoteType
+}
+
+type PromotePeopleNoteType {
+ text: String
+}
+
+type Query {
+ orgexplorer: OrgExplorerType
+}
+
+type ReactionSummaryType {
+ totalReactionCount: Int!
+ reactionTypeCount: [ReactionTypeCountType]!
+}
+
+type ReactionTypeCountType {
+ reactionType: ReactionEnumType!
+ reactionCount: Int!
+}
+
+type SharedEducationType {
+ school: LinkedInSchoolType
+ overlapInfo: OverlapInfoType
+}
+
+type SharedExperienceType {
+ company: LinkedInCompanyType
+ overlapInfo: OverlapInfoType
+}
+
+type StorylinePostInYammerType {
+ postType: StorylinePostType!
+ deeplinkUrl: String
+ body: String
+}
+
+type SuggestedWorkingHoursType {
+ workingHoursStart: String
+ workingHoursEnd: String
+ currentWorkingHours: WorkingHours
+}
+
+type TimeZoneBase {
+ name: String
+ bias: String
+}
+
+type TimeframeType {
+ start: TimestampType
+ end: TimestampType
+}
+
+type TimestampType {
+ day: Int
+ month: Int
+ year: Int
+}
+
+type UnifiedPresenceLocationDetailsType {
+ id: String
+ name: String
+ idType: String
+}
+
+type UploadPhotoNudgeType {
+ linkedInPhotoUrl: String
+}
+
+type VivaSkillType implements Node {
+ id: ID!
+ displayName: String
+}
+
+"A connection to a list of items."
+type VivaSkillsConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [VivaSkillsEdge!]
+ "A flattened list of the nodes."
+ nodes: [VivaSkillType!]
+}
+
+"An edge in a connection."
+type VivaSkillsEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: VivaSkillType!
+}
+
+type WorkAnniversaryType {
+ company: LinkedInCompanyType
+ startDate: TimestampType
+ yearsCompleted: Int!
+}
+
+type WorkLocationType {
+ location: String
+ subLocation: String
+ locationSource: String
+ expiry: DateTime!
+ isForced: Boolean!
+ approximateDetails: UnifiedPresenceLocationDetailsType
+}
+
+type WorkingHours {
+ daysOfWeek: [String]
+ startTime: String
+ endTime: String
+ timeZone: TimeZoneBase
+}
+
+"A connection to a list of items."
+type WorkingWithConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [WorkingWithEdge!]
+ "A flattened list of the nodes."
+ nodes: [PersonInFocusV2Type!]
+}
+
+"An edge in a connection."
+type WorkingWithEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: PersonInFocusV2Type!
+}
+
+"A connection to a list of items."
+type WorkingWithV2Connection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [WorkingWithV2Edge!]
+ "A flattened list of the nodes."
+ nodes: [BasicUserInfoType!]
+}
+
+"An edge in a connection."
+type WorkingWithV2Edge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: BasicUserInfoType!
+}
+
+union HighlightUnionType = BirthdayType | NewHireType | SharedEducationType | SharedExperienceType | PendingRSVPToMeetingType | PositionChangeType | MediaPostType | WorkAnniversaryType | NotFirstDegreeConnectionType | PendingLinkedInInvitationType | StorylinePostInYammerType | PostedAnnouncementType | SuggestedWorkingHoursType | UploadPhotoNudgeType | CollabNetDocumentCommentType | CollabNetDocumentMentionedMeActivity | CollabNetDocumentModifyType | CollabNetDocumentMultipleModifyType | CollabNetDocumentReplyType | CollabNetDocumentSharingType | EmailWithLotsOfReactionsType | LanguageProficiencyType | LinkedInProfileSuggestionType | NamePronunciationType | NoteBasedActionType | PromotePeopleNoteType | NewSmartNotesAvailableType | MissedMeetingType
+
+enum CollabNetDocumentActivityTypeEnumType {
+ COMMENT
+ MENTIONED_ME
+ MODIFY
+ MULTIPLE_MODIFY
+ REPLY
+ SHARING
+}
+
+enum ConnectionDegreeEnumType {
+ SELF
+ DISTANCE_1
+ DISTANCE_2
+ DISTANCE_3
+ OUT_OF_NETWORK
+}
+
+enum ConnectionStatusEnumType {
+ NOT_CONNECTED
+ CONNECTED
+ INVITATION_SENT_FROM_VIEWER
+ PENDING_INVITATION_SENT_FROM_VIEWEE
+}
+
+enum InsightType {
+ ARTICLE_POST
+ BIRTHDAY
+ NEW_HIRE
+ SHARED_EDUCATION
+ SHARED_EXPERIENCE
+ PENDING_RSVP_TO_MEETING
+ POSITION_CHANGE
+ VIDEO_POST
+ WORK_ANNIVERSARY
+ NOT_FIRST_DEGREE_CONNECTION
+ PENDING_LINKED_IN_INVITATION
+ STORYLINE_POST
+ POSTED_ANNOUNCEMENT
+ PENDING_TASK
+ SUGGESTED_WORKING_HOURS_NUDGE
+ UPLOAD_PHOTO_NUDGE
+ COLLAB_NET_DOCUMENT_COMMENT
+ COLLAB_NET_DOCUMENT_MENTIONED_ME
+ COLLAB_NET_DOCUMENT_MODIFY
+ COLLAB_NET_DOCUMENT_MULTIPLE_MODIFY
+ COLLAB_NET_DOCUMENT_REPLY
+ COLLAB_NET_DOCUMENT_SHARING
+ EMAIL_WITH_LOTS_OF_REACTIONS
+ LANGUAGE_PROFICIENCY
+ LINKED_IN_PROFILE_SUGGESTION
+ NAME_PRONUNCIATION
+ NOTE_BASED_ACTION
+ PROMOTE_PEOPLE_NOTE
+ NEW_SMART_NOTES_AVAILABLE
+ MISSED_MEETING
+}
+
+enum OccurrenceType {
+ NOT_SET
+ NEW
+ REPEAT
+}
+
+enum OrgExplorerDataSource {
+ YGGDRASIL
+ DSAPI
+}
+
+enum OverlapType {
+ BOTH_CURRENT_VIEWER_STARTED_FIRST
+ BOTH_CURRENT_VIEWEE_STARTED_FIRST
+ BOTH_CURRENT_STARTED_IN_SAME_MONTH
+ NOT_BOTH_CURRENT_NO_OVERLAP_VIEWER_STARTED_FIRST
+ NOT_BOTH_CURRENT_NO_OVERLAP_VIEWEE_STARTED_FIRST
+ NOT_BOTH_CURRENT_OVERLAP
+}
+
+enum PositionChangeTypeEnumType {
+ PROMOTION
+ COMPANY_CHANGE
+ POSITION_CHANGE
+ ADDITIONAL_POSITION
+}
+
+enum ReactionEnumType {
+ CELEBRATE
+ HEART
+ LAUGH
+ LIKE
+ SURPRISED
+ SAD
+ UNLIKE
+ NONE
+}
+
+enum ShareMediaCategory {
+ ARTICLE
+ IMAGE
+ NONE
+ RICH
+ VIDEO
+ LEARNING_COURSE
+ JOB
+ QUESTION
+ ANSWER
+ CAROUSEL
+ TOPIC
+ NATIVE_DOCUMENT
+ URN_REFERENCE
+ LIVE_VIDEO
+}
+
+enum SocialDistance {
+ CLOSE_COLLEAGUE
+ FORMER_RECONNECT
+ STRANGER
+ RECENT_RECONNECT
+}
+
+enum StorylinePostType {
+ NONE
+ DISCUSSION
+ QUESTION
+ PRAISE
+ POLL
+}
+
+directive @domain(name: String) on OBJECT | INTERFACE | UNION | ENUM | INPUT_OBJECT
+
+"The `DateTime` scalar represents an ISO-8601 compliant date time type."
+scalar DateTime @specifiedBy(url: "https:\/\/www.graphql-scalars.com\/date-time")
+
+scalar UUID @specifiedBy(url: "https:\/\/tools.ietf.org\/html\/rfc4122")
+
+"The `Upload` scalar type represents a file upload."
+scalar Upload
\ No newline at end of file
diff --git a/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorVisitorTestBase.cs b/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorVisitorTestBase.cs
index c07b8f08aec..c967e535789 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorVisitorTestBase.cs
+++ b/src/HotChocolate/Core/test/Validation.Tests/DocumentValidatorVisitorTestBase.cs
@@ -37,7 +37,7 @@ protected DocumentValidatorVisitorTestBase(Action configure)
public void ContextIsNull()
{
// arrange
- var query = Utf8GraphQLParser.Parse(@"{ foo }");
+ var query = Utf8GraphQLParser.Parse("{ foo }");
// act
var a = () => Rule.Validate(null!, query);
@@ -101,6 +101,19 @@ protected void ExpectErrors(
Assert.Collection(context.Errors, elementInspectors);
}
- context.Errors.MatchSnapshot();
+ var snapshot = Snapshot.Create();
+
+ foreach (var error in context.Errors)
+ {
+ snapshot.Add(new
+ {
+ error.Message,
+ error.Locations,
+ Path = error.Path?.ToString(),
+ error.Extensions
+ });
+ }
+
+ snapshot.Match();
}
}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs b/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs
index 456136db73c..60f748080e4 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs
+++ b/src/HotChocolate/Core/test/Validation.Tests/FieldSelectionMergingRuleTests.cs
@@ -1,10 +1,11 @@
using HotChocolate.Types;
+using HotChocolate.Validation.Rules;
using Microsoft.Extensions.DependencyInjection;
namespace HotChocolate.Validation;
public class FieldSelectionMergingRuleTests()
- : DocumentValidatorVisitorTestBase(builder => builder.AddFieldRules())
+ : DocumentValidatorVisitorTestBase(builder => builder.TryAddValidationRule())
{
[Fact]
public void MergeIdenticalFields()
@@ -57,10 +58,7 @@ fragment conflictingBecauseAlias on Dog {
name: nickname
name
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -114,10 +112,7 @@ fragment conflictingArgsOnValues on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: HEEL)
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -135,10 +130,7 @@ fragment conflictingArgsValueAndVar on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand(dogCommand: $dogCommand)
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -156,10 +148,7 @@ fragment conflictingArgsWithVars on Dog {
doesKnowCommand(dogCommand: $varOne)
doesKnowCommand(dogCommand: $varTwo)
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -177,10 +166,7 @@ fragment differingArgs on Dog {
doesKnowCommand(dogCommand: SIT)
doesKnowCommand
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -200,10 +186,7 @@ ... dog
fragment dog on Dog {
doesKnowCommand
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -269,10 +252,7 @@ ... on Cat {
someValue: meowVolume
}
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -333,10 +313,7 @@ fields @stream(initialCount: 2) {
}
}
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -357,10 +334,7 @@ fields @stream(initialCount: 1) {
}
}
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -382,10 +356,7 @@ ... FooLevel2
fragment FooLevel2 on Dog {
doesKnowCommand(dogCommand: HEEL)
}
- """,
- t => Assert.Equal(
- "Encountered fields for the same object that cannot be merged.",
- t.Message));
+ """);
}
[Fact]
@@ -949,7 +920,7 @@ public void CompatibleReturnShapesOnDifferentReturnTypes()
"""
{
someBox {
- ... on SomeBox {
+ ... on IntBox {
deepBox {
unrelatedField
}
@@ -1072,8 +1043,7 @@ ... on StringBox {
""");
}
- // TODO : Fix this issue
- [Fact(Skip = "This one needs fixing!")]
+ [Fact]
public void DisallowsDifferingDeepReturnTypesDespiteNoOverlap()
{
ExpectErrors(
@@ -1115,12 +1085,10 @@ ... on StringBox {
""");
}
- // TODO : we need to analyze this validation issue further.
- [Fact(Skip = "This one needs to be analyzed further.")]
+ [Fact]
public void SameWrappedScalarReturnTypes()
{
- ExpectErrors(
- TestSchema,
+ ExpectValid(
"""
{
someBox {
@@ -1189,7 +1157,7 @@ ... sameAliasesWithDifferentFieldTargets
}
fragment sameAliasesWithDifferentFieldTargets on Dog {
- ...sameAliasesWithDifferentFieldTargets
+ ... sameAliasesWithDifferentFieldTargets
fido: name
fido: nickname
}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/IntrospectionDepthRuleTests.cs b/src/HotChocolate/Core/test/Validation.Tests/IntrospectionDepthRuleTests.cs
index d48f598e275..46df8176cd6 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/IntrospectionDepthRuleTests.cs
+++ b/src/HotChocolate/Core/test/Validation.Tests/IntrospectionDepthRuleTests.cs
@@ -7,7 +7,8 @@ namespace HotChocolate.Validation;
public class IntrospectionDepthRuleTests()
: DocumentValidatorVisitorTestBase(b => b.AddIntrospectionDepthRule())
{
- [Fact] public void Introspection_With_Cycles_Will_Fail()
+ [Fact]
+ public void Introspection_With_Cycles_Will_Fail()
{
// arrange
IDocumentValidatorContext context = ValidationUtils.CreateContext();
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.AliasMaskingDirectFieldAccess.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.AliasMaskingDirectFieldAccess.snap
index fd6d1d1a68d..0341c2dfad5 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.AliasMaskingDirectFieldAccess.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.AliasMaskingDirectFieldAccess.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "nickname",
- "fieldB": "name",
- "typeA": "String",
- "typeB": "String!",
- "responseNameA": "name",
- "responseNameB": "name",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `name` conflict because they return conflicting types `String` and `String!`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/catOrDog/name",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ComparesDeepTypesIncludingList.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ComparesDeepTypesIncludingList.snap
index 9875ffb368c..0fe4eb8034c 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ComparesDeepTypesIncludingList.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ComparesDeepTypesIncludingList.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 15,
- "Column": 13
- },
- {
- "Line": 6,
- "Column": 17
- }
- ],
- "Extensions": {
- "declaringTypeA": "Node",
- "declaringTypeB": "Node",
- "fieldA": "id",
- "fieldB": "name",
- "typeA": "ID",
- "typeB": "String",
- "responseNameA": "id",
- "responseNameB": "id",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `name` conflict because they return conflicting types `ID` and `String`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 15,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 6,
+ "Column": 17
+ }
+ ],
+ "Path": "/connection/edges/node/id",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgNames.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgNames.snap
index 05eefd6fbcb..d2e1856f5a8 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgNames.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgNames.snap
@@ -1,56 +1,17 @@
-[
- {
- "Message": "The field `isAtLocation` does not exist on the type `Dog`.",
- "Code": null,
- "Path": {
- "Name": "catOrDog",
- "Parent": {
- "Parent": null,
- "Length": 0,
- "IsRoot": true
- },
- "Length": 1,
- "IsRoot": false
+{
+ "Message": "Fields `isAtLocation` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- }
- ],
- "Extensions": {
- "type": "Dog",
- "field": "isAtLocation",
- "responseName": "isAtLocation",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types"
- },
- "Exception": null
- },
- {
- "Message": "The field `isAtLocation` does not exist on the type `Dog`.",
- "Code": null,
- "Path": {
- "Name": "catOrDog",
- "Parent": {
- "Parent": null,
- "Length": 0,
- "IsRoot": true
- },
- "Length": 1,
- "IsRoot": false
- },
- "Locations": [
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "type": "Dog",
- "field": "isAtLocation",
- "responseName": "isAtLocation",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selections-on-Objects-Interfaces-and-Unions-Types"
- },
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/catOrDog/isAtLocation",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgValues.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgValues.snap
index d369071123b..cd5a01b19ce 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgValues.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgValues.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/catOrDog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsOnValues.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsOnValues.snap
index d369071123b..bf85c1a312d 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsOnValues.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsOnValues.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/dog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsValueAndVar.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsValueAndVar.snap
index d369071123b..bf85c1a312d 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsValueAndVar.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsValueAndVar.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/dog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsWithVars.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsWithVars.snap
index d369071123b..bf85c1a312d 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsWithVars.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingArgsWithVars.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/dog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingBecauseAlias.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingBecauseAlias.snap
index fd6d1d1a68d..46ac7b71651 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingBecauseAlias.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingBecauseAlias.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "nickname",
- "fieldB": "name",
- "typeA": "String",
- "typeB": "String!",
- "responseNameA": "name",
- "responseNameB": "name",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `name` conflict because they return conflicting types `String` and `String!`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/dog/name",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingDifferingResponses.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingDifferingResponses.snap
index 7e3b69d30bf..8afc2f81fe4 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingDifferingResponses.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingDifferingResponses.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 9,
- "Column": 9
- },
- {
- "Line": 12,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Cat",
- "fieldA": "nickname",
- "fieldB": "meowVolume",
- "typeA": "String",
- "typeB": "Int",
- "responseNameA": "someValue",
- "responseNameB": "someValue",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `meowVolume` conflict because they return conflicting types `String` and `Int`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 9,
+ "Column": 9
},
- "Exception": null
+ {
+ "Line": 12,
+ "Column": 9
+ }
+ ],
+ "Path": "/dog/someValue",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingReturnTypesWhichPotentiallyOverlap.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingReturnTypesWhichPotentiallyOverlap.snap
index 75664b2b0c5..99d39fc0fd1 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingReturnTypesWhichPotentiallyOverlap.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ConflictingReturnTypesWhichPotentiallyOverlap.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 13
- },
- {
- "Line": 7,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "IntBox",
- "declaringTypeB": "NonNullStringBox1",
- "fieldA": "scalar",
- "fieldB": "scalar",
- "typeA": "Int",
- "typeB": "String!",
- "responseNameA": "scalar",
- "responseNameB": "scalar",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `scalar` conflict because they return conflicting types `Int` and `String!`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 7,
+ "Column": 13
+ }
+ ],
+ "Path": "/someBox/scalar",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflict.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflict.snap
index a8fd51041ce..e88fc0aef54 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflict.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflict.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 3,
- "Column": 9
- },
- {
- "Line": 6,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 3,
+ "Column": 9
},
- "Exception": null
+ {
+ "Line": 6,
+ "Column": 9
+ }
+ ],
+ "Path": "/f1/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflictWithMultipleIssues.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflictWithMultipleIssues.snap
index 9d64ec38e5d..cac3658b471 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflictWithMultipleIssues.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DeepConflictWithMultipleIssues.snap
@@ -1,56 +1,39 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 3,
- "Column": 9
- },
- {
- "Line": 7,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+---------------
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 3,
+ "Column": 9
},
- "Exception": null
- },
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 9
- },
- {
- "Line": 8,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "c",
- "fieldB": "d",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "y",
- "responseNameB": "y",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ {
+ "Line": 7,
+ "Column": 9
+ }
+ ],
+ "Path": "/f1/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ }
+}
+---------------
+
+---------------
+{
+ "Message": "Fields `c` and `d` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 9
},
- "Exception": null
+ {
+ "Line": 8,
+ "Column": 9
+ }
+ ],
+ "Path": "/f1/y",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
+---------------
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondAddsAnArgument.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondAddsAnArgument.snap
index c2d166bc2b9..4ea9441e96f 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondAddsAnArgument.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondAddsAnArgument.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 7,
- "Column": 5
- },
- {
- "Line": 8,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 7,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 8,
+ "Column": 5
+ }
+ ],
+ "Path": "/catOrDog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondMissingAnArgument.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondMissingAnArgument.snap
index d369071123b..cd5a01b19ce 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondMissingAnArgument.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferentArgsSecondMissingAnArgument.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/catOrDog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferingArgs.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferingArgs.snap
index d369071123b..bf85c1a312d 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferingArgs.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DifferingArgs.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/dog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingDeepReturnTypesDespiteNoOverlap.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingDeepReturnTypesDespiteNoOverlap.snap
new file mode 100644
index 00000000000..08fe2006ec4
--- /dev/null
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingDeepReturnTypesDespiteNoOverlap.snap
@@ -0,0 +1,17 @@
+{
+ "Message": "Fields `scalar` conflict because they return conflicting types `String` and `Int`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 5,
+ "Column": 17
+ },
+ {
+ "Line": 10,
+ "Column": 17
+ }
+ ],
+ "Path": "/someBox/box/scalar",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ }
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlap.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlap.snap
index 4f383b2b360..7da138e303d 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlap.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlap.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 13
- },
- {
- "Line": 9,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "IntBox",
- "declaringTypeB": "StringBox",
- "fieldA": "listStringBox",
- "fieldB": "stringBox",
- "typeA": "[StringBox]",
- "typeB": "StringBox",
- "responseNameA": "box",
- "responseNameB": "box",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `stringBox` conflict because they return conflicting types `[StringBox]` and `StringBox`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 13
+ }
+ ],
+ "Path": "/someBox/box",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlapReverse.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlapReverse.snap
index 2fd580a227f..8fd58a3c00d 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlapReverse.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeListDespiteNoOverlapReverse.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 13
- },
- {
- "Line": 9,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "IntBox",
- "declaringTypeB": "StringBox",
- "fieldA": "stringBox",
- "fieldB": "listStringBox",
- "typeA": "StringBox",
- "typeB": "[StringBox]",
- "responseNameA": "box",
- "responseNameB": "box",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `listStringBox` conflict because they return conflicting types `StringBox` and `[StringBox]`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 13
+ }
+ ],
+ "Path": "/someBox/box",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeNullabilityDespiteNoOverlap.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeNullabilityDespiteNoOverlap.snap
index 5e7c942bc77..aee850cb0dd 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeNullabilityDespiteNoOverlap.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypeNullabilityDespiteNoOverlap.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 13
- },
- {
- "Line": 7,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "NonNullStringBox1",
- "declaringTypeB": "StringBox",
- "fieldA": "scalar",
- "fieldB": "scalar",
- "typeA": "String!",
- "typeB": "String",
- "responseNameA": "scalar",
- "responseNameB": "scalar",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `scalar` conflict because they return conflicting types `String!` and `String`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 7,
+ "Column": 13
+ }
+ ],
+ "Path": "/someBox/scalar",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypesDespiteNoOverlap.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypesDespiteNoOverlap.snap
index fd986040091..f8259974629 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypesDespiteNoOverlap.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingReturnTypesDespiteNoOverlap.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 13
- },
- {
- "Line": 7,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "IntBox",
- "declaringTypeB": "StringBox",
- "fieldA": "scalar",
- "fieldB": "scalar",
- "typeA": "Int",
- "typeB": "String",
- "responseNameA": "scalar",
- "responseNameB": "scalar",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `scalar` conflict because they return conflicting types `Int` and `String`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 7,
+ "Column": 13
+ }
+ ],
+ "Path": "/someBox/scalar",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingSubfields.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingSubfields.snap
index 78f177e2a67..49a61efdeb6 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingSubfields.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.DisallowsDifferingSubfields.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 5,
- "Column": 17
- },
- {
- "Line": 6,
- "Column": 17
- }
- ],
- "Extensions": {
- "declaringTypeA": "StringBox",
- "declaringTypeB": "StringBox",
- "fieldA": "scalar",
- "fieldB": "unrelatedField",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "val",
- "responseNameB": "val",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `scalar` and `unrelatedField` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 5,
+ "Column": 17
},
- "Exception": null
+ {
+ "Line": 6,
+ "Column": 17
+ }
+ ],
+ "Path": "/someBox/box/val",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.EncountersConflictInFragments.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.EncountersConflictInFragments.snap
index 32da67820a8..4f41bcdd710 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.EncountersConflictInFragments.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.EncountersConflictInFragments.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 7,
- "Column": 5
- },
- {
- "Line": 11,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 7,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 11,
+ "Column": 5
+ }
+ ],
+ "Path": "/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.FindsInvalidCaseEvenWithImmediatelyRecursiveFragment.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.FindsInvalidCaseEvenWithImmediatelyRecursiveFragment.snap
index 91c6874c13a..401e9c0729a 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.FindsInvalidCaseEvenWithImmediatelyRecursiveFragment.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.FindsInvalidCaseEvenWithImmediatelyRecursiveFragment.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 9,
- "Column": 5
- },
- {
- "Line": 10,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "name",
- "fieldB": "nickname",
- "typeA": "String!",
- "typeB": "String",
- "responseNameA": "fido",
- "responseNameB": "fido",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `nickname` conflict because they return conflicting types `String!` and `String`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 9,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 10,
+ "Column": 5
+ }
+ ],
+ "Path": "/dogOrHuman/fido",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictInNestedFragments.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictInNestedFragments.snap
index ab73308a09f..5513583dc3a 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictInNestedFragments.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictInNestedFragments.snap
@@ -1,56 +1,39 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 11,
- "Column": 5
- },
- {
- "Line": 25,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+---------------
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 11,
+ "Column": 5
},
- "Exception": null
- },
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 16,
- "Column": 5
- },
- {
- "Line": 20,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "c",
- "fieldB": "d",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "y",
- "responseNameB": "y",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ {
+ "Line": 25,
+ "Column": 5
+ }
+ ],
+ "Path": "/f1/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ }
+}
+---------------
+
+---------------
+{
+ "Message": "Fields `c` and `d` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 16,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 20,
+ "Column": 5
+ }
+ ],
+ "Path": "/f1/y",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
+---------------
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestor.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestor.snap
index f2c0776ec2b..39919153d1e 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestor.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestor.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 13
- },
- {
- "Line": 7,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 7,
+ "Column": 13
+ }
+ ],
+ "Path": "/f1/f2/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestorInFragments.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestorInFragments.snap
index 7bc912ca2cb..158cf31a83f 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestorInFragments.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsDeepConflictToNearestCommonAncestorInFragments.snap
@@ -1,56 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 12,
- "Column": 13
- },
- {
- "Line": 15,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 12,
+ "Column": 13
},
- "Exception": null
- },
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 15,
- "Column": 13
- },
- {
- "Line": 12,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "b",
- "fieldB": "a",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
- },
- "Exception": null
+ {
+ "Line": 15,
+ "Column": 13
+ }
+ ],
+ "Path": "/f1/f2/f3/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsEachConflictOnce.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsEachConflictOnce.snap
index a77010c0561..7c927409360 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsEachConflictOnce.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ReportsEachConflictOnce.snap
@@ -1,110 +1,43 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 18,
- "Column": 5
- },
- {
- "Line": 22,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+---------------
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 18,
+ "Column": 5
},
- "Exception": null
- },
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 22,
- "Column": 5
- },
- {
- "Line": 18,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "b",
- "fieldB": "a",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
- },
- "Exception": null
- },
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 18,
- "Column": 5
- },
- {
- "Line": 13,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "c",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ {
+ "Line": 22,
+ "Column": 5
+ }
+ ],
+ "Path": "/f1/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ }
+}
+---------------
+
+---------------
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 18,
+ "Column": 5
},
- "Exception": null
- },
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 22,
- "Column": 5
- },
- {
- "Line": 13,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "b",
- "fieldB": "c",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+ {
+ "Line": 22,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 13,
+ "Column": 9
+ }
+ ],
+ "Path": "/f3/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
+---------------
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesAllowedOnNonOverlappingFields.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesAllowedOnNonOverlappingFields.snap
index 178d2d45d29..eaffae3976f 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesAllowedOnNonOverlappingFields.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesAllowedOnNonOverlappingFields.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 9,
- "Column": 9
- },
- {
- "Line": 12,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Cat",
- "fieldA": "name",
- "fieldB": "nickname",
- "typeA": "String!",
- "typeB": "String",
- "responseNameA": "name",
- "responseNameB": "name",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `nickname` conflict because they return conflicting types `String!` and `String`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 9,
+ "Column": 9
},
- "Exception": null
+ {
+ "Line": 12,
+ "Column": 9
+ }
+ ],
+ "Path": "/catOrDog/name",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesWithDifferentFieldTargets.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesWithDifferentFieldTargets.snap
index e445b6e464b..79daaed18b0 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesWithDifferentFieldTargets.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameAliasesWithDifferentFieldTargets.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 8,
- "Column": 5
- },
- {
- "Line": 9,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "name",
- "fieldB": "nickname",
- "typeA": "String!",
- "typeB": "String",
- "responseNameA": "fido",
- "responseNameB": "fido",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `nickname` conflict because they return conflicting types `String!` and `String`. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 8,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 5
+ }
+ ],
+ "Path": "/catOrDog/fido",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameResponseNameDifferentFieldName.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameResponseNameDifferentFieldName.snap
index 63503fbdb37..5626b1b4f75 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameResponseNameDifferentFieldName.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.SameResponseNameDifferentFieldName.snap
@@ -1,29 +1,17 @@
-[
- {
- "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": "catOrDog",
- "fieldB": "dogOrHuman",
- "typeA": "CatOrDog",
- "typeB": "DogOrHuman",
- "responseNameA": "catOrDog",
- "responseNameB": "catOrDog",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `catOrDog` and `dogOrHuman` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 2,
+ "Column": 5
},
- "Exception": null
+ {
+ "Line": 5,
+ "Column": 5
+ }
+ ],
+ "Path": "/catOrDog",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ShortHandQueryWithDuplicateFieldInSecondLevelFragment.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ShortHandQueryWithDuplicateFieldInSecondLevelFragment.snap
index d47bb97303c..1c71e3e2e15 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ShortHandQueryWithDuplicateFieldInSecondLevelFragment.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.ShortHandQueryWithDuplicateFieldInSecondLevelFragment.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 3,
- "Column": 9
- },
- {
- "Line": 13,
- "Column": 5
- }
- ],
- "Extensions": {
- "declaringTypeA": "Dog",
- "declaringTypeB": "Dog",
- "fieldA": "doesKnowCommand",
- "fieldB": "doesKnowCommand",
- "typeA": "Boolean!",
- "typeB": "Boolean!",
- "responseNameA": "doesKnowCommand",
- "responseNameB": "doesKnowCommand",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `doesKnowCommand` conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional. ",
+ "Locations": [
+ {
+ "Line": 3,
+ "Column": 9
},
- "Exception": null
+ {
+ "Line": 13,
+ "Column": 5
+ }
+ ],
+ "Path": "/dog/doesKnowCommand",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_Argument_Mismatch.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_Argument_Mismatch.snap
index 37b48a660fc..3d14c8316e6 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_Argument_Mismatch.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_Argument_Mismatch.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 9
- },
- {
- "Line": 9,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "__Type",
- "declaringTypeB": "__Type",
- "fieldA": "fields",
- "fieldB": "fields",
- "typeA": "[__Field!]",
- "typeB": "[__Field!]",
- "responseNameA": "fields",
- "responseNameB": "fields",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `fields` conflict because they have differing stream directives. ",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 9
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 9
+ }
+ ],
+ "Path": "/__type/fields",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_On_Some_Fields.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_On_Some_Fields.snap
index 37b48a660fc..3d14c8316e6 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_On_Some_Fields.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.Stream_On_Some_Fields.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 9
- },
- {
- "Line": 9,
- "Column": 9
- }
- ],
- "Extensions": {
- "declaringTypeA": "__Type",
- "declaringTypeB": "__Type",
- "fieldA": "fields",
- "fieldB": "fields",
- "typeA": "[__Field!]",
- "typeB": "[__Field!]",
- "responseNameA": "fields",
- "responseNameB": "fields",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `fields` conflict because they have differing stream directives. ",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 9
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 9
+ }
+ ],
+ "Path": "/__type/fields",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.VeryDeepConflict.snap b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.VeryDeepConflict.snap
index 66698e8c799..80fdbf2e632 100644
--- a/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.VeryDeepConflict.snap
+++ b/src/HotChocolate/Core/test/Validation.Tests/__snapshots__/FieldSelectionMergingRuleTests.VeryDeepConflict.snap
@@ -1,29 +1,17 @@
-[
- {
- "Message": "Encountered fields for the same object that cannot be merged.",
- "Code": null,
- "Path": null,
- "Locations": [
- {
- "Line": 4,
- "Column": 13
- },
- {
- "Line": 9,
- "Column": 13
- }
- ],
- "Extensions": {
- "declaringTypeA": "Query",
- "declaringTypeB": "Query",
- "fieldA": "a",
- "fieldB": "b",
- "typeA": "String",
- "typeB": "String",
- "responseNameA": "x",
- "responseNameB": "x",
- "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
+{
+ "Message": "Fields `a` and `b` conflict because they have differing names. Use different aliases on the fields to fetch both if this was intentional.",
+ "Locations": [
+ {
+ "Line": 4,
+ "Column": 13
},
- "Exception": null
+ {
+ "Line": 9,
+ "Column": 13
+ }
+ ],
+ "Path": "/f1/f2/x",
+ "Extensions": {
+ "specifiedBy": "https://spec.graphql.org/October2021/#sec-Field-Selection-Merging"
}
-]
+}
diff --git a/src/HotChocolate/Language/src/Language.SyntaxTree/Location.cs b/src/HotChocolate/Language/src/Language.SyntaxTree/Location.cs
index 9c8963f5bd7..85229797f38 100644
--- a/src/HotChocolate/Language/src/Language.SyntaxTree/Location.cs
+++ b/src/HotChocolate/Language/src/Language.SyntaxTree/Location.cs
@@ -3,7 +3,7 @@ namespace HotChocolate.Language;
///
/// The location of a .
///
-public sealed class Location : IEquatable
+public sealed class Location : IEquatable, IComparable
{
///
/// Initializes a new instance of .
@@ -110,4 +110,25 @@ public override int GetHashCode()
public static bool operator !=(Location? left, Location? right)
=> !Equals(left, right);
+
+ public int CompareTo(Location? other)
+ {
+ if (ReferenceEquals(this, other))
+ {
+ return 0;
+ }
+
+ if (other is null)
+ {
+ return 1;
+ }
+
+ var startComparison = Start.CompareTo(other.Start);
+ if (startComparison != 0)
+ {
+ return startComparison;
+ }
+
+ return End.CompareTo(other.End);
+ }
}