From bc4d0134ebd00f7e4062ee4f5e73ca01e52b3beb Mon Sep 17 00:00:00 2001 From: tobias-tengler <45513122+tobias-tengler@users.noreply.github.com> Date: Tue, 27 Oct 2026 09:28:28 +0000 Subject: [PATCH] Reject selection sets on __typename for abstract parent types --- .../Core/src/Validation/ErrorHelper.cs | 2 +- .../Rules/LeafFieldSelectionsRule.cs | 16 +++++++ .../LeafFieldSelectionsRuleTests.cs | 42 +++++++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/HotChocolate/Core/src/Validation/ErrorHelper.cs b/src/HotChocolate/Core/src/Validation/ErrorHelper.cs index c92e1fe4917..bd44f36e3c6 100644 --- a/src/HotChocolate/Core/src/Validation/ErrorHelper.cs +++ b/src/HotChocolate/Core/src/Validation/ErrorHelper.cs @@ -170,7 +170,7 @@ public IError FieldDoesNotExist(FieldNode node, IComplexTypeDefinition outputTyp public IError LeafFieldsCannotHaveSelections( FieldNode node, - IComplexTypeDefinition declaringType, + ITypeDefinition declaringType, IType fieldType) { return ErrorBuilder.New() diff --git a/src/HotChocolate/Core/src/Validation/Rules/LeafFieldSelectionsRule.cs b/src/HotChocolate/Core/src/Validation/Rules/LeafFieldSelectionsRule.cs index 781003f7e18..c2561146543 100644 --- a/src/HotChocolate/Core/src/Validation/Rules/LeafFieldSelectionsRule.cs +++ b/src/HotChocolate/Core/src/Validation/Rules/LeafFieldSelectionsRule.cs @@ -102,6 +102,22 @@ private void ValidateField( { var namedParentType = parentType.NamedType(); + // __typename can be selected on unions and interfaces, which don't have this field, + // so we need this special case for it. + if (IntrospectionFieldNames.TypeName.Equals(field.Name.Value, StringComparison.Ordinal)) + { + if (field.SelectionSet is not null) + { + context.ReportError( + context.LeafFieldsCannotHaveSelections( + field, + namedParentType, + new NonNullType(context.Schema.Types["String"]))); + } + + return; + } + if (namedParentType is not IComplexTypeDefinition complex || !complex.Fields.TryGetField(field.Name.Value, out var fieldDef)) { diff --git a/src/HotChocolate/Core/test/Validation.Tests/LeafFieldSelectionsRuleTests.cs b/src/HotChocolate/Core/test/Validation.Tests/LeafFieldSelectionsRuleTests.cs index 9b68e76b97c..b7343672adf 100644 --- a/src/HotChocolate/Core/test/Validation.Tests/LeafFieldSelectionsRuleTests.cs +++ b/src/HotChocolate/Core/test/Validation.Tests/LeafFieldSelectionsRuleTests.cs @@ -44,6 +44,48 @@ public void Validate_Should_RejectInvalidSelections_WhenEnabled( Assert.Equal(expectedMessage, Assert.Single(context.Errors).Message); } + [Theory] + [InlineData("{ dog { __typename { name } } }")] + [InlineData("{ human { pets { __typename { name } } } }")] + [InlineData("{ catOrDog { __typename { name } } }")] + [InlineData("{ catOrDog { ...f } } fragment f on CatOrDog { __typename { name } }")] + public void Validate_Should_RejectSelectionsOnTypeName_When_ParentIsAnyCompositeType(string sourceText) + { + // arrange + var rule = new LeafFieldSelectionsRule(); + var document = Utf8GraphQLParser.Parse(sourceText); + var context = ValidationUtils.CreateContext(document); + + // act + rule.Validate(context, document); + + // assert + Assert.Equal( + "Field \"__typename\" must not have a selection since type \"String!\" has no subfields.", + Assert.Single(context.Errors).Message); + } + + [Fact] + public void TypeNameOnAbstractTypes() + { + ExpectValid( + """ + { + dog { + __typename + } + human { + pets { + __typename + } + } + catOrDog { + __typename + } + } + """); + } + [Fact] public void ScalarSelection() {