Skip to content

Commit

Permalink
Do not apply @semanticNonNull to Node
Browse files Browse the repository at this point in the history
  • Loading branch information
tobias-tengler committed Nov 6, 2024
1 parent 2af997c commit 7b7af03
Show file tree
Hide file tree
Showing 3 changed files with 75 additions and 12 deletions.
20 changes: 17 additions & 3 deletions src/HotChocolate/Core/src/Types/SemanticNonNullTypeInterceptor.cs
Original file line number Diff line number Diff line change
@@ -1,21 +1,20 @@
#nullable enable

using HotChocolate.Configuration;
using HotChocolate.Internal;
using HotChocolate.Language;
using HotChocolate.Language.Visitors;
using HotChocolate.Resolvers;
using HotChocolate.Types;
using HotChocolate.Types.Descriptors;
using HotChocolate.Types.Descriptors.Definitions;
using HotChocolate.Types.Helpers;
using HotChocolate.Types.Relay;

namespace HotChocolate;

// TODO: Ignore node and error interface from semantic non-null
public class SemanticNonNullTypeInterceptor : TypeInterceptor
{
private ITypeInspector _typeInspector = null!;
private ExtendedTypeReference _nodeTypeReference = null!;

internal override bool IsEnabled(IDescriptorContext context)
=> context.Options.EnableSemanticNonNull;
Expand All @@ -28,6 +27,8 @@ internal override void InitializeContext(
TypeReferenceResolver typeReferenceResolver)
{
_typeInspector = context.TypeInspector;

_nodeTypeReference = _typeInspector.GetTypeRef(typeof(NodeType));
}

public override void OnAfterCompleteName(ITypeCompletionContext completionContext, DefinitionBase definition)
Expand All @@ -45,20 +46,33 @@ public override void OnAfterCompleteName(ITypeCompletionContext completionContex
return;
}

var implementsNode = objectDef.Interfaces.Any(i => i.Equals(_nodeTypeReference));

foreach (var field in objectDef.Fields)
{
if (field.IsIntrospectionField)
{
continue;
}

if (implementsNode && field.Name == "id")
{
continue;
}

ApplySemanticNonNullDirective(field, completionContext);

field.FormatterDefinitions.Add(CreateSemanticNonNullResultFormatterDefinition());
}
}
else if (definition is InterfaceTypeDefinition interfaceDef)
{
if (interfaceDef.Name == "Node")
{
// The Node interface is well defined, so we don't want to go and change the type of its fields.
return;
}

foreach (var field in interfaceDef.Fields)
{
ApplySemanticNonNullDirective(field, completionContext);
Expand Down
44 changes: 35 additions & 9 deletions src/HotChocolate/Core/test/Types.Tests/SemanticNonNullTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,30 @@
using HotChocolate.Execution;
using HotChocolate.Tests;
using HotChocolate.Types;
using HotChocolate.Types.Relay;
using Microsoft.Extensions.DependencyInjection;

namespace HotChocolate;

// TODO: Test node & paging
public class SemanticNonNullTests
{
[Fact]
public async Task Object_Implementing_Node()
{
await new ServiceCollection()
.AddGraphQL()
.ModifyOptions(o =>
{
o.EnableSemanticNonNull = true;
o.EnsureAllNodesCanBeResolved = false;
})
.AddQueryType<QueryWithNode>()
.AddGlobalObjectIdentification()
.BuildSchemaAsync()
.MatchSnapshotAsync();
}

[Fact]
public async Task MutationConventions()
{
Expand All @@ -26,15 +43,6 @@ public async Task MutationConventions()
.MatchSnapshotAsync();
}

public class Mutation
{
[UseMutationConvention]
[Error<MyException>]
public bool DoSomething() => true;
}

public class MyException : Exception;

[Fact]
public async Task Derive_SemanticNonNull_From_ImplementationFirst()
{
Expand Down Expand Up @@ -283,4 +291,22 @@ public class Foo
{
public string Bar { get; } = default!;
}

[ObjectType("Query")]
public class QueryWithNode
{
public MyNode GetMyNode() => new(1);
}

[Node]
public record MyNode([property: ID] int Id);

public class Mutation
{
[UseMutationConvention]
[Error<MyException>]
public bool DoSomething() => true;
}

public class MyException : Exception;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
schema {
query: Query
}

"The node interface is implemented by entities that have a global unique identifier."
interface Node {
id: ID!
}

type MyNode implements Node {
id: ID!
}

type Query {
"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]!
myNode: MyNode @semanticNonNull
}

"TODO"
directive @semanticNonNull("TODO" levels: [Int!] = [ 0 ]) on FIELD_DEFINITION

0 comments on commit 7b7af03

Please sign in to comment.