diff --git a/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs b/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs index 18c339e76c2..933de2aa1a2 100644 --- a/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs +++ b/src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs @@ -104,6 +104,11 @@ public override void OnBeforeCompleteType(ITypeCompletionContext completionConte continue; } + if (field.Name == "id") + { + continue; + } + var levels = GetSemanticNonNullLevels(field.Type); if (levels.Count < 1) diff --git a/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs b/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs index 568e63f8564..450a3d472a6 100644 --- a/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs +++ b/src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs @@ -3,6 +3,7 @@ using HotChocolate.Execution; using HotChocolate.Tests; using HotChocolate.Types; +using HotChocolate.Types.Descriptors; using HotChocolate.Types.Relay; using Microsoft.Extensions.DependencyInjection; @@ -25,6 +26,20 @@ public async Task Object_With_Id_Field() .MatchSnapshotAsync(); } + [Fact] + public async Task Interface_With_Id_Field() + { + await new ServiceCollection() + .AddGraphQL() + .AddGlobalObjectIdentification() + .ModifyOptions(o => o.EnableSemanticNonNull = true) + .AddQueryType() + .AddType() + .UseField(_ => _ => default) + .BuildSchemaAsync() + .MatchSnapshotAsync(); + } + [Fact] public async Task MutationConventions() { @@ -137,6 +152,43 @@ type Foo { .MatchSnapshotAsync(); } + public class QueryWithInteface + { + public SomeObject GetSomeObject() => new(); + } + + [Node] + [ImplementsInterface] + public record SomeObject + { + public int Id { get; set; } + + public string Field { get; set; } = default!; + + public static SomeObject? Get(int id) => new(); + } + + public class InterfaceImplementingNode : InterfaceType + { + protected override void Configure(IInterfaceTypeDescriptor descriptor) + { + descriptor.Implements(); + descriptor + .Field("field") + .Type("String!"); + } + } + + [AttributeUsage(AttributeTargets.Class)] + public sealed class ImplementsInterfaceAttribute : ObjectTypeDescriptorAttribute + where T : InterfaceType + { + protected override void OnConfigure( + IDescriptorContext context, + IObjectTypeDescriptor descriptor, + Type type) => descriptor.Implements(); + } + public class QueryType : ObjectType { protected override void Configure(IObjectTypeDescriptor descriptor) diff --git a/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Interface_With_Id_Field.snap b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Interface_With_Id_Field.snap new file mode 100644 index 00000000000..56cccd09ddf --- /dev/null +++ b/src/HotChocolate/Core/test/Types.Tests/__snapshots__/SemanticNonNullTests.Interface_With_Id_Field.snap @@ -0,0 +1,28 @@ +schema { + query: QueryWithInteface +} + +interface InterfaceImplementingNode implements Node { + field: String @semanticNonNull + id: ID! +} + +"The node interface is implemented by entities that have a global unique identifier." +interface Node { + id: ID! +} + +type QueryWithInteface { + "Fetches an object given its ID." + node("ID of the object." id: ID!): Node + "Lookup nodes by a list of IDs." + nodes("The list of node IDs." ids: [ID!]!): [Node] @semanticNonNull + someObject: SomeObject @semanticNonNull +} + +type SomeObject implements Node & InterfaceImplementingNode { + id: ID! + field: String @semanticNonNull +} + +directive @semanticNonNull(levels: [Int!] = [ 0 ]) on FIELD_DEFINITION